From 863b80cd2b1fbcc137d5c70c72c8e92c03c79e0c Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Thu, 5 Sep 2019 16:50:11 -0700 Subject: [PATCH] First commit --- .gitignore | 10 + .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 47610 bytes .mvn/wrapper/maven-wrapper.properties | 1 + LICENSE.txt | 202 ++ README.md | 21 + clean.sh | 46 + mvnw | 225 +++ mvnw.cmd | 143 ++ pom.xml | 190 ++ samples/commandlinerunner-maven/README.md | 42 + samples/commandlinerunner-maven/pom.xml | 143 ++ .../com/example/commandlinerunner/CLR.java | 15 + .../CommandlinerunnerApplication.java | 35 + .../native-image/native-image.properties | 2 + .../src/main/resources/application.properties | 3 + .../CommandlinerunnerApplicationTests.java | 18 + samples/commandlinerunner/README.md | 23 + samples/commandlinerunner/compile.sh | 40 + samples/commandlinerunner/pom.xml | 155 ++ .../com/example/commandlinerunner/CLR.java | 15 + .../CommandlinerunnerApplication.java | 35 + .../src/main/resources/application.yml | 4 + .../CommandlinerunnerApplicationTests.java | 18 + samples/vanilla-grpc/README.adoc | 32 + samples/vanilla-grpc/compile.sh | 41 + samples/vanilla-grpc/pom.xml | 132 ++ .../java/com/example/ProtoApplication.java | 72 + .../src/main/proto/customer.proto | 33 + .../META-INF/native-image/reflect-config.json | 12 + .../native-image/resource-config.json | 5 + .../src/test/resources/customer.data | Bin 0 -> 14 bytes samples/vanilla-jpa/compile.sh | 43 + samples/vanilla-jpa/pom.xml | 170 ++ .../main/java/app/main/SampleApplication.java | 68 + .../src/main/java/app/main/model/Foo.java | 30 + .../java/app/main/model/FooRepository.java | 25 + .../META-INF/native-image/proxy-config.json | 3 + .../META-INF/native-image/reflect-config.json | 7 + .../src/main/resources/application.properties | 3 + .../src/main/resources/hibernate.properties | 1 + .../java/app/main/SampleApplicationTests.java | 52 + samples/vanilla-orm/.gitignore | 25 + .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 47610 bytes .../.mvn/wrapper/maven-wrapper.properties | 1 + samples/vanilla-orm/README.md | 2 + samples/vanilla-orm/compile.sh | 41 + samples/vanilla-orm/entity.sh | 60 + samples/vanilla-orm/mvnw | 225 +++ samples/vanilla-orm/mvnw.cmd | 225 +++ samples/vanilla-orm/pom.xml | 216 +++ .../main/BeanCountingApplicationListener.java | 81 + .../main/java/app/main/SampleApplication.java | 57 + .../app/main/ShutdownApplicationListener.java | 101 + .../app/main/StartupApplicationListener.java | 92 + .../src/main/java/app/main/model/Foo.java | 30 + .../main/resources/META-INF/spring.factories | 4 + .../src/main/resources/application.properties | 3 + .../src/main/resources/hibernate.properties | 1 + .../java/app/main/CaptureSystemOutput.java | 253 +++ .../app/main/CsvResultsWriterFactory.java | 181 ++ .../java/app/main/ProcessLauncherState.java | 310 +++ .../app/main/ProcessLauncherStateTests.java | 59 + .../java/app/main/SampleApplicationTests.java | 52 + .../test/java/app/main/SimpleBenchmark.java | 133 ++ .../java/app/main/VirtualMachineMetrics.java | 198 ++ .../jmh.mbr.core.ResultsWriterFactory | 1 + .../src/test/resources/logback.xml | 20 + samples/vanilla-rabbit/compile.sh | 41 + samples/vanilla-rabbit/pom.xml | 125 ++ .../src/main/java/app/main/Receiver.java | 28 + .../main/java/app/main/SampleApplication.java | 63 + .../src/main/java/app/main/model/Foo.java | 23 + .../META-INF/native-image/reflect-config.json | 12 + .../src/main/resources/application.properties | 2 + .../test/java/app/main/ClientApplication.java | 29 + .../java/app/main/SampleApplicationTests.java | 36 + .../src/test/resources/logback.xml | 20 + .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 49502 bytes .../.mvn/wrapper/maven-wrapper.properties | 1 + .../bin/.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 49502 bytes .../bin/.mvn/wrapper/maven-wrapper.properties | 1 + samples/vanilla-thymeleaf/bin/compile.sh | 42 + samples/vanilla-thymeleaf/bin/mvnw | 233 +++ samples/vanilla-thymeleaf/bin/mvnw.cmd | 145 ++ samples/vanilla-thymeleaf/bin/pom.xml | 116 ++ .../bin/src/main/java/hello/Application.class | Bin 0 -> 1304 bytes .../bin/src/main/java/hello/Greeting.class | Bin 0 -> 2685 bytes .../main/java/hello/GreetingController.class | Bin 0 -> 2472 bytes .../META-INF/native-image/reflect-config.json | 7 + .../bin/src/main/resources/static/index.html | 10 + .../main/resources/templates/greeting.html | 10 + .../test/java/hello/ApplicationTests.class | Bin 0 -> 2273 bytes samples/vanilla-thymeleaf/compile.sh | 42 + samples/vanilla-thymeleaf/mvnw | 233 +++ samples/vanilla-thymeleaf/mvnw.cmd | 145 ++ samples/vanilla-thymeleaf/pom.xml | 138 ++ .../src/main/java/hello/Application.java | 13 + .../main/java/hello/GreetingController.java | 54 + .../META-INF/native-image/reflect-config.json | 7 + .../src/main/resources/static/index.html | 10 + .../main/resources/templates/greeting.html | 10 + .../src/test/java/hello/ApplicationTests.java | 54 + samples/vanilla-tx/compile.sh | 43 + samples/vanilla-tx/pom.xml | 133 ++ .../src/main/java/app/main/Finder.java | 26 + .../src/main/java/app/main/Runner.java | 76 + .../main/java/app/main/SampleApplication.java | 29 + .../src/main/java/app/main/model/Foo.java | 27 + .../src/main/resources/application.properties | 2 + .../vanilla-tx/src/main/resources/schema.sql | 4 + .../java/app/main/SampleApplicationTests.java | 42 + .../vanilla-tx/src/test/resources/logback.xml | 20 + .../.mvn/wrapper/MavenWrapperDownloader.java | 114 ++ .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 48337 bytes .../.mvn/wrapper/maven-wrapper.properties | 1 + samples/webflux-netty/compile.sh | 41 + samples/webflux-netty/mvnw | 286 +++ samples/webflux-netty/mvnw.cmd | 161 ++ samples/webflux-netty/pom.xml | 133 ++ .../com/example/demo/DemoApplication.java | 28 + .../main/java/com/example/demo/Foobar.java | 13 + .../src/main/resources/application.properties | 0 .../src/main/resources/logging.properties | 12 + .../example/demo/DemoApplicationTests.java | 16 + src/json-shade/README.adoc | 5 + .../org/springframework/graal/json/JSON.java | 125 ++ .../springframework/graal/json/JSONArray.java | 675 +++++++ .../graal/json/JSONException.java | 51 + .../graal/json/JSONObject.java | 840 ++++++++ .../graal/json/JSONStringer.java | 453 +++++ .../graal/json/JSONTokener.java | 563 ++++++ .../svm/CleanerJava6Substitution.java | 33 + .../util/internal/svm/NettyIsAround.java | 17 + .../svm/PlatformDependent0Substitution.java | 33 + .../svm/PlatformDependentSubstitution.java | 39 + .../svm/UnsafeRefArrayAccessSubstitution.java | 32 + .../netty/util/internal/svm/package-info.java | 21 + .../dao/DataAccessException.java | 60 + .../InitializationDescriptor.java | 117 ++ .../InitializationJsonConverter.java | 60 + .../InitializationJsonMarshaller.java | 131 ++ .../domain/proxies/ProxiesDescriptor.java | 92 + .../ProxiesDescriptorJsonConverter.java | 92 + .../ProxiesDescriptorJsonMarshaller.java | 141 ++ .../graal/domain/proxies/ProxyDescriptor.java | 154 ++ .../graal/domain/reflect/ClassDescriptor.java | 232 +++ .../graal/domain/reflect/FieldDescriptor.java | 90 + .../graal/domain/reflect/JsonConverter.java | 88 + .../graal/domain/reflect/JsonMarshaller.java | 142 ++ .../domain/reflect/MemberDescriptor.java | 66 + .../domain/reflect/MethodDescriptor.java | 98 + .../domain/reflect/ReflectionDescriptor.java | 91 + .../domain/resources/ResourcesDescriptor.java | 86 + .../resources/ResourcesJsonConverter.java | 90 + .../resources/ResourcesJsonMarshaller.java | 113 ++ .../springframework/graal/support/App.java | 28 + .../graal/support/DynamicProxiesHandler.java | 76 + .../graal/support/InitializationHandler.java | 59 + .../graal/support/ReflectionHandler.java | 341 ++++ .../graal/support/ResourcesHandler.java | 610 ++++++ .../graal/support/SpringFeature.java | 84 + .../springframework/graal/support/Utils.java | 28 + .../graal/type/CompilationHint.java | 27 + .../graal/type/ConstantPoolScanner.java | 336 ++++ .../graal/type/HintDescriptor.java | 51 + .../springframework/graal/type/Method.java | 41 + .../graal/type/MissingTypeException.java | 30 + .../org/springframework/graal/type/Type.java | 1074 +++++++++++ .../graal/type/TypeSystem.java | 539 ++++++ .../svm/MessageInterpolatorIsAround.java | 18 + .../internal/svm/OnlyPresent.java | 33 + ...efaultMethodInvokingMethodInterceptor.java | 49 + ...nternal_PersistenceUnitInfoDescriptor.java | 52 + ...pringframework_boot_SpringBootVersion.java | 48 + ...validation_MessageInterpolatorFactory.java | 41 + ...ework_jdbc_EmbeddedDatabaseConnection.java | 37 + ...nceunit_defaultpersistenceunitmanager.java | 52 + .../jdbc/core/ConnectionCallback.java | 70 + .../embedded/EmbeddedDatabaseType.java | 37 + src/main/resources/initialization.json | 179 ++ src/main/resources/proxies.json | 26 + src/main/resources/reflect.json | 1689 +++++++++++++++++ src/main/resources/resources.json | 73 + .../support/graal/TypeSystemTest.java | 30 + 184 files changed, 17931 insertions(+) create mode 100644 .gitignore create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100755 clean.sh create mode 100755 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml create mode 100644 samples/commandlinerunner-maven/README.md create mode 100644 samples/commandlinerunner-maven/pom.xml create mode 100644 samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CLR.java create mode 100644 samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java create mode 100644 samples/commandlinerunner-maven/src/main/resources/META-INF/native-image/native-image.properties create mode 100644 samples/commandlinerunner-maven/src/main/resources/application.properties create mode 100644 samples/commandlinerunner-maven/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java create mode 100644 samples/commandlinerunner/README.md create mode 100755 samples/commandlinerunner/compile.sh create mode 100644 samples/commandlinerunner/pom.xml create mode 100644 samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CLR.java create mode 100644 samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java create mode 100644 samples/commandlinerunner/src/main/resources/application.yml create mode 100644 samples/commandlinerunner/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java create mode 100644 samples/vanilla-grpc/README.adoc create mode 100755 samples/vanilla-grpc/compile.sh create mode 100644 samples/vanilla-grpc/pom.xml create mode 100644 samples/vanilla-grpc/src/main/java/com/example/ProtoApplication.java create mode 100644 samples/vanilla-grpc/src/main/proto/customer.proto create mode 100644 samples/vanilla-grpc/src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 samples/vanilla-grpc/src/main/resources/META-INF/native-image/resource-config.json create mode 100644 samples/vanilla-grpc/src/test/resources/customer.data create mode 100755 samples/vanilla-jpa/compile.sh create mode 100644 samples/vanilla-jpa/pom.xml create mode 100644 samples/vanilla-jpa/src/main/java/app/main/SampleApplication.java create mode 100644 samples/vanilla-jpa/src/main/java/app/main/model/Foo.java create mode 100644 samples/vanilla-jpa/src/main/java/app/main/model/FooRepository.java create mode 100644 samples/vanilla-jpa/src/main/resources/META-INF/native-image/proxy-config.json create mode 100644 samples/vanilla-jpa/src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 samples/vanilla-jpa/src/main/resources/application.properties create mode 100644 samples/vanilla-jpa/src/main/resources/hibernate.properties create mode 100644 samples/vanilla-jpa/src/test/java/app/main/SampleApplicationTests.java create mode 100644 samples/vanilla-orm/.gitignore create mode 100644 samples/vanilla-orm/.mvn/wrapper/maven-wrapper.jar create mode 100644 samples/vanilla-orm/.mvn/wrapper/maven-wrapper.properties create mode 100644 samples/vanilla-orm/README.md create mode 100755 samples/vanilla-orm/compile.sh create mode 100755 samples/vanilla-orm/entity.sh create mode 100755 samples/vanilla-orm/mvnw create mode 100755 samples/vanilla-orm/mvnw.cmd create mode 100644 samples/vanilla-orm/pom.xml create mode 100644 samples/vanilla-orm/src/main/java/app/main/BeanCountingApplicationListener.java create mode 100644 samples/vanilla-orm/src/main/java/app/main/SampleApplication.java create mode 100644 samples/vanilla-orm/src/main/java/app/main/ShutdownApplicationListener.java create mode 100644 samples/vanilla-orm/src/main/java/app/main/StartupApplicationListener.java create mode 100644 samples/vanilla-orm/src/main/java/app/main/model/Foo.java create mode 100644 samples/vanilla-orm/src/main/resources/META-INF/spring.factories create mode 100644 samples/vanilla-orm/src/main/resources/application.properties create mode 100644 samples/vanilla-orm/src/main/resources/hibernate.properties create mode 100644 samples/vanilla-orm/src/test/java/app/main/CaptureSystemOutput.java create mode 100644 samples/vanilla-orm/src/test/java/app/main/CsvResultsWriterFactory.java create mode 100644 samples/vanilla-orm/src/test/java/app/main/ProcessLauncherState.java create mode 100644 samples/vanilla-orm/src/test/java/app/main/ProcessLauncherStateTests.java create mode 100644 samples/vanilla-orm/src/test/java/app/main/SampleApplicationTests.java create mode 100644 samples/vanilla-orm/src/test/java/app/main/SimpleBenchmark.java create mode 100644 samples/vanilla-orm/src/test/java/app/main/VirtualMachineMetrics.java create mode 100644 samples/vanilla-orm/src/test/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory create mode 100644 samples/vanilla-orm/src/test/resources/logback.xml create mode 100755 samples/vanilla-rabbit/compile.sh create mode 100644 samples/vanilla-rabbit/pom.xml create mode 100644 samples/vanilla-rabbit/src/main/java/app/main/Receiver.java create mode 100644 samples/vanilla-rabbit/src/main/java/app/main/SampleApplication.java create mode 100644 samples/vanilla-rabbit/src/main/java/app/main/model/Foo.java create mode 100644 samples/vanilla-rabbit/src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 samples/vanilla-rabbit/src/main/resources/application.properties create mode 100644 samples/vanilla-rabbit/src/test/java/app/main/ClientApplication.java create mode 100644 samples/vanilla-rabbit/src/test/java/app/main/SampleApplicationTests.java create mode 100644 samples/vanilla-rabbit/src/test/resources/logback.xml create mode 100644 samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.jar create mode 100644 samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.properties create mode 100644 samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.jar create mode 100644 samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.properties create mode 100755 samples/vanilla-thymeleaf/bin/compile.sh create mode 100755 samples/vanilla-thymeleaf/bin/mvnw create mode 100755 samples/vanilla-thymeleaf/bin/mvnw.cmd create mode 100644 samples/vanilla-thymeleaf/bin/pom.xml create mode 100644 samples/vanilla-thymeleaf/bin/src/main/java/hello/Application.class create mode 100644 samples/vanilla-thymeleaf/bin/src/main/java/hello/Greeting.class create mode 100644 samples/vanilla-thymeleaf/bin/src/main/java/hello/GreetingController.class create mode 100644 samples/vanilla-thymeleaf/bin/src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 samples/vanilla-thymeleaf/bin/src/main/resources/static/index.html create mode 100644 samples/vanilla-thymeleaf/bin/src/main/resources/templates/greeting.html create mode 100644 samples/vanilla-thymeleaf/bin/src/test/java/hello/ApplicationTests.class create mode 100755 samples/vanilla-thymeleaf/compile.sh create mode 100755 samples/vanilla-thymeleaf/mvnw create mode 100755 samples/vanilla-thymeleaf/mvnw.cmd create mode 100644 samples/vanilla-thymeleaf/pom.xml create mode 100644 samples/vanilla-thymeleaf/src/main/java/hello/Application.java create mode 100644 samples/vanilla-thymeleaf/src/main/java/hello/GreetingController.java create mode 100644 samples/vanilla-thymeleaf/src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 samples/vanilla-thymeleaf/src/main/resources/static/index.html create mode 100644 samples/vanilla-thymeleaf/src/main/resources/templates/greeting.html create mode 100644 samples/vanilla-thymeleaf/src/test/java/hello/ApplicationTests.java create mode 100755 samples/vanilla-tx/compile.sh create mode 100644 samples/vanilla-tx/pom.xml create mode 100644 samples/vanilla-tx/src/main/java/app/main/Finder.java create mode 100644 samples/vanilla-tx/src/main/java/app/main/Runner.java create mode 100644 samples/vanilla-tx/src/main/java/app/main/SampleApplication.java create mode 100644 samples/vanilla-tx/src/main/java/app/main/model/Foo.java create mode 100644 samples/vanilla-tx/src/main/resources/application.properties create mode 100644 samples/vanilla-tx/src/main/resources/schema.sql create mode 100644 samples/vanilla-tx/src/test/java/app/main/SampleApplicationTests.java create mode 100644 samples/vanilla-tx/src/test/resources/logback.xml create mode 100644 samples/webflux-netty/.mvn/wrapper/MavenWrapperDownloader.java create mode 100644 samples/webflux-netty/.mvn/wrapper/maven-wrapper.jar create mode 100644 samples/webflux-netty/.mvn/wrapper/maven-wrapper.properties create mode 100755 samples/webflux-netty/compile.sh create mode 100644 samples/webflux-netty/mvnw create mode 100644 samples/webflux-netty/mvnw.cmd create mode 100644 samples/webflux-netty/pom.xml create mode 100644 samples/webflux-netty/src/main/java/com/example/demo/DemoApplication.java create mode 100644 samples/webflux-netty/src/main/java/com/example/demo/Foobar.java create mode 100644 samples/webflux-netty/src/main/resources/application.properties create mode 100644 samples/webflux-netty/src/main/resources/logging.properties create mode 100644 samples/webflux-netty/src/test/java/com/example/demo/DemoApplicationTests.java create mode 100644 src/json-shade/README.adoc create mode 100644 src/json-shade/java/org/springframework/graal/json/JSON.java create mode 100644 src/json-shade/java/org/springframework/graal/json/JSONArray.java create mode 100644 src/json-shade/java/org/springframework/graal/json/JSONException.java create mode 100644 src/json-shade/java/org/springframework/graal/json/JSONObject.java create mode 100644 src/json-shade/java/org/springframework/graal/json/JSONStringer.java create mode 100644 src/json-shade/java/org/springframework/graal/json/JSONTokener.java create mode 100644 src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java create mode 100644 src/main/java/io/netty/util/internal/svm/NettyIsAround.java create mode 100644 src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java create mode 100644 src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java create mode 100644 src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java create mode 100644 src/main/java/io/netty/util/internal/svm/package-info.java create mode 100644 src/main/java/org/springframework/dao/DataAccessException.java create mode 100644 src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationDescriptor.java create mode 100644 src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonConverter.java create mode 100644 src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonMarshaller.java create mode 100644 src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptor.java create mode 100644 src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonConverter.java create mode 100644 src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonMarshaller.java create mode 100644 src/main/java/org/springframework/graal/domain/proxies/ProxyDescriptor.java create mode 100644 src/main/java/org/springframework/graal/domain/reflect/ClassDescriptor.java create mode 100644 src/main/java/org/springframework/graal/domain/reflect/FieldDescriptor.java create mode 100644 src/main/java/org/springframework/graal/domain/reflect/JsonConverter.java create mode 100644 src/main/java/org/springframework/graal/domain/reflect/JsonMarshaller.java create mode 100644 src/main/java/org/springframework/graal/domain/reflect/MemberDescriptor.java create mode 100644 src/main/java/org/springframework/graal/domain/reflect/MethodDescriptor.java create mode 100644 src/main/java/org/springframework/graal/domain/reflect/ReflectionDescriptor.java create mode 100644 src/main/java/org/springframework/graal/domain/resources/ResourcesDescriptor.java create mode 100644 src/main/java/org/springframework/graal/domain/resources/ResourcesJsonConverter.java create mode 100644 src/main/java/org/springframework/graal/domain/resources/ResourcesJsonMarshaller.java create mode 100644 src/main/java/org/springframework/graal/support/App.java create mode 100644 src/main/java/org/springframework/graal/support/DynamicProxiesHandler.java create mode 100644 src/main/java/org/springframework/graal/support/InitializationHandler.java create mode 100644 src/main/java/org/springframework/graal/support/ReflectionHandler.java create mode 100644 src/main/java/org/springframework/graal/support/ResourcesHandler.java create mode 100644 src/main/java/org/springframework/graal/support/SpringFeature.java create mode 100644 src/main/java/org/springframework/graal/support/Utils.java create mode 100644 src/main/java/org/springframework/graal/type/CompilationHint.java create mode 100644 src/main/java/org/springframework/graal/type/ConstantPoolScanner.java create mode 100644 src/main/java/org/springframework/graal/type/HintDescriptor.java create mode 100644 src/main/java/org/springframework/graal/type/Method.java create mode 100644 src/main/java/org/springframework/graal/type/MissingTypeException.java create mode 100644 src/main/java/org/springframework/graal/type/Type.java create mode 100644 src/main/java/org/springframework/graal/type/TypeSystem.java create mode 100644 src/main/java/org/springframework/internal/svm/MessageInterpolatorIsAround.java create mode 100644 src/main/java/org/springframework/internal/svm/OnlyPresent.java create mode 100644 src/main/java/org/springframework/internal/svm/Target_DefaultMethodInvokingMethodInterceptor.java create mode 100644 src/main/java/org/springframework/internal/svm/Target_org_hibernate_jpa_boot_internal_PersistenceUnitInfoDescriptor.java create mode 100644 src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_SpringBootVersion.java create mode 100644 src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_validation_MessageInterpolatorFactory.java create mode 100644 src/main/java/org/springframework/internal/svm/Target_org_springframework_jdbc_EmbeddedDatabaseConnection.java create mode 100644 src/main/java/org/springframework/internal/svm/Target_org_springframework_orm_jpa_persistenceunit_defaultpersistenceunitmanager.java create mode 100644 src/main/java/org/springframework/jdbc/core/ConnectionCallback.java create mode 100644 src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java create mode 100644 src/main/resources/initialization.json create mode 100644 src/main/resources/proxies.json create mode 100644 src/main/resources/reflect.json create mode 100644 src/main/resources/resources.json create mode 100644 src/test/java/org/springframework/support/graal/TypeSystemTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..84bcec752 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +dependency-reduced-pom.xml +.settings +.vscode +.factorypath +log.txt +.classpath +.project +unpack +target +.attach_pid* diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..9cc84ea9b4d95453115d0c26488d6a78694e0bc6 GIT binary patch literal 47610 zcmbTd1CXW7vMxN+wr$(CZCk5to71*!+jjS~ZJX1!ds=tCefGhB{(HVS`>u$J^~PFn zW>r>YRc2N`sUQsug7OUl0^-}ZZ-jr^e|{kUJj#ly2+~T*iO~apQ;-J#>z!{v|9nH? zexD9D~4A70;F%I|$?{aX9)~)7!NMGs_XtoO(D2z3Q#5Lmj zOYWk1b{iMmsdX30UFmYyZk1gWICVeOtk^$+{3U2(8gx?WA2F!EfBPf&|1?AJ|5Z>M zfUAk^zcf#n|9^4|J34286~NKrUt&c5cZ~iqE?PH7fW5tm3-qG$) z56%`QPSn!0RMV3)jjXfG^UQ}*^yBojH!}58lPlDclX5iUhf*|DV=~e*bl;(l$Wn@r zPE*iH(NK!e9KQcU$rRM}aJc?-&H1PO&vOs*=U+QVvwuk-=zr1x>;XpRCjSyC;{TWQ z|824V8t*^*{x=5yn^pP#-?k<5|7|4y&Pd44&e_TN&sxg@ENqpX0glclj&w%W04Jwp zwJ}#@ag^@h5VV4H5U@i7V#A*a;4bzM-y_rd{0WG#jRFPJU}(#&o8vo@uM+B+$>Tiq zei^5$wg8CVf{+_#Vh`yPx-6TmB~zT_nocS_Rb6&EYp*KjbN#-aP<~3j=NVuR)S1wm zdy3AWx2r9uww3eNJxT>{tdmY4#pLw`*`_fIwSu;yzFYP)=W6iawn`s*omzNbR?E&LyC17rFcjWp!M~p?;{v!78DTxtF85BK4dT< zA5p)Z%6O}mP?<%Z{>nZmbVEbomm zLgy;;N&!y>Dma2sqmbvz&KY-j&s~dd#mWGlNF%7}vS7yt>Dm{P=X zG>Pyv2D!ba0CcTI*G6-v?!0}`EWm1d?K)DgZIQk9eucI&lBtR))NxqVz)+hBR1b|7 zgv&^46cI?mgCvp>lY9W(nJT#^<*kY3o#Php1RZLY@ffmLLq3A!Yd}O~n@BhXVp`<5 zJx`BjR%Svv)Sih_8TFg-9F-Gg3^kQrpDGej@uT5%y_9NSsk5SW>7{>&11u(JZHsZO zZweI|!&qHl0;7qxijraQo=oV^Pi~bNlzx;~b2+hXreonWGD%C$fyHs+8d1kKN>TgB z{Mu?~E{=l1osx|_8P*yC>81_GB7>NS7UA+x2k_c*cU-$gQjR{+IU)z069Ic$<)ci< zb?+V#^-MK!0s~wRP|grx?P^8EZ(9Jt0iA{`uVS6fNo>b@as5_-?e766V}&)8ZOEVtKB z*HtHAqat+2lbJbEI#fl~`XKNIF&J?PHKq)A!z(#j%)Uby=5d!bQP)-Mr!0#J=FV%@9G#Cby%r#(S=23H#9d)5Ndy>pIXJ%si!D=m*-QQZ(O9~#Jhx#AS3 z&Vs+*E5>d+{ib4>FEd#L15-ovl*zV%SYSWF>Z}j!vGn=g%w0~3XvAK&$Dl@t5hiUa#mT(4s9-JF1l zPi5d2YmuFJ4S(O>g~H)5l_`%h3qm?+8MmhXA>GRN}7GX;$4(!WTkYZB=TA^8ZFh^d9_@x$fK4qenP!zzaqQ1^(GQ- zjC$P$B5o{q&-H8UH_$orJTv0}#|9ja(vW9gA%l|@alYk+Uth1ey*ax8wmV7U?^Z9? zsQMrEzP8|_s0=bii4wDWa7te&Vmh9T>fcUXJS|dD3Y$A`s-7kY!+idEa`zB) zaW*%xb+#}9INSa62(M1kwL=m_3E2T|l5Sm9QmON8ewxr#QR`;vOGCgyMsA8$O(;=U z#sEw)37duzeM#9_7l!ly#5c+Mu3{;<9%O{e z`+0*{COEF^py;f6)y6NX)gycj`uU9pdZMum9h(bS!zu1gDXdmF4{Og{u;d(Dr~Co1 z1tm@i#5?>oL}-weK1zJRlLv*+M?l=eI~Sp9vg{R6csq=3tYSB2pqB8 z=#p`us7r|uH=cZnGj|juceAu8J#vb+&UFLFmGn~9O|TNeGH>sboBl%JI9v(@^|45? zLvr2ha)NWP4yxV8K%dU(Ae=zl)qdGyz={$my;Vs6?4?2*1?&u!OFyFbAquv6@1e)~&Rp#Ww9O88!mrze((=@F?&BPl_u9gK4VlHo@4gLK_pGtEA(gO4YpIIWTrFN zqVi%Q{adXq^Ez~dZ0VUC>DW`pGtpTY<9tMd;}WZUhT1iy+S^TfHCWXGuDwAv1Ik85 zh3!tSlWU3*aLtmdf?g(#WnLvVCXW$>gnT_{(%VilR=#2VKh~S}+Po#ha9C*<-l~Fx z$EK{1SO8np&{JC)7hdM8O+C( zF^s3HskJz@p3ot`SPKA92PG!PmC2d|9xA!CZxR!rK9-QYYBGAM-Gj zCqzBaIjtOZ6gu+lA%**RI7to$x^s8xIx}VF96=<29CjWtsl;tmNbuHgrCyB^VzEIB zt@sqnl8Vg`pnMppL6vbjNNKc?BrH<)fxiZ|WrYW%cnz-FMENGzMI+)@l7dit?oP|Wu zg-oLcv~79=fdqEM!zK%lI=R7S!Do!HBaD+*h^ULWVB}4jr^e5oUqY`zA&NUvzseI% z+XCvzS+n|m7WJoyjXXk(PE8;i^r$#Pq|NFd!{g~m2OecA1&>$7SYFw z;}Q{`F3LCE34Z>5;5dDtz&2Z&w|B9fwvU<@S<BBo(L4SbDV#X3%uS+<2q7iH+0baiGzlVP5n0fBDP z7kx+7|Cws+?T|cw-pt~SIa7BRDI_ATZ9^aQS^1I?WfnfEHZ*sGlT#Wk9djDL?dWLA zk%(B?<8L?iV*1m803UW|*sU$raq<(!N!CrQ&y7?7_g zF2!aAfw5cWqO}AX)+v)5_GvQ$1W8MV8bTMr3P{^!96Q4*YhS}9ne|+3GxDJmZEo zqh;%RqD5&32iTh7kT>EEo_%`8BeK&)$eXQ-o+pFIP!?lee z&kos;Q)_afg1H&{X|FTQ0V z@yxv4KGGN)X|n|J+(P6Q`wmGB;J}bBY{+LKVDN9#+_w9s$>*$z)mVQDOTe#JG)Zz9*<$LGBZ-umW@5k5b zbIHp=SJ13oX%IU>2@oqcN?)?0AFN#ovwS^|hpf5EGk0#N<)uC{F}GG}%;clhikp2* zu6ra2gL@2foI>7sL`(x5Q)@K2$nG$S?g`+JK(Q0hNjw9>kDM|Gpjmy=Sw5&{x5$&b zE%T6x(9i|z4?fMDhb%$*CIe2LvVjuHca`MiMcC|+IU51XfLx(BMMdLBq_ z65RKiOC$0w-t)Cyz0i-HEZpkfr$>LK%s5kga^FIY_|fadzu*r^$MkNMc!wMAz3b4P+Z3s(z^(%(04}dU>ef$Xmof(A|XXLbR z2`&3VeR1&jjKTut_i?rR_47Z`|1#$NE$&x#;NQM|hxDZ>biQ*+lg5E62o65ILRnOOOcz%Q;X$MJ?G5dYmk$oL_bONX4 zT^0yom^=NsRO^c$l02#s0T^dAAS&yYiA=;rLx;{ro6w08EeTdVF@j^}Bl;o=`L%h! zMKIUv(!a+>G^L3{z7^v3W$FUUHA+-AMv~<}e?2?VG|!itU~T>HcOKaqknSog zE}yY1^VrdNna1B6qA`s?grI>Y4W%)N;~*MH35iKGAp*gtkg=FE*mFDr5n2vbhwE|4 zZ!_Ss*NMZdOKsMRT=uU{bHGY%Gi=K{OD(YPa@i}RCc+mExn zQogd@w%>14cfQrB@d5G#>Lz1wEg?jJ0|(RwBzD74Eij@%3lyoBXVJpB{q0vHFmE7^ zc91!c%pt&uLa|(NyGF2_L6T{!xih@hpK;7B&bJ#oZM0`{T6D9)J2IXxP?DODPdc+T zC>+Zq8O%DXd5Gog2(s$BDE3suv=~s__JQnX@uGt+1r!vPd^MM}=0((G+QopU?VWgR zqj8EF0?sC`&&Nv-m-nagB}UhXPJUBn-UaDW9;(IX#)uc zL*h%hG>ry@a|U=^=7%k%V{n=eJ%Nl0Oqs!h^>_PgNbD>m;+b)XAk+4Cp=qYxTKDv& zq1soWt*hFf%X8}MpQZL-Lg7jc0?CcWuvAOE(i^j1Km^m8tav)lMx1GF{?J#*xwms2 z3N_KN-31f;@JcW(fTA`J5l$&Q8x{gb=9frpE8K0*0Rm;yzHnDY0J{EvLRF0 zRo6ca)gfv6C)@D#1I|tgL~uHJNA-{hwJQXS?Kw=8LU1J$)nQ-&Jhwxpe+%WeL@j0q z?)92i;tvzRki1P2#poL;YI?9DjGM4qvfpsHZQkJ{J^GNQCEgUn&Sg=966 zq?$JeQT+vq%zuq%%7JiQq(U!;Bsu% zzW%~rSk1e+_t89wUQOW<8%i|5_uSlI7BcpAO20?%EhjF%s%EE8aY15u(IC za2lfHgwc;nYnES7SD&Lf5IyZvj_gCpk47H}e05)rRbfh(K$!jv69r5oI| z?){!<{InPJF6m|KOe5R6++UPlf(KUeb+*gTPCvE6! z(wMCuOX{|-p(b~)zmNcTO%FA z$-6}lkc*MKjIJ(Fyj^jkrjVPS);3Qyq~;O$p+XT+m~0$HsjB@}3}r*h(8wGbH9ktQ zbaiiMSJf`6esxC3`u@nNqvxP1nBwerm|KN)aBzu$8v_liZ0(G8}*jB zv<8J%^S2E_cu+Wp1;gT66rI$>EwubN4I(Lo$t8kzF@?r0xu8JX`tUCpaZi(Q0~_^K zs6pBkie9~06l>(Jpy*d&;ZH{HJ^Ww6>Hs!DEcD{AO42KX(rTaj)0ox`;>}SRrt)N5 zX)8L4Fg)Y6EX?He?I`oHeQiGJRmWOAboAC4Jaf;FXzspuG{+3!lUW8?IY>3%)O546 z5}G94dk)Y>d_%DcszEgADP z8%?i~Ak~GQ!s(A4eVwxPxYy3|I~3I=7jf`yCDEk_W@yfaKjGmPdM}($H#8xGbi3l3 z5#?bjI$=*qS~odY6IqL-Q{=gdr2B5FVq7!lX}#Lw**Pyk!`PHN7M3Lp2c=T4l}?kn zVNWyrIb(k&`CckYH;dcAY7-kZ^47EPY6{K(&jBj1Jm>t$FD=u9U z#LI%MnI3wPice+0WeS5FDi<>~6&jlqx=)@n=g5TZVYdL@2BW3w{Q%MkE%sx}=1ihvj(HDjpx!*qqta?R?| zZ(Ju_SsUPK(ZK*&EdAE(Fj%eABf2+T>*fZ6;TBP%$xr(qv;}N@%vd5iGbzOgyMCk* z3X|-CcAz%}GQHalIwd<-FXzA3btVs-_;!9v7QP)V$ruRAURJhMlw7IO@SNM~UD)2= zv}eqKB^kiB))Yhh%v}$ubb#HBQHg3JMpgNF+pN*QbIx(Rx1ofpVIL5Y{)0y&bMO(@ zyK1vv{8CJQidtiI?rgYVynw{knuc!EoQ5-eete(AmM`32lI7{#eS#!otMBRl21|g^SVHWljl8jU?GU@#pYMIqrt3mF|SSYI&I+Vz|%xuXv8;pHg zlzFl!CZ>X%V#KWL3+-743fzYJY)FkKz>GJ<#uKB)6O8NbufCW%8&bQ^=8fHYfE(lY z1Fl@4l%|iaTqu=g7tTVk)wxjosZf2tZ2`8xs9a$b1X29h!9QP#WaP#~hRNL>=IZO@SX4uYQR_c0pSt89qQR@8gJhL*iXBTSBDtlsiNvc_ewvY-cm%bd&sJTnd@hE zwBGvqGW$X^oD~%`b@yeLW%An*as@4QzwdrpKY9-E%5PLqvO6B+bf>ph+TWiPD?8Ju z-V}p@%LcX{e)?*0o~#!S%XU<+9j>3{1gfU=%sHXhukgH+9z!)AOH_A{H3M}wmfmU8 z&9jjfwT-@iRwCbIEwNP4zQHvX3v-d*y87LoudeB9Jh5+mf9Mnj@*ZCpwpQ*2Z9kBWdL19Od7q|Hdbwv+zP*FuY zQc4CJ6}NIz7W+&BrB5V%{4Ty$#gf#V<%|igk)b@OV`0@<)cj(tl8~lLtt^c^l4{qP z=+n&U0LtyRpmg(_8Qo|3aXCW77i#f{VB?JO3nG!IpQ0Y~m!jBRchn`u>HfQuJwNll zVAMY5XHOX8T?hO@7Vp3b$H)uEOy{AMdsymZ=q)bJ%n&1;>4%GAjnju}Osg@ac*O?$ zpu9dxg-*L(%G^LSMhdnu=K)6ySa|}fPA@*Saj}Z>2Dlk~3%K(Py3yDG7wKij!7zVp zUZ@h$V0wJ|BvKc#AMLqMleA*+$rN%#d95$I;;Iy4PO6Cih{Usrvwt2P0lh!XUx~PGNySbq#P%`8 zb~INQw3Woiu#ONp_p!vp3vDl^#ItB06tRXw88L}lJV)EruM*!ZROYtrJHj!X@K$zJ zp?Tb=Dj_x1^)&>e@yn{^$B93%dFk~$Q|0^$=qT~WaEU-|YZZzi`=>oTodWz>#%%Xk z(GpkgQEJAibV%jL#dU)#87T0HOATp~V<(hV+CcO?GWZ_tOVjaCN13VQbCQo=Dt9cG znSF9X-~WMYDd66Rg8Ktop~CyS7@Pj@Vr<#Ja4zcq1}FIoW$@3mfd;rY_Ak^gzwqqD z^4<_kC2Eyd#=i8_-iZ&g_e#$P`;4v zduoZTdyRyEZ-5WOJwG-bfw*;7L7VXUZ8aIA{S3~?()Yly@ga|-v%?@2vQ;v&BVZlo7 z49aIo^>Cv=gp)o?3qOraF_HFQ$lO9vHVJHSqq4bNNL5j%YH*ok`>ah?-yjdEqtWPo z+8i0$RW|$z)pA_vvR%IVz4r$bG2kSVM&Z;@U*{Lug-ShiC+IScOl?O&8aFYXjs!(O z^xTJ|QgnnC2!|xtW*UOI#vInXJE!ZpDob9x`$ox|(r#A<5nqbnE)i<6#(=p?C~P-7 zBJN5xp$$)g^l};@EmMIe;PnE=vmPsTRMaMK;K`YTPGP0na6iGBR8bF%;crF3>ZPoLrlQytOQrfTAhp;g){Mr$zce#CA`sg^R1AT@tki!m1V zel8#WUNZfj(Fa#lT*nT>^pY*K7LxDql_!IUB@!u?F&(tfPspwuNRvGdC@z&Jg0(-N z(oBb3QX4em;U=P5G?Y~uIw@E7vUxBF-Ti*ccU05WZ7`m=#4?_38~VZvK2{MW*3I#fXoFG3?%B;ki#l%i#$G_bwYQR-4w>y;2` zMPWDvmL6|DP1GVXY)x+z8(hqaV5RloGn$l&imhzZEZP6v^d4qAgbQ~bHZEewbU~Z2 zGt?j~7`0?3DgK+)tAiA8rEst>p#;)W=V+8m+%}E$p-x#)mZa#{c^3pgZ9Cg}R@XB) zy_l7jHpy(u;fb+!EkZs6@Z?uEK+$x3Ehc8%~#4V?0AG0l(vy{8u@Md5r!O+5t zsa{*GBn?~+l4>rChlbuT9xzEx2yO_g!ARJO&;rZcfjzxpA0Chj!9rI_ZD!j` z6P@MWdDv&;-X5X8o2+9t%0f1vJk3R~7g8qL%-MY9+NCvQb)%(uPK4;>y4tozQ2Dl* zEoR_1#S~oFrd9s%NOkoS8$>EQV|uE<9U*1uqAYWCZigiGlMK~vSUU}f5M9o{<*WW? z$kP)2nG$My*fUNX3SE!g7^r#zTT^mVa#A*5sBP8kz4se+o3y}`EIa)6)VpKmto6Ew z1J-r2$%PM4XUaASlgVNv{BBeL{CqJfFO|+QpkvsvVBdCA7|vlwzf1p$Vq50$Vy*O+ z5Eb85s^J2MMVj53l4_?&Wpd1?faYE-X1ml-FNO-|a;ZRM*Vp!(ods{DY6~yRq%{*< zgq5#k|KJ70q47aO1o{*gKrMHt)6+m(qJi#(rAUw0Uy8~z8IX)>9&PTxhLzh#Oh*vZ zPd1b$Z&R{yc&TF^x?iQCw#tV}la&8^W)B*QZ${19LlRYgu#nF7Zj`~CtO^0S#xp+r zLYwM~si$I>+L}5gLGhN=dyAKO)KqPNXUOeFm#o+3 z&#!bD%aTBT@&;CD_5MMC&_Yi+d@nfuxWSKnYh0%~{EU`K&DLx}ZNI2osu#(gOF2}2 zZG#DdQ|k0vXj|PxxXg-MYSi9gI|hxI%iP)YF2$o< zeiC8qgODpT?j!l*pj_G(zXY2Kevy~q=C-SyPV$~s#f-PW2>yL}7V+0Iu^wH;AiI$W zcZDeX<2q%!-;Ah!x_Ld;bR@`bR4<`FTXYD(%@CI#biP z5BvN;=%AmP;G0>TpInP3gjTJanln8R9CNYJ#ziKhj(+V33zZorYh0QR{=jpSSVnSt zGt9Y7Bnb#Ke$slZGDKti&^XHptgL7 zkS)+b>fuz)B8Lwv&JV*};WcE2XRS63@Vv8V5vXeNsX5JB?e|7dy$DR9*J#J= zpKL@U)Kx?Y3C?A3oNyJ5S*L+_pG4+X*-P!Er~=Tq7=?t&wwky3=!x!~wkV$Ufm(N| z1HY?`Ik8?>%rf$6&0pxq8bQl16Jk*pwP`qs~x~Trcstqe-^hztuXOG zrYfI7ZKvK$eHWi9d{C${HirZ6JU_B`f$v@SJhq?mPpC-viPMpAVwE;v|G|rqJrE5p zRVf904-q{rjQ=P*MVKXIj7PSUEzu_jFvTksQ+BsRlArK&A*=>wZPK3T{Ki-=&WWX= z7x3VMFaCV5;Z=X&(s&M^6K=+t^W=1>_FFrIjwjQtlA|-wuN7&^v1ymny{51gZf4-V zU8|NSQuz!t<`JE%Qbs||u-6T*b*>%VZRWsLPk&umJ@?Noo5#{z$8Q0oTIv00`2A`# zrWm^tAp}17z72^NDu^95q1K)6Yl`Wvi-EZA+*i&8%HeLi*^9f$W;f1VF^Y*W;$3dk|eLMVb_H{;0f*w!SZMoon+#=CStnG-7ZU8V>Iy( zmk;42e941mi7!e>J0~5`=NMs5g)WrdUo^7sqtEvwz8>H$qk=nj(pMvAb4&hxobPA~p&-L5a_pTs&-0XCm zKXZ8BkkriiwE)L2CN$O-`#b15yhuQO7f_WdmmG<-lKeTBq_LojE&)|sqf;dt;llff znf|C$@+knhV_QYVxjq*>y@pDK|DuZg^L{eIgMZnyTEoe3hCgVMd|u)>9knXeBsbP_$(guzw>eV{?5l$ z063cqIysrx82-s6k;vE?0jxzV{@`jY3|*Wp?EdNUMl0#cBP$~CHqv$~sB5%50`m(( zSfD%qnxbGNM2MCwB+KA?F>u__Ti>vD%k0#C*Unf?d)bBG6-PYM!!q;_?YWptPiHo} z8q3M~_y9M6&&0#&uatQD6?dODSU)%_rHen`ANb z{*-xROTC1f9d!8`LsF&3jf{OE8~#;>BxHnOmR}D80c2Eh zd867kq@O$I#zEm!CCZJw8S`mCx}HrCl_Rh4Hsk{Cb_vJ4VA3GK+icku z%lgw)Y@$A0kzEV^#=Zj8i6jPk&Mt_bKDD!jqY3&W(*IPbzYu$@x$|3*aP{$bz-~xE^AOxtbyWvzwaCOHv6+99llI&xT_8)qX3u|y|0rDV z(Hu*#5#cN0mw4OSdY$g_xHo-zyZ-8WW&4r%qW(=5N>0O-t{k;#G9X81F~ynLV__Kz zbW1MA>Pjg0;3V?iV+-zQsll_0jimGuD|0GNW^av|4yes(PkR1bGZwO6xvgCy}ThR7?d&$N`kA3N!Xn5uSKKCT-`{lE1ZYYy?GzL}WF+mh|sgT6K2Z*c9YB zFSpGRNgYvk&#<2@G(vUM5GB|g?gk~-w+I4C{vGu{`%fiNuZIeu@V1qt`-x$E?OR;zu866Y@2^et5GTNCpX#3D=|jD5>lT^vD$ zr}{lRL#Lh4g45Yj43Vs7rxUb*kWC?bpKE1@75OJQ=XahF z5(C0DyF;at%HtwMTyL!*vq6CLGBi^Ey}Mx39TC2$a)UmekKDs&!h>4Hp2TmSUi!xo zWYGmyG)`$|PeDuEL3C6coVtit>%peYQ6S1F4AcA*F`OA;qM+1U6UaAI(0VbW#!q9* zz82f@(t35JH!N|P4_#WKK6Rc6H&5blD6XA&qXahn{AP=oKncRgH!&=b6WDz?eexo* z9pzh}_aBc_R&dZ+OLk+2mK-5UhF`>}{KN7nOxb{-1 zd`S-o1wgCh7k0u%QY&zoZH}!<;~!)3KTs-KYRg}MKP3Vl%p$e6*MOXLKhy)<1F5L* z+!IH!RHQKdpbT8@NA+BFd=!T==lzMU95xIyJ13Z6zysYQ1&zzH!$BNU(GUm1QKqm< zTo#f%;gJ@*o;{#swM4lKC(QQ<%@;7FBskc7$5}W9Bi=0heaVvuvz$Ml$TR8@}qVn>72?6W1VAc{Mt}M zkyTBhk|?V}z`z$;hFRu8Vq;IvnChm+no@^y9C1uugsSU`0`46G#kSN9>l_ozgzyqc zZnEVj_a-?v@?JmH1&c=~>-v^*zmt`_@3J^eF4e))l>}t2u4L`rueBR=jY9gZM;`nV z>z(i<0eedu2|u-*#`SH9lRJ7hhDI=unc z?g^30aePzkL`~hdH*V7IkDGnmHzVr%Q{d7sfb7(|)F}ijXMa7qg!3eHex)_-$X;~* z>Zd8WcNqR>!`m#~Xp;r4cjvfR{i04$&f1)7sgen9i>Y|3)DCt^f)`uq@!(SG?w|tdSLS+<;ID74 zTq8FJYHJHrhSwvKL|O1ZnSbG-=l6Eg-Suv60Xc;*bq~g+LYk*Q&e)tR_h3!(y)O}$ zLi*i5ec^uHkd)fz2KWiR;{RosL%peU`TxM7w*M9m#rAiG`M)FTB>=X@|A`7x)zn5- z$MB5>0qbweFB249EI@!zL~I7JSTZbzjSMMJ=!DrzgCS!+FeaLvx~jZXwR`BFxZ~+A z=!Pifk?+2awS3DVi32fgZRaqXZq2^->izZpIa1sEog@01#TuEzq%*v359787rZoC( z9%`mDR^Hdxb%XzUt&cJN3>Cl{wmv{@(h>R38qri1jLKds0d|I?%Mmhu2pLy=< zOkKo4UdS`E9Y~z3z{5_K+j~i7Ou}q0?Qv4YebBya1%VkkWzR%+oB!c?9(Ydaka32! zTEv*zgrNWs`|~Q{h?O|8s0Clv{Kg0$&U}?VFLkGg_y=0Qx#=P${6SNQFp!tDsTAPV z0Ra{(2I7LAoynS0GgeQ6_)?rYhUy}AE^$gwmg?i!x#<9eP=0N=>ZgB#LV9|aH8q#B za|O-vu(GR|$6Ty!mKtIfqWRS-RO4M0wwcSr9*)2A5`ZyAq1`;6Yo)PmDLstI zL2%^$1ikF}0w^)h&000z8Uc7bKN6^q3NBfZETM+CmMTMU`2f^a#BqoYm>bNXDxQ z`3s6f6zi5sj70>rMV-Mp$}lP|jm6Zxg}Sa*$gNGH)c-upqOC7vdwhw}e?`MEMdyaC zP-`+83ke+stJPTsknz0~Hr8ea+iL>2CxK-%tt&NIO-BvVt0+&zsr9xbguP-{3uW#$ z<&0$qcOgS{J|qTnP;&!vWtyvEIi!+IpD2G%Zs>;k#+d|wbodASsmHX_F#z?^$)zN5 zpQSLH`x4qglYj*{_=8p>!q39x(y`B2s$&MFQ>lNXuhth=8}R}Ck;1}MI2joNIz1h| zjlW@TIPxM_7 zKBG{Thg9AP%B2^OFC~3LG$3odFn_mr-w2v**>Ub7da@>xY&kTq;IGPK5;^_bY5BP~ z2fiPzvC&osO@RL)io905e4pY3Yq2%j&)cfqk|($w`l`7Pb@407?5%zIS9rDgVFfx! zo89sD58PGBa$S$Lt?@8-AzR)V{@Q#COHi-EKAa5v!WJtJSa3-Wo`#TR%I#UUb=>j2 z7o-PYd_OrbZ~3K`pn*aw2)XKfuZnUr(9*J<%z@WgC?fexFu%UY!Yxi6-63kAk7nsM zlrr5RjxV45AM~MPIJQqKpl6QmABgL~E+pMswV+Knrn!0T)Ojw{<(yD8{S|$(#Z!xX zpH9_Q>5MoBKjG%zzD*b6-v>z&GK8Dfh-0oW4tr(AwFsR(PHw_F^k((%TdkglzWR`iWX>hT1rSX;F90?IN4&}YIMR^XF-CEM(o(W@P#n?HF z!Ey(gDD_0vl+{DDDhPsxspBcks^JCEJ$X74}9MsLt=S?s3)m zQ0cSrmU*<u;KMgi1(@Ip7nX@4Zq>yz;E<(M8-d0ksf0a2Ig8w2N-T69?f}j}ufew}LYD zxr7FF3R7yV0Gu^%pXS^49){xT(nPupa(8aB1>tfKUxn{6m@m1lD>AYVP=<)fI_1Hp zIXJW9gqOV;iY$C&d=8V)JJIv9B;Cyp7cE}gOoz47P)h)Y?HIE73gOHmotX1WKFOvk z5(t$Wh^13vl;+pnYvJGDz&_0Hd3Z4;Iwa-i3p|*RN7n?VJ(whUPdW>Z-;6)Re8n2# z-mvf6o!?>6wheB9q}v~&dvd0V`8x&pQkUuK_D?Hw^j;RM-bi_`5eQE5AOIzG0y`Hr zceFx7x-<*yfAk|XDgPyOkJ?){VGnT`7$LeSO!n|o=;?W4SaGHt4ngsy@=h-_(^qX)(0u=Duy02~Fr}XWzKB5nkU$y`$67%d^(`GrAYwJ? zN75&RKTlGC%FP27M06zzm}Y6l2(iE*T6kdZPzneMK9~m)s7J^#Q=B(Okqm1xB7wy< zNC>)8Tr$IG3Q7?bxF%$vO1Y^Qhy>ZUwUmIW5J4=ZxC|U)R+zg4OD$pnQ{cD`lp+MM zS3RitxImPC0)C|_d18Shpt$RL5iIK~H z)F39SLwX^vpz;Dcl0*WK*$h%t0FVt`Wkn<=rQ6@wht+6|3?Yh*EUe+3ISF zbbV(J6NNG?VNIXC)AE#(m$5Q?&@mjIzw_9V!g0#+F?)2LW2+_rf>O&`o;DA!O39Rg ziOyYKXbDK!{#+cj_j{g;|IF`G77qoNBMl8r@EIUBf+7M|eND2#Y#-x=N_k3a52*fi zp-8K}C~U4$$76)@;@M@6ZF*IftXfwyZ0V+6QESKslI-u!+R+?PV=#65d04(UI%}`r z{q6{Q#z~xOh}J=@ZN<07>bOdbSI(Tfcu|gZ?{YVVcOPTTVV52>&GrxwumlIek}OL? zeGFo#sd|C_=JV#Cu^l9$fSlH*?X|e?MdAj8Uw^@Dh6+eJa?A?2Z#)K zvr7I|GqB~N_NU~GZ?o1A+fc@%HlF$71Bz{jOC{B*x=?TsmF0DbFiNcnIuRENZA43a zfFR89OAhqSn|1~L4sA9nVHsFV4xdIY_Ix>v0|gdP(tJ^7ifMR_2i4McL#;94*tSY) zbwcRqCo$AnpV)qGHZ~Iw_2Q1uDS2XvFff#5BXjO!w&1C^$Pv^HwXT~vN0l}QsTFOz zp|y%Om9}{#!%cPR8d8sc4Y@BM+smy{aU#SHY>>2oh1pK+%DhPqc2)`!?wF{8(K$=~ z<4Sq&*`ThyQETvmt^NaN{Ef2FQ)*)|ywK%o-@1Q9PQ_)$nJqzHjxk4}L zJRnK{sYP4Wy(5Xiw*@M^=SUS9iCbSS(P{bKcfQ(vU?F~)j{~tD>z2I#!`eFrSHf;v zquo)*?AW$#+qP}n$%<{;wr$()*yw5N`8_rOTs^kOqyY;dIjsdw*6k_mL}v2V9C_*sK<_L8 za<3)C%4nRybn^plZ(y?erFuRVE9g%mzsJzEi5CTx?wwx@dpDFSOAubRa_#m+=AzZ~ z^0W#O2zIvWEkxf^QF660(Gy8eyS`R$N#K)`J732O1rK4YHBmh|7zZ`!+_91uj&3d} zKUqDuDQ8YCmvx-Jv*$H%{MrhM zw`g@pJYDvZp6`2zsZ(dm)<*5p3nup(AE6}i#Oh=;dhOA=V7E}98CO<1Lp3*+&0^`P zs}2;DZ15cuT($%cwznqmtTvCvzazAVu5Ub5YVn#Oo1X|&MsVvz8c5iwRi43-d3T%tMhcK#ke{i-MYad@M~0B_p`Iq){RLadp-6!peP^OYHTq~^vM zqTr5=CMAw|k3QxxiH;`*;@GOl(PXrt(y@7xo$)a3Fq4_xRM_3+44!#E zO-YL^m*@}MVI$5PM|N8Z2kt-smM>Jj@Dkg5%`lYidMIbt4v=Miqj4-sEE z)1*5VCqF1I{KZVw`U0Wa!+)|uiOM|=gM65??+k|{E6%76MqT>T+;z{*&^5Q9ikL2D zN2}U$UY)=rIyUnWo=yQ@55#sCZeAC}cQA(tg5ZhqLtu*z>4}mbfoZ>JOj-|a2fR$L zQ(7N$spJL_BHb6Bf%ieO10~pQX%@^WKmQOQNOUe4h|M}XOTRL`^QVpN$MjJ7t+UdP zDdzcK3e7_fdv)PPR>O|-`kVC1_O08_WGcQXj*W5d?}3yE?-fZ_@mE-zcq6^Mn49!; zDDcus*@4dFIyZ%_d3*MO=kk3$MQ^?zaDR1-o<<7T=;`8 zz2(w>U9IQ+pZ<*B;4dE@LnlF7YwNG>la#rQ@mC4u@@0_pf40+<&t)+9(YOgCP9(aJ z5v7SRi(y4;fWR)oHRxf2|Va=?P zXq&7GtTYd+3U{Wm5?#e7gDwz#OFbvHL4Jq{BGhNYzh|U!1$_WEJef&NKDD9)*$d+e ztXF1-rvO5OBm{g9Mo8x?^YB;J|G*~3m@2y%Fyx6eb*O^lW- z`JUL?!exvd&SL_w89KoQxw5ZZ}7$FD4s>z`!3R}6vcFf0lWNYjH$#P z<)0DiPN%ASTkjWqlBB;8?RX+X+y>z*$H@l%_-0-}UJ>9l$`=+*lIln9lMi%Q7CK-3 z;bsfk5N?k~;PrMo)_!+-PO&)y-pbaIjn;oSYMM2dWJMX6tsA5>3QNGQII^3->manx z(J+2-G~b34{1^sgxplkf>?@Me476Wwog~$mri{^`b3K0p+sxG4oKSwG zbl!m9DE87k>gd9WK#bURBx%`(=$J!4d*;!0&q;LW82;wX{}KbPAZtt86v(tum_1hN z0{g%T0|c(PaSb+NAF^JX;-?=e$Lm4PAi|v%(9uXMU>IbAlv*f{Ye3USUIkK`^A=Vn zd))fSFUex3D@nsdx6-@cfO1%yfr4+0B!uZ)cHCJdZNcsl%q9;#%k@1jh9TGHRnH2(ef0~sB(`82IC_71#zbg=NL$r=_9UD-~ z8c54_zA@jEhkJpL?U`$p&|XF}OpRvr`~}+^BYBtiFB1!;FX;a3=7jkFSET)41C@V` zxhfS)O-$jRJ|R}CL{=N{{^0~c8WuLOC?`>JKmFGi?dlfss4Y^AAtV#FoLvWoHsEeg zAAOc+PXl@WoSOOu_6Tz~K=>OK@KL#^re(1oPrhcen@+#ouGG|g(;A5(SVuE~rp$?# zR$o(46m}O~QtU{!N-s}RfYh+?*m9v#w@;=DEXI;!CEf0bHEgI<~T7&VnIvtG%o=s@3c zG1AT(J>!bph%Z1^xT_aO>@%jWnTW=8Z^2k0?aJ(8R5VA}H+mDh>$b9ua{)I5X9$%b z&O%F;3AIW&9j3=Q1#8uL%4_2mc3xX2AdzYJi%#Q#PEY3lk<#u=Pc?EJ7qt4WZX)bH481F8hwMr^9C^N8KUiWIgcVa=V` z4_7By=0Fkq>M6N?Bis+nc$YOqN4Qs@KDdQCy0TTi;SQ7^#<wi9E4T)##ZVvS(SK4#6j^QjHIUh<0_ZD2Yl+t?Z2;4zA zvI<(>jLvJae#sIA`qHl0lnkcU$>Rrkcnp{E;VZwW`cucIIWi{hftjEx-7>xXWRsa4VH(CCyuleyG8a+wOY8l*y>n@ zxZb}o=p9lR)9N^FKfkvPH-t2{qDE=hG8Z!`JO>6aJ^hKJVyIV&qGo*YSpoU(d)&OE ziv2#o`&W>(IK~sH{_5aPL;qcn{2%Gae+r5G4yMl5U)EB>ZidEo|F@f)70WN%Pxo`= zQ+U-W9}iLlF=`VeGD0*EpI!(lVJHy(%9yFZkS_GMSF?J*$bq+2vW37rwn;9?9%g(Jhwc<`lHvf6@SfnQaA&aF=los z0>hw9*P}3mWaZ|N5+NXIqz#8EtCtYf-szHPI`%!HhjmeCnZCim3$IX?5Il%muqrPr zyUS#WRB(?RNxImUZHdS&sF8%5wkd0RIb*O#0HH zeH~m^Rxe1;4d(~&pWGyPBxAr}E(wVwlmCs*uyeB2mcsCT%kwX|8&Pygda=T}x{%^7 z)5lE5jl0|DKd|4N*_!(ZLrDL5Lp&WjO7B($n9!_R3H(B$7*D zLV}bNCevduAk2pJfxjpEUCw;q$yK=X-gH^$2f}NQyl(9ymTq>xq!x0a7-EitRR3OY zOYS2Qh?{_J_zKEI!g0gz1B=_K4TABrliLu6nr-`w~g2#zb zh7qeBbkWznjeGKNgUS8^^w)uLv*jd8eH~cG-wMN+{*42Z{m(E{)>K7O{rLflN(vC~ zRcceKP!kd)80=8ttH@14>_q|L&x0K^N0Ty{9~+c>m0S<$R@e11>wu&=*Uc^^`dE9RnW+)N$re2(N@%&3A?!JdI?Vx;X=8&1+=;krE8o%t z32Gi2=|qi=F?kmSo19LqgEPC5kGeJ5+<3TpUXV3Yik_6(^;SJw=Cz`dq(LN)F9G<$ za-aTiEiE}H(a>WITnJ+qG$3eCqrKgXFRiIv=@1C4zGNV!+ z{{7_AulEPXdR+~$sJ+yHA73j_w^4>UHZFnK$xsp}YtpklHa57+9!NfhOuU7m4@WQp z5_qb`)p|6atW#^b;KIj?8mWxF(!eN<#8h=Ohzw&bagGAS4;O^;d-~#Ct0*gpp_4&( ztwlS2Jf#9i>=e5+X8QSy**-JE&6{$GlkjNzNJY;K5&h|iDT-6%4@g;*JK&oA8auCovoA0+S(t~|vpG$yI+;aKSa{{Y(Tnm{ zzWuo^wgB?@?S9oKub=|NZNEDc;5v@IL*DBqaMkgn@z+IeaE^&%fZ0ZGLFYEubRxP0WG`S| zRCRXWt+ArtBMCRqB725odpDu(qdG;jez|6*MZE_Ml<4ehK_$06#r3*=zC9q}YtZ*S zBEb2?=5|Tt;&QV^qXpaf?<;2>07JVaR^L9-|MG6y=U9k{8-^iS4-l_D(;~l=zLoq% zVw05cIVj1qTLpYcQH0wS1yQ47L4OoP;otb02V!HGZhPnzw`@TRACZZ_pfB#ez4wObPJYcc%W>L8Z*`$ZPypyFuHJRW>NAha3z?^PfHsbP*-XPPq|`h} zljm&0NB7EFFgWo%0qK`TAhp220MRLHof1zNXAP6At4n#(ts2F+B`SaIKOHzEBmCJ3 z$7Z&kYcKWH&T!=#s5C8C_UMQ4F^CFeacQ{e0bG?p5J~*mOvg>zy_C{A4sbf!JT+JK z>9kMi=5@{1To&ILA)1wwVpOJ&%@yfuRwC9cD2`0CmsURi5pr2nYb6oBY&EmL9Gd@i zj{F}h!T*#a<@6mKzogszCSUCq5pxGeCq-w2|M>ZzLft79&A-&!AH~#ER1?Z=ZavC0 z)V05~!^Nl{E5wrkBLnrxLoO|AG&hoOa6AV2{KWL#X*UItj_W`}DEbIUxa;huN0S#` zUtXHi+cPyg-=Gad`2Aw-HWO*;`_&j9B3GHLy(f^@Do@Wu*5{FANC+>M*e6(YAz4k^ zcb_n4oJgrykBM1T!VN(2`&(rNBh+UcE}oL@A~Fj}xf0|qtJK?WzUk{t=M15p!)i7k zM!`qg^o;xR*VM49 zcY_1Yv0?~;V7`h7c&Rj;yapzw2+H%~-AhagWAfI0U`2d7$SXt=@8SEV_hpyni~8B| zmy7w?04R$7leh>WYSu8)oxD`88>7l=AWWJmm9iWfRO z!Aa*kd7^Z-3sEIny|bs9?8<1f)B$Xboi69*|j5E?lMH6PhhFTepWbjvh*7 zJEKyr89j`X>+v6k1O$NS-`gI;mQ(}DQdT*FCIIppRtRJd2|J?qHPGQut66-~F>RWs=TMIYl6K=k7`n1c%*gtLMgJM2|D;Hc|HNidlC>-nKm5q2 zBXyM)6euzXE&_r%C06K*fES5`6h-_u>4PZs^`^{bxR?=s!7Ld0`}aJ?Z6)7x1^ zt3Yi`DVtZ*({C;&E-sJ1W@dK29of-B1lIm)MV4F?HkZ_3t|LrpIuG~IZdWO@(2S6& zB2jA7qiiGi%HO2fU5|yY#aC<57DNc7T%q9L>B_Qh@v#)x(?}*zr1f4C4p8>~v2JFR z8=g|BIpG$W)QEc#GV1A}_(>v&=KTqZbfm)rqdM>}3n%;mv2z*|8%@%u)nQWi>X=%m?>Thn;V**6wQEj#$rU&_?y|xoCLe4=2`e&7P16L7LluN^#&f1#Gsf<{` z>33Bc8LbllJfhhAR?d7*ej*Rty)DHwVG)3$&{XFKdG?O-C=-L9DG$*)_*hQicm`!o zib(R-F%e@mD*&V`$#MCK=$95r$}E<4%o6EHLxM0&K$=;Z#6Ag0Tcl9i+g`$Pcz&tP zgds)TewipwlXh0T)!e~d+ES8zuwFIChK+c4;{!RC4P(|E4$^#0V*HhXG80C;ZD-no z!u+uQ;GCpm^iAW&odDVeo+LJU6qc$4+CJ6b6T&Y^K3(O_bN{@A{&*c6>f6y@EJ+34 zscmnr_m{V`e8HdZ>xs*=g6DK)q2H5Xew?8h;k{)KBl;fO@c_1uRV>l#Xr+^vzgsub zMUo8k!cQ>m1BnO>TQ<)|oBHVATk|}^c&`sg>V5)u-}xK*TOg%E__w<*=|;?? z!WptKGk*fFIEE-G&d8-jh%~oau#B1T9hDK;1a*op&z+MxJbO!Bz8~+V&p-f8KYw!B zIC4g_&BzWI98tBn?!7pt4|{3tm@l+K-O>Jq08C6x(uA)nuJ22n`meK;#J`UK0b>(e z2jhQ{rY;qcOyNJR9qioLiRT51gfXchi2#J*wD3g+AeK>lm_<>4jHCC>*)lfiQzGtl zPjhB%U5c@-(o}k!hiTtqIJQXHiBc8W8yVkYFSuV_I(oJ|U2@*IxKB1*8gJCSs|PS+EIlo~NEbD+RJ^T1 z@{_k(?!kjYU~8W&!;k1=Q+R-PDVW#EYa(xBJ2s8GKOk#QR92^EQ_p-?j2lBlArQgT z0RzL+zbx-Y>6^EYF-3F8`Z*qwIi_-B5ntw#~M}Q)kE% z@aDhS7%)rc#~=3b3TW~c_O8u!RnVEE10YdEBa!5@&)?!J0B{!Sg}Qh$2`7bZR_atZ zV0Nl8TBf4BfJ*2p_Xw+h;rK@{unC5$0%X}1U?=9!fc2j_qu13bL+5_?jg+f$u%)ZbkVg2a`{ZwQCdJhq%STYsK*R*aQKU z=lOv?*JBD5wQvdQIObh!v>HG3T&>vIWiT?@cp$SwbDoV(?STo3x^DR4Yq=9@L5NnN z_C?fdf!HDWyv(?Uw={r`jtv_67bQ5WLFEsf@p!P3pKvnKh_D}X@WTX^xml)D^Sj8Er?RRo2GLWxu`-Bsc ztZ*OU?k$jdB|C6uJtJ#yFm{8!oAQj<0X}2I(9uuw#fiv5bdF$ZBOl@h<#V401H;_` zu5-9V`$k1Mk44+9|F}wIIjra8>7jLUQF|q zIi8JCWez)_hj3aHBMn6(scZd9q#I<3MZzv}Yjc^t_gtGunP?|mAs+s!nGtNlDQ?ZO zgtG2b3s#J8Wh#0z1E|n_(y*F5-s7_LM0Rj3atDhs4HqmZc|?8LDFFu}YWZ}^8D`Yi z`AgJWbQ)dK(Qn?%Z=YDi#f%pLZu_kRnLrC2Qu|V>iD=z=8Y%}YY=g8bb~&dj;h7(T zPhji+7=m2hP~Xw`%Ma7o#?jo#+{IY&YkSeg^os)9>3?ZB z|Bt1-;uj0%|M_9k;#6c+)a)0oA}8+=h^#A_o=QR@jX^|y`YIR9V8ppGX>)FS%X>eB zD&v$!{eebt&-}u8z2t`KZLno>+UPceqXzuZe2u zHYz7U9}_Sw2da@ugQjBJCp(MNp~mVSk>b9nN*8UE`)88xXr88KXWmTa;FKKrd{Zy> zqL}@fo*7-ImF(Ad!5W7Z#;QLsABck0s8aWQohc@PmX3TK#f$`734%ifVd{M!J1;%A z)qjpf=kxPgv5NpUuUyc=C%MzLufCgTEFXQawxJo)rv4xG&{TKfV;V#ggkxefi`{sS zX+NQ8yc>qcdU zUuLM~0x32S& z|NdQ-wE6O{{U-(dCn@}Ty2i=)pJeb-?bP+BGRkLHp&;`Vup!}`pJdth`04rFPy;$a zkU=wWy;P$BMzf+0DM(IbYh`Dk*60l?3LAU;z3I^tHbXtB5H$Op=VEPL8!mydG>$T@S9;?^}mmDK)+x*TCN_Z`%SG{Hv0;P*>(P@^xe2%mUldaqF9$ zG+Oq<5)pQ+V4%%R>bK|~veGY4T&ALmnT@W*I)aT~2(zk>&L9PVG9&;LdC%xAUA`gC4KOGLHiqxbxMTA^!+T*7G;rF z;7ZNc3t&xd!^{e|E(7-FHu@!VrWQ8CB=pP;#jG#yi6(!BfCV(rrY~7D)0vCp_Ra@9 zSuu)to5ArdCAYX}MU&4u6}*{oe=Ipe09Z7|z41Y&lh`olz{lmO>wZpnwx+x4!~7@37|N~@wr=Tqf*+}4H{7GE*BvptMyhTAwu?VYEaj~BiJm7 zQw98FiwJTx0`qY8Y+268mkV#!grHt3S_69w?1TRi-P^2iNv=ajmQIkoX7OkY=Cpvk zs;-Gv?R(YEAb(%@0tNz)_r8bwE zPh75RwYWr?wPZ0rkG<5WwX|fjqCBP4^etDs4{ZF9+|c#@Y60nB)I_U5Z$FYe=SLXI zn}7T@%LLA>*fWf9X?vSD3tpXSEk%H{*`ZmRik>=se}`HWHKL|HHiXovNzTS~-4e?1 zgVLCWv@)(($B*C3rGn`N#nzUyVrSw>OiD;4`i15QHhdicm}A(CP)UO>PO(3!(=v-x zrsKIUCbJMb>=IB}20b{69IdU(vQ%Ti0Zm?VLQoL++HK(G%^P{wuH;|@Cn7Ncybw%D zDhWh??1)6j5j7RbEy-{rVefvMhV|Su8n9`m>4LU^TanMzUIy>S&UbSKJW56C(K5NX z*Ypzh@KaMD=ank_G}Di5SaDTz3@Ze;5$pkK$7Pz?SBj&njRD4so5e0Msp_p}|D8aq zDvU@2s@T_?)?f5XEWS3j_%6%AK-4aXU5!Xzk{fL%mI~AYWP?q}8X}}ZV3ZzKLFvmm zOHWR3OY0l)pZ#y@qGPkjS~mGj&J8uJnU<~+n?qrBTsf>8jN~i17c~Ry=4wM6YrgqZ@h`8`?iL&$8#fYrt7MinX)gEl7Sh_TS zOW{AyVh%SzW|QYBJo8iEVrA!yL(Lm&j6GB0|c?~N{~?Qyj^qjbs>E~lpWo!q!lNwfr(DPZVe zaazh2J{{o=*AQ|Wxz*!pBwYx_9+G$12{5G3V!0F=yB=tPa zEgh47ryFGZc;E%A{m4lJoik6@^k%E0{99pIL1gE;NqT!1dl5UV>RkEWtP)3f_5hG6 zs%M}qX?DNaI+4HN*-wn`HOjlEz0}K{o0fG~_%%c8sDq)6Z2)6msormgjhmtdzv;Hy{BwHXKp&3Bf9paw+J4r-E zBoWmEr6%r3t?F`38eCyr+)`In1&qS9`gcQ|rHBP`LlCl=_x?ck0lISju@hW*d~EQ) zU2sgl#~^(ye%SeZR%gZ=&?1ZxeU1v@44;`}yi^j0*Efg1lIFcC*xEj}Y~k|(I&}7z zXXi2xe>mc_cC`K=v8&-5p%=m=z47Z6HQUzNi5=oCeJ$-Bo#B0=i}CemYbux7I~B*e z3hSneMn$KHNXf4;wr5fkuA+)IzWs8gJ%$o0Q^vfnXQLnABJW;NRN(83Dcbu9dLnvo z6mweq2@yPK%0|R9vT)B$&|S!QO6f(~J^Z+b`G(j1;HKOq_fG$-36zvBI$`hvA94i( zGPGVo&Y%nRsodWyzn0bD0VZlG?=0M23Mc2V1_7>R^3`|z_5B;}JnIp0FI}9XNKJ^o z7xYKOFdYxX?UW~4PC!hVz86aP+dsOkBA(sz3J+6$KL`SU4tRwWnnCQN z&+C92x#?WNBaxf?Q^Q}@QD5rC=@aj8SIg;(QG06k^C5bZFwmiAyFl|qPX^@e2*J%m z1Fu_Jk5oZEB&%YN54Y8;?#l#GYHr->Q>-?72QSIc+Gx^C%;!$ezH>t<=o$&#w*Y_Y7=|PH*+o57yb>b&zpTUQv)0raRzrkL=hA-Z(10vNYDiT487% zzp2zr4ujA#rQ;Hxh7moX(VldzylrhKvPnl9Fb?LCt#|==!=?2aiZ`$Wx*^Lv@5r_ySpQ_vQ{h2_>I`Wd|GjXY?!>=X8v}wmTc+Nqi-?ln zQa28}pDfvjpheaM2>AYDC2x`+&QYH(jGqHDYLi}w55O5^e9s=Ui^hQ~xG*&TU8I}Y zeH~7!$!=a+1_RZe{6G$BICI6R2PKE{gYW8_ss!VY*4uXw8`?o>p=fC>n&DGzxJ$&w zoIxdMA4I503p(>m9*FnFeEJQ5Nd^WK*>I_79(IA)e#hr2qZ8Y!RMcbS}R z(2;{C#FXUv_o-0C=w18S!7fh!MXAN-iF!Oq4^n#Q{ktGsqj0nd~}H&v#Brb}6cd=q75>E;O8p?6a;CR4FiN zxyB?rmw)!Kxrh&7DbPei$lj)r+fDY&=qH+ zKX`VtQ=2fc?BwarW+heGX&C!Qk;F;mEuPC*8 z0Tv0h2v&J#wCU_0q-Wq9SHLOvx@F!QQQN+qN^-r-OgGRYhpu%J-L~SiU7o@0&q6t( zxtimUlrTO)Zk6SnXsm8l$`GW-ZHKNo1a}<%U4Ng z(k8=jTPjoZZ%$(tdr@17t|MV8uhdF4s|HbPO)SF`++T%r=cNRx&$BkW7|$)u%Anm; zGOv)GmwW*J5DzeI8Vk_HZ4v?Mmz$vpL#M%+vyeiW;BK6w|_S0 z{pqGZxI%-~r~b@=F#^|^+pwQE*qc8+b7!b}A$8OjqA%6=i?yI;3BcDP1xU_UVYa?^ z3o-aYI`X%p!w>>cRe_3rtp}@f1d&AQZ_2eeB;1_+9(`jpC22z+w%(kh6G3}Rz&~U_ z5_LxI)7~`nP=ZdVO&`rUP8`b-t^Vqi;Yt~Ckxauk>cj@W0v=E}$00?Jq(sxBcQHKc z(W}uAA*+e%Q)ybLANOe7gb4w^eX#gI%i56{GJz6NVMA{tQ! z3-}Mdjxfy6C#;%_-{5h|d0xP0YQ!qQ^uV*Y&_F9pP!A;qx#0w*)&xPF0?%{;8t+uWA#vrZ|CBD0wz@?M=ge(^#$y< zIEBv1wmL`NKAe&)7@UC9H^t0E0$}Odd>u4cQGdKdlfCn0`goK~uQ0xrP*{VJ*TjR; za16!CM>-msM@KcxU|HsEGgn{v>uy1R?slG}XL5)*rLTNHdYowI*;qe~TZH z|1Ez0TXrc@khWdmgZJKV6+aJVlFsv5z~PhdC>=^tL5BC|3tyMuXSdsEC3L0qw60S>ecX zi&`-rZ=GqxfrH{+JvkuOY?{d?;HZmv z2@4+ep(g+yG6W%NrdJe2%miVnb8nX{yXK>?5DC#GA6IIXU-`!?8+xm(8r)Vi;=?g! zmOK)$jQv~nakv-|`0=Z`-Ir1%2q8~>T7-k=DyG^Rjk7|!y(QO&)cBEKdBrv~E$7_y z&?K!6DP;Qr_0fbbj86^W(4M{lqGx6Mb;`H;>IDqqGG@3I+oZg_)nb=k|ItMkuX2Y@ zYzDmMV~3{y43}y%IT+)nBCIzi^Cr1gEfyrjrQ7gXAmE$4Hj(&CuyWXjDrkV~uP>9T zCX5cXn!1oEjO!P#71iyGh#q+8qrD8)h#wE#x;bz+a^sQyAntO(UhxFVUqR^dux8 zOsN=Nzw5imC7U~@t^#gLo}j#vge3C6o(%0V5<0d~1qlxe4%yD~{EDGzZ40)ZIXytB zg3^NFa(98n#OwV!DJqgy;xitYp)Q(W$(J0<0Xr5DHFYO$zuUkC(4}Zv2uB`O@_TR7 zG3Ehp!K;YLl%2&*oz3`{p|hj`Bzd(@BMVVA2ruucGsD0mj`^a1Qw3WsT7_z)c_<&j zvy(u5yod#@5~XT5KRPqKKp*2Q`rN!6gd#Wdh9;806oaWGi6~pB78)SYEhIYZDo*^} z-93olUg^Vh29G^}wQ8p(BK0(<7R6(8><}Bia@h%62o%ONE`~PiaIdfy!HGUm0GZdJ z&^aK^@JP|8YL`L(zI6Y#c%Q{6*APf`DU#$22PjfSP@T4xKHW~A(vL$pvf+~p{QLdx^j4sUA;?IZ zVWID3OA_VkZ_3?~Yy1yn?4Ev^r}1~c!n9;Z7pRn*D$^J%4QyWNvPkKF5{{bMBefvT zFZu|hco!0Me-__dyLe6S!}>m?I-x%1{Zr3_Qi!(T@)hh%zBE1my2AWl^XY#v%TSX3 z;?rn8Chf+?>SQ|v8gl$*f5dpix{i;?651ezum2tQCU`9sKxuZG2A9o(M~}G`*q2m#iW# z?0fJS+j_XxOk1fb+Nx6$rZqhg!x}eO!3nMy6a@4doqY&?(c`8$^B?0InG4T&{mu*3 zpcYaf)z__Dgr%+6UFYYXSu(oRrPYGviL~FKc{0X%tnt+9slAC|W0F8l^(@8qDXks~ zOZgs?O-6e-12Q>w5d?|E$P&oyah^mqd(Cu#uNtjCpp&F}G&biuW49LGkFCDEYe0S* zo-W_}-yR$%Z^03i8{&R&oU1BbY9$ER3RR5LjocL5er=CclJwCH>M6ge$R*Wi zd3zUoE*~?a1owq&DiT2#_Q)~tr$;Q=BJrMHrG@j3^J=#U3 zmd)ubgUu(9g(qmjx~7+!$9^%~fpi9$*n=+HfX&<>a}qkD;Ky@piqolGdF>VEX?(!DuO z{=7v}0Y|$@o3c`s^K3&3uMD0T1NMMrgwn$+g{=Tr&IHH@S`Aj4zn z{Mpln$!B->uUYTFe+75e!ee*euX`W%xA&g!-%s-YJ-sJP*(~t=44RSN6K5u7}a9;40`KN#fg#N>-s?YE6*qS9zkP2*=!a%O&aJ4>)JR>{O6n)(@ z$2mBny!kLLgnPgrX&!fTVnSXLEY}ZR{fLL4Jw;uI;)DhJJ<;%5&X%lg5)mYwwyHK=W zS`3yPe&Ncy_OA!;HvQV1TI3}7jib>EhqT!PZIoDg_Wm4OraFX|nGmCsXj|{&g!(_; z;(_uG68gxxy{T#wPPuETHggw6G8nCyc`=x89;arkuB%&7rbL&VzCm|jQFg8me78tu z2l-K|IsFgX@am)(c=1IWYX5fhCjIZ&9MBs9(Qg*`U5T`@H2xqzQxj`1bK#2gmDn2=yI!n0*6A2{JuA3~uX7 zsXocdxHHMV^?dsW+s}S8j8Mq!pjB8=NytY%-MEgx+HnavDcotwYmA{J%RzlLhZ{?t-W6 zr-JA(qw%OVMtv?N?75aid-cY`ZJLFT`fh-fZ0()^P(3wyQ`wDHG$9cUmEr^~!;iGV z#ukG&nXeLHarXD$=({)#Es!?%=2*`or!FE4N6XWEo>>`}ocE?kmQb+2JP;-))sn0V zoC6&be>gf!XD#yJO`FCF(Ts|~ zUbO#y44!V-U|&SEr1#r^_fJ1Ql3isjfCVAfvNga7OBJG^YAP`r8d{))?5D{xm+FB~ z*>D&s+(Z(o*)gx|EpJAYlnk@A&=zpkYvak{W~Y}~8M_p7Uu1bY#7m{Mq-#4-xw3lH z{(8=+O+WrU)^C(;qRm%NiKnO+<0W6EF|>n#fw%OKxr!@d%dWHOmv~#M2{eIlxaRW% z;k6v=< zZ{5W}@ik?!__~T?0QX0xX^^}Isw8Ey-yXCwQkS!)xT-ZdV6A`#HdMECf78X){%6)7 znLSKwqK}!hdkVk2QjAZ?j%&Id%WY~^<$ntL2p8J;eq$VCp%Cg{)oW&%Z3vp6ihm9D zIlPC#zVE^>62fNwZqsk)mt+E#rrU@%4vWtkYK)Qv$a*}$T2ZJCtTFI`tuLb*7j`!^eR`?d9h2TjF-h2Yr+ z){T|kWBNyrA5vpZE{Ez_)pG7Zf%QXqW)R@(<_0oOP?cwg&gib`IjKTzN_R*5A)G>_ z1r#qXr5i)U$$wv(kXfodOg=h$UZk78c@50K^wOMcKCx26s{q}vdOioj1n!&if0FRY zSi@$}gn4KW;2<;+lY?&>M6GNrRtfUTEIzqih@yLMQA2(17m3)hLTa@zlj=oHqaCG5 zYg71D3e}v36DjH++<*=MXgd2q&dP^6f&^KctfDe(SQrvy5JXC@BG#|N_^XbfxhcV) z>KV$aMxcL*ISc0|0;+<2ix7U7xq8m48=~j!a`g?SzE5}(Y;hxqEHJg_+qB99$}py7 z*ZPXL?FKLA>0uVicvq3okpoLZE#OG@fv^+k0{35pf`XdVT)1< z#mV4mcikkivZcE(=0rgfv&#+yZJrAOX&VDL(}Zx8@&$yi4Y1kmEK&uL<}ZqWr05mr zcSwaqH=squnLs+UCn@yp#WNQuIv$~B*sN_NAACD>N3k_$E(j~}Uvqda!_ zZcu7UrsR_q-P2YTrg|lijt8kyqL>T@ab#-a7i>%#*eoxFfgx(FoPa(y1nDI{z#Pz^ zfF~)6RBc?#ivEF<@XVD*#9r^r-;*<^(tE%UtWw^oom83;$5d{UoUbmAP(3Z)14YTK zMXQ#mz9yw>*8D^82vL^|%lyo|ZiQPd&{<*wCZI%up=wadl~C~cRJ!=Hjc&F)FNlnd zgNI|iSIMyqh=qV(z+HbldU4}!sqMs1R?t*RV!S*WW>qW_GF4NJ&vb-{2sJjiTIpL; z{bC@V&EhO|>GuDv7`%$kO<-P@^VI+y zl0tXGm|eISy)fiY3m8_Yaz>`Q=B(Yi8EH71{wfM*8ziS3BIju?26ujw==Xh4x5rH71h?Z859IWq(i#9 zLt0wt?(QBsL(q4yCv&g4t0jJvu^@FtJJk`8YXb{{(OdTS%rGxnPR)xY#6=?AWjD5M2n z5GZ@@ulO|JN34J-2y*-Nh@6|?RkFHwSj$e}p}mbc3Y}*el{O31RU0Z_E48@5O~5n;kDJy}a$x&Lc;27DTvAd@s^9>IA@$q{m6K?eZqOJGKpgCT!Zhld>#d^DAK+MDP}|3h zZ{i!ENw;mW62Pq^|FY#w?@8U6Nvjgi(sKW}&uvgjz0YIS>%Sxk1`5 z`qk`C2*bWd|0I4L=_~s(^2F$Bv7OTjo*G+gBD=Rq-~$7t{Bo|mmck(d6ywQ*UbIjkS>qtkH~Zs(sq zEYNB4xxdYmy+G=${gOjGGfSQQLi1D*{&en*3{wyd7U3M)y^FX(+d)eFi?9oMy@64c zwL?!q#*eJ$eayb4lc!B$W%M4B$4dH>9eFXwjfk5U@}6vXOWDiiLMYP3^VYlG$yDjaC({9tyL4NxPb{x=ADdJ7Bl5EHzU6h-Cbke zwi+34LGVF=G%>d5Q7C>n!)%!LT`UZ0v^YN1WrcjC(pS!&vek-SK#kj^EL9!l?TvY% zOkz%!#5Cf^2JFrvNeU5ZL1_aI(M~e4?~kId$T!A@Z$?f40q#~5HuElkRMQV+6r0>J zK9y=%I^m-_xwRNyO<2Zq-0W6!frE$jT$C3Qi3d>0911QPc`Ky6`~Y<)?mMy*u`nz8 z={b()Z;8DqbWJ?MdOsaF6Zn)$d>DQpRHM~bD3cq=Rw_fzWpiwtJFY`BF}hTFCeh+C zs-4A}MCP}`EInNzh3hRoZ6L1a`J7}T&wh9#HItmHBCRwefpQ97*u{--QH=5>MSZud zv_%DacJS+lsxlJ0q=40vs-8P$Q$_Pt)JM=)|1dcFO&JWY8KwhiP$a&Ua*Z z$BTW#lu4QZna#vZECq#Q?Up_(@`0#(@~0?mG{qA#^rZDq^&6T=pbGL8nU?BY-TwKE zPmMqhP_w?q1B~|43T5=Hl(Bi-+{yY;Acv4i9u}oWC+@^i*}l}=dg`Y~E%dTn;rqj5 z&3pLFHjC62jcxW_a@Jj2Ce%eToCB!6OV*6I0!XF9Hq7orpm-RpizSSHx890&_kCQ% z$cKVw-`WnDvv5Lq?L!qGDcUPtgmotX=C`~Smjg&oM5V?}gAzL%WkRwLmNZyrCbKwC zcsUD3O0ruLr%s`B5W)IYjzLTXcAqinas75T_j&1_m!m!^ORvk6_bYvK||DIVE@IUjWQ z0dQ(H9=a-c`@{Q=uj?JC8g`r$a>)gR#=2%vuea5B_BAp;*QX&I;N?>jHYFR=q?8sq zatBJBYX`tr1BQxIgACJ==*ivk$UjW^Maod6-=SzI3MMUbCqu!3wVHt!Be?M@)2aK+$Rv(?iH18-}e+rDznPRv< zi!{-5NNHE)eqVEeYl>F5S{6w^8L$0p7l|M;(^c+Ei|{V7!!8;xiDx@QK4Pl8Iel7N z*9%$ISyQPK_+5tc2c9jhX%sfIOCZf-E%K9X7Z6N0Nvp!~v(KAZvWnaHK^SQSragIF zVIC_7tGTXeU(TRqj?owTmj{SXNtf7;9evoBURMB5R`8R1$@$}FCS%ugA{4igxOhRi z*q_y$&&!mHF1$S}2279&m0^nFxDV#WvV&?Pphq(craPjcBtveg0Nqdm9tXL4lN{t= z?BLepVnp$U5KskjvVX-GjEf=M3mOTZb|Z$Hp*yytey0C^{cH*v>gqF&-j?gcEj4)l)cdGBmB(^HrSe_)qzf z+TZ^Yo4|GWz=Oi3m`r(hV`iZHb_mu63g(JXPMW4p9JhL_(tg+XQnmR0&52UUA|nZI zvjwOx(fNtZ`8!#|4$7GoJPQ`;T?hKOi`^`kFOyX;C4KfC(U-(CX?Qh2!RTe!4raMP zjLaC7qL_tJ?^0!T9ibZe!m-x!u7o%2dHK{uYZ~#+vERAv-G-MQeYQ*~DILuFpu02u z(Qc)=bHqb4{fs+hdKa5etlX z3EW#vlbEZmWT>X{3WbgW)8~u=8IGuRc<=?KoDXg5V`jf%i^Ai`Cd9=&FH6d|N9uJl z>QhxtW_{}H10BF}GQNitk~V=GnB%NI1Xv-6-OeaI&Amg0s{4i4;HhP$6oc(L-}yHt zej63({`5VLSoIef7D3Z9BA5x<9$^x?PhV=6A@Nu=QiJo@*o?M@*6-UA@EdV@bQCR< z9>{N%eK;Y#U-@XDBBCT^j=?<|y|lsAWrXsf`t%4VT{)63oxQe^u_5NuOq{rsrRd}Z zOx&OldRtR4leEX#r$9`gPJtbHccH!JgZK&3x`tJ<_{kv)E?$LhZ?brv`Cc}X%cWC7<@6yqM2O&m(rB`1v-TiqcQmA5n$rbGJ4zs({=R-I%6}*^UQ)wi9WuzW%Ri%&5 zTdd%>+GvADk+4q#3s5qne99`MC)X_#=p1!d?(mcKDW=Efc31Jso)9M49O0OMeP&7~ zIm!vorpxBSbvSiczr^?WP&e&-!3GLxCIaR5?PGeLgwYT;lYu9UE8SwmXR(D?A^s`7 z^F4di(+oHh%$DZjj7F3_-Y9}k^uCKeSC?Jd7h>RZIDZ{wcbh|9w4)p$dmv7|gX1n& zkrYjSso~;~qMMzZUQ5AC+GUvuj@y{4E&&v(+OE-rS^J7iE~Yz1 zCQ9hAI&0X2_H8CKZMqo00MsxtwjvM{`AdSaZ8#Y?5zPI;a+0`JF52!uVwr@5Ufctm zm;5G%gI&utfGa~fv6!jHh9d1r3TYD zEOlrbyFnDl5J%sEO>HErK~WWE6I$_eXp!dbphDf zc;~oWDQylVa=y?q;c>SKzvZ~R(ZE2csFwf@10@zaZxFAYWaV9TFMh(QuqxNhPUav~ zzCkoe8-lM{?vh}kdM6EMCH(eLK3Rt{HsEJ+4fve=xAVq(cUc9fO9g1%zI+QfFOb@0 zePFU(&?Np9w3&xs)ZwPnQniC0%xs8(Hyx{7*Ot51*`9&2^h7@!nmzuF`3pl8ep#Ls z<)nk7ts}`9tGgaVJWC-3w;B~$juY6m+7XgfzjR4I=oV}E9LRGf4@cI>d3z%CYyURI z7lRn11g!D34zI6|26>?CELeIh?cEv_GCCMd5&g<=9-)pe8iXINQ}4IljYsQyfRz|( z<%w=HN4ZOQKJ9e7DOUhjA7A%-xcR%2`@1?U&u}rvqNc_8l9dUT_S`4TKJ;yezIdp} z?qDAfx6IHQ7YlO;EAP%d4U2O7jU`Uh(um!J`hJ_3&mmQez8AqWLQEftYJuMdCj27t zoV#b!c0d8al0j1yveY6)U#kPCh%OfL>P=%WE^LQew^k-QqZ{rjX6PqOd2K7>1^VUB z`&H@+vW=wH0UY>88nXCH@RKCY&?bR%8-53b{;@>|;uzDd5f`Z% zaSC<8OLh|b@ZnBET?My38fV9~ku2cPfcWZl7nW|pkQKfFlp@xRt+K0Tj@gdvVAQXP z?i45RNE4W#Kf0%Pp2=?hESkG}EK557cwn0r1{uWeG53_tb!9bg&R8R_d4s5N0poc- zr>1g0W~1oha&#@_irbqnL)jJ@Z=y7J3fCQ@qlr{6(%rSs2rpkS1QIU^tieJ-xq%nd ze-C=#{@E+Kzb&SJ2KM~9q^4Yk^jyXa#{;P)y`YsFvfzX?%V~r6GciP4eX~$vk{-C? zeipAYsMSp`Z~&-Jc*dt}m-A_w&cnb#~sIdbU{uCayd>nWKDxQ9!%R zTrgS~+>TqXgrN~e2&eeWdPhuHP2*#K1=f^B@UGZBjFq- z;mtKYyul9ZNuq89XEoeSg7^qld5^R}FHpbyRyk1pRPMDO$_Kqi*sp1hk&UpUKc!V! zJZpCQc!)@X+%qOQMP)CU@Qe|=IG@|DZ~o#j>TBFQxH>8rJ#0y`XO9ukvc)kJ6LY3$ zY}{(tri#32!LjVY^exC3Ky)i$NY6v^*>X5y8F65pYYjt^T^X<=zm=)Cr=>dcId>?I zR^0I?)=)|}ak7wG)&Ar#A&60BRp}&NWFPy7zt)yl3aObS?sB8fxfU9ayR{$#%S<#3 zrsbmi#bDSP)@w%iYS%&wyyIB??LJ0Q%aD^!XXYk3)tQt~x_YU?y4KVKl{MJ)KSz&f zV;tJ1smY(dLM6zZXVAWND3L|(W=q~HjA6OkjQ+kx-EuqtaaQQPaa=2_wwuW@G*1>e z_TqB;+1@yuHg}YYpEJL&Sw~jD3Xeb(Wo(-nz6`#gbP7?agYT>j_R%+^h{1>7W&cP{s8epLY9Ky6mU*u*!QBn zI7T~WL-_qj+~Hdpr}qtfjZmD;eI%H0SP~~ifqoD59-q)R9_Z zKr6OeoZT!Za#k5yo&CCmzLbGP*6ggJ@2QPhIY^aMXjVjQ@D+-E#qmAjuL{o@NCUDF zFy)B~$j`rK7Iz$L>_Jl~O?IJu2P3 zlHQ@${Jgcvp`PKu7p;6Fr=4y1?8nJ;=~jls^gx4&_O4+)C-OGc5)L0+R!&uI&qQID zhV&ZQ@+2={Z|2F%WoOu9Ljt}|0r;!e zCBx(uAViqOffibUBOVEH_IlV=57ZQSQ~Te5(wmsO+o_CCNAgCJzZ3ly84J34_Zf#SwQ9q8i41 zE>u$JuO$kQq*W6MDo$Eu?3jJAFUt&>Qy#K{lT-Vx z6=kceU^v`;vBRoFxQED5TL+=>QJ!iaxV^Z2r#%CaaEWgbs1ysT$&~sem&74AEC!;< zcGDH;CENBJ&hfI!@G5ezCK!sXzdB@m#a(q8KeX;U=yl6AujNz z{}huJlo1yL$DlAsi{12aS?CJ*{xuIIV4wf-V6E?L4E!5BWMQ0Zh4uel*xZJ}QQuPE z-u#DdD6hH6`;nVJ>O}8iuWxH>Z2vc>a;iFbm)nrbj$ps$6aa4TjfVZVZr7dK+E_E# z+S`ErJDM9i{HX815lax33Wl(;H~m|sF28cs+hB$%2pjyXgubo5p_%ay3!*?212bxX z@1{$rzY6~DK*{`5@oRm0>(9INQX61!{Ip#NymIM*g~u=D)UFH!NcfQ(AsZXVOPv5) zX?=4bI9>9;>HvTACiBNDt)x;_}tsJousTuWrG- zDUSM9|4|IRSy@PhdB$sAk4b;vRr>Nt@t3OB<#_*dl_7P>FGcFF3-DA?KBW00A<;2=*&`^P8}cEZW!GSO9(+{;-V@ zd%%C8KEDYD$pC#x%zb4bfVJ|kgWcG0-UNZT9@2=R|Wz+H2iJ2A29LV z#Dye7Qn~^KUqOIS)8EGZC9w+k*Sq|}?ze$| zKpJrq7cvL=dV^7%ejE4Cn@aE>Q}b^ELnd#EUUf703IedX{*S;n6P|BELgooxW`$lE z2;lhae}w#VCPR>N+{A=T+qyn;-Jk!Dn2`C1H{l?&Wv&mW{)_(?+|T+JGMPf)s$;=d z5J27Mw}F4!tB`@`mkAnI1_G4%{WjW<(=~4PFy#B)>ubz@;O|2J^F9yq(EB<9e9})4 z{&vv)&j^s`f|tKquM7lG$@pD_AFY;q=hx31Z;lY;$;aa>NbnT| kh{^d0>dn0}#6IV5TMroUdkH8gdhnkj_&0LYo6ArC2O!h?t^fc4 literal 0 HcmV?d00001 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..b573bb50d --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /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. diff --git a/README.md b/README.md new file mode 100644 index 000000000..c640d7481 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# spring-boot-feature + +This is a Graal feature than enables Spring applications to be compiled using the Graal +native-image command. Once compiled they will have instant startup! + +This feature supports: +- Graal 19.2.0 +- Spring Boot 2.2.0.M5 +- Spring Framework 5.2.0.RC1 + +To try it out, install Graal 19.2 from: https://github.com/oracle/graal/releases + +Then build the root feature project with: + +`mvn clean package` + +Now go into the samples subfolder. Each folder in there is using a piece of Spring +technology. Within each is a mini project and a `compile.sh` script - the script +will call the native-image command passing the feature on the classpath, the executable +produced in each case should start instantly. + diff --git a/clean.sh b/clean.sh new file mode 100755 index 000000000..dc7acfbd0 --- /dev/null +++ b/clean.sh @@ -0,0 +1,46 @@ +mvn clean + +cd samples/commandlinerunner +mvn clean +rm clr +rm -rf unpack + +cd ../commandlinerunner-maven +mvn clean + +cd ../vanilla-grpc +mvn clean +rm -rf unpack +rm grpc + +cd ../vanilla-jpa +mvn clean +rm -rf unpack +rm jpa + +cd ../vanilla-orm +mvn clean +rm -rf unpack +rm orm + +cd ../vanilla-rabbit +mvn clean +rm -rf unpack +rm rabbit + +cd ../vanilla-thymeleaf +mvn clean +rm -rf unpack +rm thymeleaf + +cd ../vanilla-tx +mvn clean +rm -rf unpack +rm tx + +cd ../webflux-netty +mvn clean +rm -rf unpack +rm webflux-netty + +cd ../.. diff --git a/mvnw b/mvnw new file mode 100755 index 000000000..5bf251c07 --- /dev/null +++ b/mvnw @@ -0,0 +1,225 @@ +#!/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 +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 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 Migwn, 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)`" + # TODO classpath? +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 + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +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 + +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 "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 000000000..019bd74d7 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,143 @@ +@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 Maven2 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 key stroke 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 enable echoing my 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 + +%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% diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..3665e8d5d --- /dev/null +++ b/pom.xml @@ -0,0 +1,190 @@ + + 4.0.0 + + org.springframework + spring-graal-feature + 0.5.0.BUILD-SNAPSHOT + jar + + spring-graal-feature + http://maven.apache.org + + + UTF-8 + 1.8 + 1.8 + + + + + + org.springframework.boot + spring-boot + 2.2.0.M5 + provided + true + + + org.springframework + spring-orm + 5.2.0.RC1 + provided + true + + + org.hibernate + hibernate-core + 5.4.2.Final + provided + true + + + jakarta.validation + jakarta.validation-api + 2.0.1 + provided + true + + + + com.oracle.substratevm + graal-hotspot-library + 19.2.0 + provided + true + + + org.ow2.asm + asm-tree + 7.1 + true + + + junit + junit + 4.12 + test + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-json-shade-source + generate-sources + + add-source + + + + ${basedir}/src/json-shade/java + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + package + + shade + + + + + org.objectweb.asm + sbg.asm + + + + + org.ow2.asm:asm-tree + org.ow2.asm:asm + + + + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + diff --git a/samples/commandlinerunner-maven/README.md b/samples/commandlinerunner-maven/README.md new file mode 100644 index 000000000..b5b8e6ca4 --- /dev/null +++ b/samples/commandlinerunner-maven/README.md @@ -0,0 +1,42 @@ +This is a variant of the commandlinerunner sample that uses maven to drive the native-image construction. + +Ensure you have: +- `mvn install`ed the spring-graal-feature project +- have the graal JDK installed and JAVA_HOME set appropriately (eg. `/Users/aclement/installs/graalvm-ce-19.2.0/Contents/Home`) + +Then you can: + +`mvn clean package` + +This will build the project normally and give you a boot executable jar (as normal) + + +`mvn -Pgraal clean package` + +This will compile the project then drive it through native-image, producing an executable called `clr` in the target folder. + +Notes: +- without the compile script we need to pass the options to the native-image command. This is done via the file (currently) in `src/main/resources/META-INF/native-image/native-image.properties`: +``` +ImageName=clr +Args= -H:+ReportExceptionStackTraces --no-fallback --allow-incomplete-classpath --report-unsupported-elements-at-runtime +``` +- the script also used to pass the initial class to the command. That is now done by setting `` in the properties section of the pom. + +``` +time ./target/clr + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: + +CLR running! + +real 0m0.050s +user 0m0.030s +sys 0m0.015s +``` diff --git a/samples/commandlinerunner-maven/pom.xml b/samples/commandlinerunner-maven/pom.xml new file mode 100644 index 000000000..99d823f93 --- /dev/null +++ b/samples/commandlinerunner-maven/pom.xml @@ -0,0 +1,143 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + com.example + commandlinerunner + 0.0.1-SNAPSHOT + commandlinerunner + Demo project for Spring Boot + + 1.8 + com.example.commandlinerunner.CommandlinerunnerApplication + + + + org.springframework.boot + spring-boot-starter + + + org.springframework + spring-graal-feature + 0.5.0.BUILD-SNAPSHOT + + + org.springframework + spring-context-indexer + + + + + jar + + true + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + graal + + + + com.oracle.substratevm + native-image-maven-plugin + 19.2.0 + + + + native-image + + package + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + diff --git a/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CLR.java b/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CLR.java new file mode 100644 index 000000000..b8c7dcb69 --- /dev/null +++ b/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CLR.java @@ -0,0 +1,15 @@ +package com.example.commandlinerunner; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +//(proxyBeanMethods=false) +public class CLR implements CommandLineRunner { + + @Override + public void run(String... args) throws Exception { + System.out.println("CLR running!"); + } + +} diff --git a/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java b/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java new file mode 100644 index 000000000..70f687edd --- /dev/null +++ b/samples/commandlinerunner-maven/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java @@ -0,0 +1,35 @@ +package com.example.commandlinerunner; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication(proxyBeanMethods=false) +public class CommandlinerunnerApplication { + +// private static final Logger LOGGER; +// static { +// try { +// LogManager.getLogManager().readConfiguration(CommandlinerunnerApplication.class.getResourceAsStream("logging.properties")); +// } catch (SecurityException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// LOGGER = Logger.getLogger(CommandlinerunnerApplication.class.getName()); +// } + + public static void main(String[] args) { +// LOGGER.fine("foo"); + SpringApplication.run(CommandlinerunnerApplication.class, args); + } + +// @Bean +// public CommandLineRunner clr1() { +// return new CLR(); +// } + +} diff --git a/samples/commandlinerunner-maven/src/main/resources/META-INF/native-image/native-image.properties b/samples/commandlinerunner-maven/src/main/resources/META-INF/native-image/native-image.properties new file mode 100644 index 000000000..a0d786603 --- /dev/null +++ b/samples/commandlinerunner-maven/src/main/resources/META-INF/native-image/native-image.properties @@ -0,0 +1,2 @@ +ImageName=clr +Args= -H:+ReportExceptionStackTraces --no-fallback --allow-incomplete-classpath --report-unsupported-elements-at-runtime diff --git a/samples/commandlinerunner-maven/src/main/resources/application.properties b/samples/commandlinerunner-maven/src/main/resources/application.properties new file mode 100644 index 000000000..f85e7ff2e --- /dev/null +++ b/samples/commandlinerunner-maven/src/main/resources/application.properties @@ -0,0 +1,3 @@ +spring.datasource.jmx-enabled=false +logging.level.root=INFO + diff --git a/samples/commandlinerunner-maven/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java b/samples/commandlinerunner-maven/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java new file mode 100644 index 000000000..1f31be55e --- /dev/null +++ b/samples/commandlinerunner-maven/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java @@ -0,0 +1,18 @@ +/* +package com.example.commandlinerunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class CommandlinerunnerApplicationTests { + + @Test + public void contextLoads() { + } + +} +*/ diff --git a/samples/commandlinerunner/README.md b/samples/commandlinerunner/README.md new file mode 100644 index 000000000..5489a9858 --- /dev/null +++ b/samples/commandlinerunner/README.md @@ -0,0 +1,23 @@ +Very basic spring boot project. Using a CommandLineRunner bean. + +Run the `./compile.sh` script to run the maven build and run native-image on it. + +Then you can launch the `./clr` executable: + +``` + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: + +Sep 05, 2019 8:53:20 AM org.springframework.boot.StartupInfoLogger logStarting +INFO: Starting CommandlinerunnerApplication on Andys-MacBook-Pro-2018.local with PID 18483 (/Users/aclement/gits3/spring-graal-feature/samples/commandlinerunner/clr started by aclement in /Users/aclement/gits3/spring-graal-feature/samples/commandlinerunner) +Sep 05, 2019 8:53:20 AM org.springframework.boot.SpringApplication logStartupProfileInfo +INFO: No active profile set, falling back to default profiles: default +Sep 05, 2019 8:53:20 AM org.springframework.boot.StartupInfoLogger logStarted +INFO: Started CommandlinerunnerApplication in 0.045 seconds (JVM running for 0.048) +CLR running! +``` diff --git a/samples/commandlinerunner/compile.sh b/samples/commandlinerunner/compile.sh new file mode 100755 index 000000000..210d1260d --- /dev/null +++ b/samples/commandlinerunner/compile.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +../../mvnw clean install + +export JAR="commandlinerunner-0.0.1-SNAPSHOT.jar" +rm clr +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=clr \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP com.example.commandlinerunner.CommandlinerunnerApplication + +mv clr ../../.. + +printf "\n\nJava exploded jar\n" +time java -classpath $CP com.example.commandlinerunner.CommandlinerunnerApplication + +printf "\n\nCompiled app (clr)\n" +cd ../../.. +time ./clr + diff --git a/samples/commandlinerunner/pom.xml b/samples/commandlinerunner/pom.xml new file mode 100644 index 000000000..f8b262024 --- /dev/null +++ b/samples/commandlinerunner/pom.xml @@ -0,0 +1,155 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + com.example + commandlinerunner + 0.0.1-SNAPSHOT + commandlinerunner + Demo project for Spring Boot + + 1.8 + + + + org.springframework.boot + spring-boot-starter + + + org.springframework + spring-context-indexer + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + maven-dependency-plugin + + + generate-sources + + build-classpath + + + maven.compile.classpath + : + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.apache.maven.plugins + + + maven-dependency-plugin + + + [3.1.1,) + + + build-classpath + + + + + + + + + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + diff --git a/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CLR.java b/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CLR.java new file mode 100644 index 000000000..b8c7dcb69 --- /dev/null +++ b/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CLR.java @@ -0,0 +1,15 @@ +package com.example.commandlinerunner; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +@Component +//(proxyBeanMethods=false) +public class CLR implements CommandLineRunner { + + @Override + public void run(String... args) throws Exception { + System.out.println("CLR running!"); + } + +} diff --git a/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java b/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java new file mode 100644 index 000000000..70f687edd --- /dev/null +++ b/samples/commandlinerunner/src/main/java/com/example/commandlinerunner/CommandlinerunnerApplication.java @@ -0,0 +1,35 @@ +package com.example.commandlinerunner; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication(proxyBeanMethods=false) +public class CommandlinerunnerApplication { + +// private static final Logger LOGGER; +// static { +// try { +// LogManager.getLogManager().readConfiguration(CommandlinerunnerApplication.class.getResourceAsStream("logging.properties")); +// } catch (SecurityException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// LOGGER = Logger.getLogger(CommandlinerunnerApplication.class.getName()); +// } + + public static void main(String[] args) { +// LOGGER.fine("foo"); + SpringApplication.run(CommandlinerunnerApplication.class, args); + } + +// @Bean +// public CommandLineRunner clr1() { +// return new CLR(); +// } + +} diff --git a/samples/commandlinerunner/src/main/resources/application.yml b/samples/commandlinerunner/src/main/resources/application.yml new file mode 100644 index 000000000..c45f53038 --- /dev/null +++ b/samples/commandlinerunner/src/main/resources/application.yml @@ -0,0 +1,4 @@ +logging: + level: + root: INFO + diff --git a/samples/commandlinerunner/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java b/samples/commandlinerunner/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java new file mode 100644 index 000000000..1f31be55e --- /dev/null +++ b/samples/commandlinerunner/src/test/java/com/example/commandlinerunner/CommandlinerunnerApplicationTests.java @@ -0,0 +1,18 @@ +/* +package com.example.commandlinerunner; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class CommandlinerunnerApplicationTests { + + @Test + public void contextLoads() { + } + +} +*/ diff --git a/samples/vanilla-grpc/README.adoc b/samples/vanilla-grpc/README.adoc new file mode 100644 index 000000000..5ede70099 --- /dev/null +++ b/samples/vanilla-grpc/README.adoc @@ -0,0 +1,32 @@ +A GRPC version of Josh's https://spring.io/blog/2015/03/22/using-google-protocol-buffers-with-spring-mvc-based-rest-services[2015 Blog] on Protobuf support in Spring. + +Service reflection: + +``` +$ grpcurl -plaintext localhost:50051 describe demo.Greeter +demo.Greeter is a service: +service Greeter { + rpc Hello ( .demo.CustomerRequest ) returns ( .demo.Customer ); +} +``` + +Hello world: + +``` +$ grpcurl -plaintext -d '{}' localhost:50051 demo.Greeter/Hello +{ + "id": 1, + "firstName": "Josh", + "lastName": "Long" +} +``` + +Native image: + +``` +$ CP=`java -jar $HOME/.m2/repository/org/springframework/boot/experimental/spring-boot-thin-launcher/1.0.22.RELEASE/spring-boot-thin-launcher-1.0.22.RELEASE-exec.jar --thin.archive=target/vanilla-proto-0.0.1-SNAPSHOT.jar --thin.classpath` +$ native-image -Dio.netty.noUnsafe=true --no-server -H:Name=target/demo \ +-H:+ReportExceptionStackTraces --no-fallback --allow-incomplete-classpath --report-unsupported-elements-at-runtime \ +-DremoveUnusedAutoconfig=true -cp $CP \ +--initialize-at-build-time='com.google.protobuf.ExtensionRegistry,com.google.protobuf.ExtensionLite,com.google.protobuf.Extension,io.netty.handler.codec.http2.ReadOnlyHttp2Headers,io.netty.handler.codec.http2.CharSequenceMap,io.netty.handler.codec.http2.Http2Headers$PseudoHeaderName' \ +com.example.ProtoApplication` \ No newline at end of file diff --git a/samples/vanilla-grpc/compile.sh b/samples/vanilla-grpc/compile.sh new file mode 100755 index 000000000..116f2cd8a --- /dev/null +++ b/samples/vanilla-grpc/compile.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +export EXECUTABLE_NAME=grpc + +../../mvnw -DskipTests clean package + +export JAR=`ls -1 target/*.jar` + +rm $EXECUTABLE_NAME +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=$EXECUTABLE_NAME \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP com.example.ProtoApplication + #--debug-attach \ + +mv $EXECUTABLE_NAME ../../.. + +printf "\n\nCompiled app...\n" +cd ../../.. +time ./$EXECUTABLE_NAME + diff --git a/samples/vanilla-grpc/pom.xml b/samples/vanilla-grpc/pom.xml new file mode 100644 index 000000000..6aadf57d3 --- /dev/null +++ b/samples/vanilla-grpc/pom.xml @@ -0,0 +1,132 @@ + + + 4.0.0 + + com.example + vanilla-grpc + 0.0.1-SNAPSHOT + jar + + vanilla-grpc + Demo project for Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + UTF-8 + UTF-8 + 1.8 + 1.5.0.Final + 0.6.1 + 3.7.1 + 1.22.1 + + + + + org.springframework.boot + spring-boot-starter + + + io.grpc + grpc-netty + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + io.grpc + grpc-services + ${grpc.version} + + + + + + + kr.motd.maven + os-maven-plugin + ${os.plugin.version} + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf.plugin.version} + + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + + spring-snapshots + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + diff --git a/samples/vanilla-grpc/src/main/java/com/example/ProtoApplication.java b/samples/vanilla-grpc/src/main/java/com/example/ProtoApplication.java new file mode 100644 index 000000000..a3ca957b8 --- /dev/null +++ b/samples/vanilla-grpc/src/main/java/com/example/ProtoApplication.java @@ -0,0 +1,72 @@ +package com.example; + +import java.io.IOException; +import java.util.logging.Logger; + +import demo.CustomerProtos.Customer; +import demo.CustomerProtos.CustomerRequest; +import demo.GreeterGrpc; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.stub.StreamObserver; + +public class ProtoApplication { + + private static final Logger logger = Logger + .getLogger(ProtoApplication.class.getName()); + + private Server server; + + private void start() throws IOException { + /* The port on which the server should run */ + int port = 50051; + server = ServerBuilder.forPort(port) + .addService(ProtoReflectionService.newInstance()) + .addService(new GreeterImpl()).build().start(); + logger.info("Server started, listening on " + port); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM + // shutdown hook. + System.err.println( + "*** shutting down gRPC server since JVM is shutting down"); + ProtoApplication.this.stop(); + System.err.println("*** server shut down"); + } + }); + } + + private void stop() { + if (server != null) { + server.shutdown(); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + public static void main(String[] args) throws Exception { + final ProtoApplication server = new ProtoApplication(); + server.start(); + server.blockUntilShutdown(); + } +} + +class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void hello(CustomerRequest req, StreamObserver responseObserver) { + Customer reply = Customer.newBuilder().setId(1).setFirstName("Josh").setLastName("Long") + .build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } +} \ No newline at end of file diff --git a/samples/vanilla-grpc/src/main/proto/customer.proto b/samples/vanilla-grpc/src/main/proto/customer.proto new file mode 100644 index 000000000..cea761ebe --- /dev/null +++ b/samples/vanilla-grpc/src/main/proto/customer.proto @@ -0,0 +1,33 @@ +package demo; + +option java_package = "demo"; +option java_outer_classname = "CustomerProtos"; + +service Greeter { + rpc Hello(CustomerRequest) returns (Customer) {} +} + +message CustomerRequest {} + +message Customer { + required int32 id = 1; + required string firstName = 2; + required string lastName = 3; + + enum EmailType { + PRIVATE = 1; + PROFESSIONAL = 2; + } + + message EmailAddress { + required string email = 1; + optional EmailType type = 2 [default = PROFESSIONAL]; + } + + repeated EmailAddress email = 5; +} + +message Organization { + required string name = 1; + repeated Customer customer = 2; +} \ No newline at end of file diff --git a/samples/vanilla-grpc/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-grpc/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..2e6a4a0a2 --- /dev/null +++ b/samples/vanilla-grpc/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,12 @@ +[ + { + "name": "demo.CustomerProtos$Customer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "demo.GreeterGrpc", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] diff --git a/samples/vanilla-grpc/src/main/resources/META-INF/native-image/resource-config.json b/samples/vanilla-grpc/src/main/resources/META-INF/native-image/resource-config.json new file mode 100644 index 000000000..c2b59f99b --- /dev/null +++ b/samples/vanilla-grpc/src/main/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,5 @@ +{ + "resources": [ + {"pattern": ".*.proto"} + ] +} \ No newline at end of file diff --git a/samples/vanilla-grpc/src/test/resources/customer.data b/samples/vanilla-grpc/src/test/resources/customer.data new file mode 100644 index 0000000000000000000000000000000000000000..8cb1bceb963e9b812d854d64b7b4bac784bb9fad GIT binary patch literal 14 Vcmd;J5MpsjEK8MQ39d{n0ss=J17H9E literal 0 HcmV?d00001 diff --git a/samples/vanilla-jpa/compile.sh b/samples/vanilla-jpa/compile.sh new file mode 100755 index 000000000..245709827 --- /dev/null +++ b/samples/vanilla-jpa/compile.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +export EXECUTABLE_NAME=jpa +export JAR="vanilla-jpa-0.0.1.BUILD-SNAPSHOT.jar" + +../../mvnw -DskipTests clean package + +rm $EXECUTABLE_NAME +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=$EXECUTABLE_NAME \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP app.main.SampleApplication + #--debug-attach \ + +mv $EXECUTABLE_NAME ../../.. + +printf "\n\nCompiled app...\n" +cd ../../.. +#time ./orm -Dhibernate.dialect=org.hibernate.dialect.H2Dialect +# Do we need the -D on this one? +time ./$EXECUTABLE_NAME +# -Dhibernate.dialect=org.hibernate.dialect.H2Dialect + diff --git a/samples/vanilla-jpa/pom.xml b/samples/vanilla-jpa/pom.xml new file mode 100644 index 000000000..ab83e62c7 --- /dev/null +++ b/samples/vanilla-jpa/pom.xml @@ -0,0 +1,170 @@ + + + 4.0.0 + + org.springframework.experimental + vanilla-jpa + 0.0.1.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + +org.springframework +spring-context-indexer + + + org.springframework.boot + spring-boot-starter-data-jpa + + + net.bytebuddy + byte-buddy + + + org.javassist + javassist + + + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-webflux + + + io.netty + netty-transport-native-epoll + + + jakarta.validation + jakarta.validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + org.hibernate.validator + hibernate-validator + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + 1.8 + app.main.SampleApplication + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.hibernate.orm.tooling + hibernate-enhance-maven-plugin + ${hibernate.version} + + + + true + true + true + true + false + + + enhance + + + + + + + + + + jitpack.io + https://jitpack.io + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + false + + + true + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + true + + + true + + + + + diff --git a/samples/vanilla-jpa/src/main/java/app/main/SampleApplication.java b/samples/vanilla-jpa/src/main/java/app/main/SampleApplication.java new file mode 100644 index 000000000..254f594ff --- /dev/null +++ b/samples/vanilla-jpa/src/main/java/app/main/SampleApplication.java @@ -0,0 +1,68 @@ +package app.main; + +import java.util.Optional; + +import app.main.model.Foo; +import app.main.model.FooRepository; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.server.RouterFunction; + +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +@SpringBootApplication(proxyBeanMethods = false) +public class SampleApplication { + + private FooRepository entities; + + public SampleApplication(FooRepository entities) { + this.entities = entities; + } + + @Bean + public CommandLineRunner runner() { + System.err.println("+++++++++++"); + return args -> { + try { + System.err.println("****"); + Optional foo = entities.findById(1L); + System.err.println("****: " + foo); + if (!foo.isPresent()) { + entities.save(new Foo("Hello")); + } + } + catch (Exception e) { + e.printStackTrace(); + } + }; + } + + @Bean + public RouterFunction userEndpoints() { + return route(GET("/"), request -> ok().body( + Mono.fromCallable(this::findOne).log().subscribeOn(Schedulers.elastic()), + Foo.class)); + } + + private Foo findOne() { + try { + return entities.findById(1L).get(); + } + catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} diff --git a/samples/vanilla-jpa/src/main/java/app/main/model/Foo.java b/samples/vanilla-jpa/src/main/java/app/main/model/Foo.java new file mode 100644 index 000000000..27d07f437 --- /dev/null +++ b/samples/vanilla-jpa/src/main/java/app/main/model/Foo.java @@ -0,0 +1,30 @@ +package app.main.model; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Foo { + @Id + @GeneratedValue(strategy=GenerationType.AUTO) + private long id; + private String value; + public Foo() { + } + public Foo(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + public void setValue(String value) { + this.value = value; + } + @Override + public String toString() { + return String.format( + "Foo[id=%d, value='%s']", + id, value); + } +} diff --git a/samples/vanilla-jpa/src/main/java/app/main/model/FooRepository.java b/samples/vanilla-jpa/src/main/java/app/main/model/FooRepository.java new file mode 100644 index 000000000..d7a079f0d --- /dev/null +++ b/samples/vanilla-jpa/src/main/java/app/main/model/FooRepository.java @@ -0,0 +1,25 @@ +/* + * Copyright 2019-2019 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 app.main.model; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * @author Dave Syer + * + */ +public interface FooRepository extends JpaRepository { +} diff --git a/samples/vanilla-jpa/src/main/resources/META-INF/native-image/proxy-config.json b/samples/vanilla-jpa/src/main/resources/META-INF/native-image/proxy-config.json new file mode 100644 index 000000000..ad6e882c1 --- /dev/null +++ b/samples/vanilla-jpa/src/main/resources/META-INF/native-image/proxy-config.json @@ -0,0 +1,3 @@ +[ + ["app.main.model.FooRepository", "org.springframework.data.repository.Repository", "org.springframework.transaction.interceptor.TransactionalProxy", "org.springframework.aop.framework.Advised", "org.springframework.core.DecoratingProxy"] +] \ No newline at end of file diff --git a/samples/vanilla-jpa/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-jpa/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..5d09cac6e --- /dev/null +++ b/samples/vanilla-jpa/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,7 @@ +[ + { + "name": "app.main.model.Foo", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] \ No newline at end of file diff --git a/samples/vanilla-jpa/src/main/resources/application.properties b/samples/vanilla-jpa/src/main/resources/application.properties new file mode 100644 index 000000000..7284df336 --- /dev/null +++ b/samples/vanilla-jpa/src/main/resources/application.properties @@ -0,0 +1,3 @@ +logging.level.root=debug +#spring.jpa.show-sql=true +#spring.data.jpa.repositories.bootstrap-mode=lazy diff --git a/samples/vanilla-jpa/src/main/resources/hibernate.properties b/samples/vanilla-jpa/src/main/resources/hibernate.properties new file mode 100644 index 000000000..35396da22 --- /dev/null +++ b/samples/vanilla-jpa/src/main/resources/hibernate.properties @@ -0,0 +1 @@ +hibernate.bytecode.provider=none \ No newline at end of file diff --git a/samples/vanilla-jpa/src/test/java/app/main/SampleApplicationTests.java b/samples/vanilla-jpa/src/test/java/app/main/SampleApplicationTests.java new file mode 100644 index 000000000..f48c83bed --- /dev/null +++ b/samples/vanilla-jpa/src/test/java/app/main/SampleApplicationTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2018 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. + */ + +package app.main; + +import org.junit.Before; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.server.WebHandler; + +/** + * @author Dave Syer + * + */ +@SpringBootTest +@AutoConfigureWebTestClient +public class SampleApplicationTests { + + @Autowired + private WebHandler webHandler; + + @Autowired + private WebTestClient client; + + @Before + public void init() { + client = WebTestClient.bindToWebHandler(webHandler).build(); + } + + @Test + public void test() { + client.get().uri("/").exchange().expectBody(String.class).isEqualTo("{\"value\":\"Hello\"}"); + } + +} diff --git a/samples/vanilla-orm/.gitignore b/samples/vanilla-orm/.gitignore new file mode 100644 index 000000000..82eca336e --- /dev/null +++ b/samples/vanilla-orm/.gitignore @@ -0,0 +1,25 @@ +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ \ No newline at end of file diff --git a/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.jar b/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..9cc84ea9b4d95453115d0c26488d6a78694e0bc6 GIT binary patch literal 47610 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 literal 0 HcmV?d00001 diff --git a/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.properties b/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..9dda3b659 --- /dev/null +++ b/samples/vanilla-orm/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip diff --git a/samples/vanilla-orm/README.md b/samples/vanilla-orm/README.md new file mode 100644 index 000000000..010c35cb4 --- /dev/null +++ b/samples/vanilla-orm/README.md @@ -0,0 +1,2 @@ + +Copy of Daves app from git@github.com:dsyer/vanilla-orm.git diff --git a/samples/vanilla-orm/compile.sh b/samples/vanilla-orm/compile.sh new file mode 100755 index 000000000..cd387ba37 --- /dev/null +++ b/samples/vanilla-orm/compile.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="orm-0.0.1.BUILD-SNAPSHOT.jar" +rm orm +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP com.example.demo.DemoApplication + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=orm \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP app.main.SampleApplication + + #--debug-attach \ +mv orm ../../.. + +printf "\n\nCompiled app (demo)\n" +cd ../../.. +time ./orm -Dhibernate.dialect=org.hibernate.dialect.H2Dialect + diff --git a/samples/vanilla-orm/entity.sh b/samples/vanilla-orm/entity.sh new file mode 100755 index 000000000..84c548cfc --- /dev/null +++ b/samples/vanilla-orm/entity.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +if ! [ "$1" == "" ]; then + cd $1; shift +fi + +n=100 +if ! [ "$1" == "" ]; then + n=$1; shift +fi + +base=src/main/java +pkg=app/main/model +if ! [ "$1" == "" ]; then + pkg=$1; shift +fi + +mkdir -p $base/$pkg + +foo=$base/$pkg/Foo.java +imprt=`echo $pkg | sed -e s,/,.,g` +if ! [ -e $foo ]; then + cat < $foo +package ${imprt}; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Foo { + @Id + @GeneratedValue(strategy=GenerationType.AUTO) + private long id; + private String value; + public Foo() { + } + public Foo(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + public void setValue(String value) { + this.value = value; + } + @Override + public String toString() { + return String.format( + "Foo[id=%d, value='%s']", + id, value); + } +} +EOF +fi + +for f in $(seq 1 $n); do + target=${foo/Foo/Foo$f} + sed -e "s/Foo/Foo$f/g" $foo > $target +done diff --git a/samples/vanilla-orm/mvnw b/samples/vanilla-orm/mvnw new file mode 100755 index 000000000..5bf251c07 --- /dev/null +++ b/samples/vanilla-orm/mvnw @@ -0,0 +1,225 @@ +#!/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 +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 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 Migwn, 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)`" + # TODO classpath? +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 + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +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 + +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 "$@" diff --git a/samples/vanilla-orm/mvnw.cmd b/samples/vanilla-orm/mvnw.cmd new file mode 100755 index 000000000..5bf251c07 --- /dev/null +++ b/samples/vanilla-orm/mvnw.cmd @@ -0,0 +1,225 @@ +#!/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 +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 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 Migwn, 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)`" + # TODO classpath? +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 + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +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 + +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 "$@" diff --git a/samples/vanilla-orm/pom.xml b/samples/vanilla-orm/pom.xml new file mode 100644 index 000000000..85c3d8b30 --- /dev/null +++ b/samples/vanilla-orm/pom.xml @@ -0,0 +1,216 @@ + + + 4.0.0 + + org.springframework.experimental + orm + 0.0.1.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework + spring-orm + + + org.hibernate + hibernate-entitymanager + + + net.bytebuddy + byte-buddy + + + org.javassist + javassist + + + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-webflux + + + io.netty + netty-transport-native-epoll + + + jakarta.validation + jakarta.validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + org.hibernate.validator + hibernate-validator + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + com.github.mp911de.microbenchmark-runner + microbenchmark-runner-junit5 + 0.1.0.RELEASE + test + + + org.springframework.boot.experimental + spring-boot-thin-launcher + ${thin.version} + test + + + + + 1.8 + app.main.SampleApplication + 1.21 + 1.0.22.RELEASE + + + + + tools.jar + + [1.8,1.9) + + + + com.sun + tools + ${java.version} + system + ${java.home}/../lib/tools.jar + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.hibernate.orm.tooling + hibernate-enhance-maven-plugin + ${hibernate.version} + + + + true + true + true + true + false + + + enhance + + + + + + + + + + jitpack.io + https://jitpack.io + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + false + + + true + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + true + + + true + + + + + diff --git a/samples/vanilla-orm/src/main/java/app/main/BeanCountingApplicationListener.java b/samples/vanilla-orm/src/main/java/app/main/BeanCountingApplicationListener.java new file mode 100644 index 000000000..57bcb6620 --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/BeanCountingApplicationListener.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016-2017 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. + */ +package app.main; + +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping; + +public class BeanCountingApplicationListener + implements ApplicationListener, ApplicationContextAware { + + private static Log logger = LogFactory.getLog(BeanCountingApplicationListener.class); + private ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + if (!event.getApplicationContext().equals(this.context)) { + return; + } + ConfigurableApplicationContext context = event.getApplicationContext(); + log(context); + } + + public void log(ConfigurableApplicationContext context) { + int count = 0; + String id = context.getId(); + List names = new ArrayList<>(); + if (context.getEnvironment().getProperty("initialize", Boolean.class, false)) { + try { + context.getBean(AbstractHandlerMethodMapping.class); + logger.info("Instantiated bean"); + } + catch (BeansException e) { + } + } + while (context != null) { + count += context.getBeanDefinitionCount(); + names.addAll(Arrays.asList(context.getBeanDefinitionNames())); + context = (ConfigurableApplicationContext) context.getParent(); + } + logger.info("Bean count: " + id + "=" + count); + logger.debug("Bean names: " + id + "=" + names); + try { + logger.info("Class count: " + id + "=" + ManagementFactory + .getClassLoadingMXBean().getTotalLoadedClassCount()); + } + catch (Exception e) { + } + } + +} diff --git a/samples/vanilla-orm/src/main/java/app/main/SampleApplication.java b/samples/vanilla-orm/src/main/java/app/main/SampleApplication.java new file mode 100644 index 000000000..4bcfd4ff1 --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/SampleApplication.java @@ -0,0 +1,57 @@ +package app.main; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.EntityTransaction; + +import app.main.model.Foo; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.server.RouterFunction; + +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +@SpringBootApplication(proxyBeanMethods = false) +public class SampleApplication { + + private EntityManagerFactory entities; + + public SampleApplication(EntityManagerFactory entities) { + this.entities = entities; + } + + @Bean + public CommandLineRunner runner() { + return args -> { + EntityManager manager = entities.createEntityManager(); + EntityTransaction transaction = manager.getTransaction(); + transaction.begin(); + Foo foo = manager.find(Foo.class, 1L); + if (foo == null) { + manager.persist(new Foo("Hello")); + } + transaction.commit(); + }; + } + + @Bean + public RouterFunction userEndpoints() { + return route(GET("/"), + request -> ok().body(Mono + .fromCallable( + () -> entities.createEntityManager().find(Foo.class, 1L)) + .subscribeOn(Schedulers.elastic()), Foo.class)); + } + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} diff --git a/samples/vanilla-orm/src/main/java/app/main/ShutdownApplicationListener.java b/samples/vanilla-orm/src/main/java/app/main/ShutdownApplicationListener.java new file mode 100644 index 000000000..afca68768 --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/ShutdownApplicationListener.java @@ -0,0 +1,101 @@ +/* + * Copyright 2019-2019 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 app.main; + +import java.lang.reflect.Method; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.core.annotation.Order; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +@Order(Ordered.HIGHEST_PRECEDENCE) +public class ShutdownApplicationListener + implements ApplicationListener, DisposableBean, + ApplicationContextAware { + + private static final String SHUTDOWN_LISTENER = "SHUTDOWN_LISTENER"; + public static final String MARKER = "Benchmark app stopped"; + + private ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + if (!event.getApplicationContext().equals(this.context)) { + return; + } + if (isSpringBootApplication(sources(event))) { + ((DefaultListableBeanFactory) event.getApplicationContext().getBeanFactory()) + .registerDisposableBean(SHUTDOWN_LISTENER, this); + } + } + + @Override + public void destroy() throws Exception { + try { + System.out.println(MARKER); + } + catch (Exception e) { + } + } + + private boolean isSpringBootApplication(Set> sources) { + for (Class source : sources) { + if (AnnotatedElementUtils.hasAnnotation(source, + SpringBootConfiguration.class)) { + return true; + } + } + return false; + } + + private Set> sources(ApplicationReadyEvent event) { + Method method = ReflectionUtils.findMethod(SpringApplication.class, + "getAllSources"); + if (method == null) { + method = ReflectionUtils.findMethod(SpringApplication.class, "getSources"); + } + ReflectionUtils.makeAccessible(method); + @SuppressWarnings("unchecked") + Set objects = (Set) ReflectionUtils.invokeMethod(method, + event.getSpringApplication()); + Set> result = new LinkedHashSet<>(); + for (Object object : objects) { + if (object instanceof String) { + object = ClassUtils.resolveClassName((String) object, null); + } + result.add((Class) object); + } + return result; + } +} \ No newline at end of file diff --git a/samples/vanilla-orm/src/main/java/app/main/StartupApplicationListener.java b/samples/vanilla-orm/src/main/java/app/main/StartupApplicationListener.java new file mode 100644 index 000000000..1748659dc --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/StartupApplicationListener.java @@ -0,0 +1,92 @@ +/* + * Copyright 2019-2019 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 app.main; + +import java.lang.reflect.Method; +import java.util.LinkedHashSet; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; + +public class StartupApplicationListener + implements ApplicationListener, ApplicationContextAware { + + public static final String MARKER = "Benchmark app started"; + private static Log logger = LogFactory.getLog(StartupApplicationListener.class); + private ApplicationContext context; + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + if (!event.getApplicationContext().equals(this.context)) { + return; + } + if (isSpringBootApplication(sources(event))) { + try { + logger.info(MARKER); + } + catch (Exception e) { + } + } + } + + private boolean isSpringBootApplication(Set> sources) { + for (Class source : sources) { + if (AnnotatedElementUtils.hasAnnotation(source, + SpringBootConfiguration.class)) { + return true; + } + } + return false; + } + + private Set> sources(ApplicationReadyEvent event) { + Method method = ReflectionUtils.findMethod(SpringApplication.class, + "getAllSources"); + if (method == null) { + method = ReflectionUtils.findMethod(SpringApplication.class, "getSources"); + } + ReflectionUtils.makeAccessible(method); + @SuppressWarnings("unchecked") + Set objects = (Set) ReflectionUtils.invokeMethod(method, + event.getSpringApplication()); + Set> result = new LinkedHashSet<>(); + for (Object object : objects) { + if (object instanceof String) { + object = ClassUtils.resolveClassName((String) object, null); + } + result.add((Class) object); + } + return result; + } + +} \ No newline at end of file diff --git a/samples/vanilla-orm/src/main/java/app/main/model/Foo.java b/samples/vanilla-orm/src/main/java/app/main/model/Foo.java new file mode 100644 index 000000000..27d07f437 --- /dev/null +++ b/samples/vanilla-orm/src/main/java/app/main/model/Foo.java @@ -0,0 +1,30 @@ +package app.main.model; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class Foo { + @Id + @GeneratedValue(strategy=GenerationType.AUTO) + private long id; + private String value; + public Foo() { + } + public Foo(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + public void setValue(String value) { + this.value = value; + } + @Override + public String toString() { + return String.format( + "Foo[id=%d, value='%s']", + id, value); + } +} diff --git a/samples/vanilla-orm/src/main/resources/META-INF/spring.factories b/samples/vanilla-orm/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..13e8da539 --- /dev/null +++ b/samples/vanilla-orm/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.context.ApplicationListener=\ +app.main.BeanCountingApplicationListener,\ +app.main.StartupApplicationListener,\ +app.main.ShutdownApplicationListener diff --git a/samples/vanilla-orm/src/main/resources/application.properties b/samples/vanilla-orm/src/main/resources/application.properties new file mode 100644 index 000000000..7813d9f0a --- /dev/null +++ b/samples/vanilla-orm/src/main/resources/application.properties @@ -0,0 +1,3 @@ +logging.level.slim=debug +#spring.jpa.show-sql=true +spring.data.jpa.repositories.bootstrap-mode=lazy diff --git a/samples/vanilla-orm/src/main/resources/hibernate.properties b/samples/vanilla-orm/src/main/resources/hibernate.properties new file mode 100644 index 000000000..35396da22 --- /dev/null +++ b/samples/vanilla-orm/src/main/resources/hibernate.properties @@ -0,0 +1 @@ +hibernate.bytecode.provider=none \ No newline at end of file diff --git a/samples/vanilla-orm/src/test/java/app/main/CaptureSystemOutput.java b/samples/vanilla-orm/src/test/java/app/main/CaptureSystemOutput.java new file mode 100644 index 000000000..4c7f3440d --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/CaptureSystemOutput.java @@ -0,0 +1,253 @@ +/* + * Copyright 2012-2017 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. + */ + +package app.main; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.List; + +import org.hamcrest.Matcher; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.platform.commons.support.ReflectionSupport; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.allOf; + +/** + * {@code @CaptureSystemOutput} is a JUnit JUpiter extension for capturing output to + * {@code System.out} and {@code System.err} with expectations supported via Hamcrest + * matchers. + * + *

Example Usage

+ * + *
+ * {@literal @}Test
+ * {@literal @}CaptureSystemOutput
+ * void systemOut(OutputCapture outputCapture) {
+ *     outputCapture.expect(containsString("System.out!"));
+ *
+ *     System.out.println("Printed to System.out!");
+ * }
+ * 
+ * {@literal @}Test
+ * {@literal @}CaptureSystemOutput
+ * void systemErr(OutputCapture outputCapture) {
+ *     outputCapture.expect(containsString("System.err!"));
+ *
+ *     System.err.println("Printed to System.err!");
+ * }
+ * 
+ * + *

+ * Based on code from Spring Boot's OutputCapture + * rule for JUnit 4 by Phillip Webb and Andy Wilkinson. + * + * @author Sam Brannen + * @author Phillip Webb + * @author Andy Wilkinson + */ +@Target({ TYPE, METHOD }) +@Retention(RUNTIME) +@ExtendWith(CaptureSystemOutput.Extension.class) +public @interface CaptureSystemOutput { + + class Extension implements BeforeEachCallback, AfterEachCallback, ParameterResolver { + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + getOutputCapture(context).captureOutput(); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + OutputCapture outputCapture = getOutputCapture(context); + try { + if (!outputCapture.matchers.isEmpty()) { + String output = outputCapture.toString(); + assertThat(output, allOf(outputCapture.matchers)); + } + } + finally { + outputCapture.releaseOutput(); + } + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + boolean isTestMethodLevel = extensionContext.getTestMethod().isPresent(); + boolean isOutputCapture = parameterContext.getParameter() + .getType() == OutputCapture.class; + return isTestMethodLevel && isOutputCapture; + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, + ExtensionContext extensionContext) { + return getOutputCapture(extensionContext); + } + + private OutputCapture getOutputCapture(ExtensionContext context) { + return getOrComputeIfAbsent(getStore(context), OutputCapture.class); + } + + private V getOrComputeIfAbsent(Store store, Class type) { + return store.getOrComputeIfAbsent(type, ReflectionSupport::newInstance, type); + } + + private Store getStore(ExtensionContext context) { + return context.getStore( + Namespace.create(getClass(), context.getRequiredTestMethod())); + } + + } + + /** + * {@code OutputCapture} captures output to {@code System.out} and {@code System.err}. + * + *

+ * To obtain an instance of {@code OutputCapture}, declare a parameter of type + * {@code OutputCapture} in a JUnit Jupiter {@code @Test}, {@code @BeforeEach}, or + * {@code @AfterEach} method. + * + *

+ * {@linkplain #expect Expectations} are supported via Hamcrest matchers. + * + *

+ * To obtain all output to {@code System.out} and {@code System.err}, simply invoke + * {@link #toString()}. + * + * @author Phillip Webb + * @author Andy Wilkinson + * @author Sam Brannen + */ + static class OutputCapture { + + private final List> matchers = new ArrayList<>(); + + private CaptureOutputStream captureOut; + + private CaptureOutputStream captureErr; + + private ByteArrayOutputStream copy; + + void captureOutput() { + this.copy = new ByteArrayOutputStream(); + this.captureOut = new CaptureOutputStream(System.out, this.copy); + this.captureErr = new CaptureOutputStream(System.err, this.copy); + System.setOut(new PrintStream(this.captureOut)); + System.setErr(new PrintStream(this.captureErr)); + } + + void releaseOutput() { + System.setOut(this.captureOut.getOriginal()); + System.setErr(this.captureErr.getOriginal()); + this.copy = null; + } + + private void flush() { + try { + this.captureOut.flush(); + this.captureErr.flush(); + } + catch (IOException ex) { + // ignore + } + } + + /** + * Verify that the captured output is matched by the supplied {@code matcher}. + * + *

+ * Verification is performed after the test method has executed. + * + * @param matcher the matcher + */ + public void expect(Matcher matcher) { + this.matchers.add(matcher); + } + + /** + * Return all captured output to {@code System.out} and {@code System.err} as a + * single string. + */ + @Override + public String toString() { + flush(); + return this.copy.toString(); + } + + private static class CaptureOutputStream extends OutputStream { + + private final PrintStream original; + + private final OutputStream copy; + + CaptureOutputStream(PrintStream original, OutputStream copy) { + this.original = original; + this.copy = copy; + } + + PrintStream getOriginal() { + return this.original; + } + + @Override + public void write(int b) throws IOException { + this.copy.write(b); + this.original.write(b); + this.original.flush(); + } + + @Override + public void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + this.copy.write(b, off, len); + this.original.write(b, off, len); + } + + @Override + public void flush() throws IOException { + this.copy.flush(); + this.original.flush(); + } + + } + + } + +} \ No newline at end of file diff --git a/samples/vanilla-orm/src/test/java/app/main/CsvResultsWriterFactory.java b/samples/vanilla-orm/src/test/java/app/main/CsvResultsWriterFactory.java new file mode 100644 index 000000000..0fcd4626e --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/CsvResultsWriterFactory.java @@ -0,0 +1,181 @@ +/* + * Copyright 2016-2017 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. + */ +package app.main; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import jmh.mbr.core.ResultsWriter; +import jmh.mbr.core.ResultsWriterFactory; +import org.openjdk.jmh.results.Result; +import org.openjdk.jmh.results.RunResult; +import org.openjdk.jmh.runner.format.OutputFormat; +import org.openjdk.jmh.util.FileUtils; +import org.openjdk.jmh.util.ScoreFormatter; +import org.openjdk.jmh.util.Statistics; + +/** + * @author Dave Syer + * + */ +public class CsvResultsWriterFactory implements ResultsWriterFactory { + + @Override + public ResultsWriter forUri(String uri) { + if (!uri.startsWith("csv:")) { + return null; + } + return new ResultsWriter() { + + @Override + public void write(OutputFormat output, Collection results) { + StringBuilder report = new StringBuilder(); + try { + Map params = new LinkedHashMap<>(); + int paramPlaces = 0; + for (RunResult result : results) { + for (String param : result.getParams().getParamsKeys()) { + int count = paramPlaces; + params.computeIfAbsent(param, key -> count); + paramPlaces++; + } + } + Map auxes = new LinkedHashMap<>(); + int auxPlaces = 0; + for (RunResult result : results) { + @SuppressWarnings("rawtypes") + Map second = result.getAggregatedResult() + .getSecondaryResults(); + if (second != null) { + for (String aux : second.keySet()) { + int count = auxPlaces; + auxes.computeIfAbsent(aux, key -> count); + auxPlaces++; + } + } + } + StringBuilder header = new StringBuilder(); + header.append("class, method, "); + params.forEach((key, value) -> header.append(key).append(", ")); + auxes.forEach((key, value) -> header.append(propertyName(key)) + .append(", ")); + header.append("median, mean, range"); + report.append(header.toString()).append(System.lineSeparator()); + for (RunResult result : results) { + StringBuilder builder = new StringBuilder(); + String benchmark = result.getParams().getBenchmark(); + String cls = benchmark.substring(0, benchmark.lastIndexOf(".")); + String mthd = benchmark.substring(benchmark.lastIndexOf(".") + 1); + builder.append(cls).append(", ").append(mthd).append(", "); + for (int i = 0; i < params.values().size(); i++) { + boolean found = false; + for (String param : result.getParams().getParamsKeys()) { + if (params.get(param) == i) { + builder.append(result.getParams().getParam(param)) + .append(", "); + found = true; + } + } + if (!found) { + builder.append(", "); + } + } + @SuppressWarnings("rawtypes") + Map second = result.getAggregatedResult() + .getSecondaryResults(); + if (second != null) { + for (int i = 0; i < auxes.values().size(); i++) { + boolean found = false; + for (String param : second.keySet()) { + if (auxes.get(param) == i) { + builder.append(ScoreFormatter + .format(second.get(param).getStatistics() + .getPercentile(0.5))) + .append(", "); + found = true; + } + } + if (!found) { + builder.append(", "); + } + } + } + Statistics statistics = result.getPrimaryResult().getStatistics(); + builder.append( + ScoreFormatter.format(statistics.getPercentile(0.5))); + builder.append(", "); + builder.append(ScoreFormatter.format(statistics.getMean())); + builder.append(", "); + double error = (statistics.getMax() - statistics.getMin()) / 2; + builder.append(ScoreFormatter.format(error)); + report.append(builder.toString()).append(System.lineSeparator()); + } + } + catch (Exception e) { + e.printStackTrace(); + } + output.println(report.toString()); + if (uri != null) { + File file = new File(uri.substring("csv:".length())); + file.getParentFile().mkdirs(); + if (file.getParentFile().exists()) { + try { + FileUtils.writeLines(file, + Collections.singleton(report.toString())); + } + catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + private String propertyName(String key) { + if (key.matches("get[A-Z].*")) { + key = changeFirstCharacterCase(key.substring(3), false); + } + return key; + } + }; + } + + private static String changeFirstCharacterCase(String str, boolean capitalize) { + if (str == null || str.length() == 0) { + return str; + } + + char baseChar = str.charAt(0); + char updatedChar; + if (capitalize) { + updatedChar = Character.toUpperCase(baseChar); + } + else { + updatedChar = Character.toLowerCase(baseChar); + } + if (baseChar == updatedChar) { + return str; + } + + char[] chars = str.toCharArray(); + chars[0] = updatedChar; + return new String(chars, 0, chars.length); + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherState.java b/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherState.java new file mode 100644 index 000000000..85d7c75a2 --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherState.java @@ -0,0 +1,310 @@ +/* + * Copyright 2016-2017 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. + */ +package app.main; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.boot.loader.archive.Archive; +import org.springframework.boot.loader.thin.ArchiveUtils; +import org.springframework.boot.loader.thin.DependencyResolver; +import org.springframework.boot.loader.thin.PathResolver; +import org.springframework.util.ReflectionUtils; + +public class ProcessLauncherState { + + private static final Logger log = LoggerFactory.getLogger(ProcessLauncherState.class); + + public static final String CLASS_COUNT_MARKER = "Class count"; + + public static final String BEAN_COUNT_MARKER = "Bean count"; + + private Process started; + + private List args = new ArrayList<>(); + + private List progs = new ArrayList<>(); + + private static List DEFAULT_JVM_ARGS = Arrays.asList("-Xmx128m", "-cp", "", + "-Djava.security.egd=file:/dev/./urandom", "-noverify", + "-Dspring.data.jpa.repositories.bootstrap-mode=lazy", + "-Dspring.cache.type=none", "-Dspring.main.lazy-initialization=true", + "-Dspring.jmx.enabled=false"); + + private File home; + + private String mainClass; + + private String name = "thin"; + + private String[] profiles = new String[0]; + + private BufferedReader buffer; + + private CountDownLatch latch = new CountDownLatch(1); + + private int classes; + + private int beans; + + private long memory; + + private long heap; + + private String classpath; + + public int getClasses() { + return classes; + } + + public int getBeans() { + return beans; + } + + public double getMemory() { + return memory / (1024. * 1024); + } + + public double getHeap() { + return heap / (1024. * 1024); + } + + public ProcessLauncherState(String dir, String... args) { + this.args.addAll(DEFAULT_JVM_ARGS); + String vendor = System.getProperty("java.vendor", "").toLowerCase(); + if (vendor.contains("ibm") || vendor.contains("j9")) { + this.args.addAll(Arrays.asList("-Xms32m", "-Xquickstart", "-Xshareclasses", + "-Xscmx128m")); + } + else { + this.args.addAll(Arrays.asList("-XX:TieredStopAtLevel=1")); + } + if (System.getProperty("bench.args") != null) { + this.args.addAll(Arrays.asList(System.getProperty("bench.args").split(" "))); + } + this.progs.addAll(Arrays.asList(args)); + this.home = new File(dir); + } + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + public void setName(String name) { + this.name = name; + } + + public void setProfiles(String... profiles) { + this.profiles = profiles; + } + + public void addArgs(String... args) { + this.args.addAll(Arrays.asList(args)); + } + + protected String getClasspath() { + return getClasspath(true); + } + + protected String getClasspath(boolean includeTargetClasses) { + if (this.classpath == null) { + PathResolver resolver = new PathResolver(DependencyResolver.instance()); + Archive root = ArchiveUtils.getArchive(ProcessLauncherState.class); + List resolved = resolver.resolve(root, name, profiles); + StringBuilder builder = new StringBuilder(); + if (includeTargetClasses) { + builder.append(new File("target/classes").getAbsolutePath()); + } + else { + builder.append(new File("target/orm-0.0.1.BUILD-SNAPSHOT.jar") + .getAbsolutePath()); + } + try { + for (Archive archive : resolved) { + if (archive.getUrl().equals(root.getUrl())) { + continue; + } + if (builder.length() > 0) { + builder.append(File.pathSeparator); + } + builder.append(file(archive.getUrl().toString())); + } + } + catch (MalformedURLException e) { + throw new IllegalStateException("Cannot find archive", e); + } + log.debug("Classpath: " + builder); + this.classpath = builder.toString(); + } + return this.classpath; + } + + private String file(String path) { + if (path.endsWith("!/")) { + path = path.substring(0, path.length() - 2); + } + if (path.startsWith("jar:")) { + path = path.substring("jar:".length()); + } + if (path.startsWith("file:")) { + path = path.substring("file:".length()); + } + return path; + } + + public String getPid() { + String pid = null; + try { + if (started != null) { + Field field = ReflectionUtils.findField(started.getClass(), "pid"); + ReflectionUtils.makeAccessible(field); + pid = "" + ReflectionUtils.getField(field, started); + } + } + catch (Exception e) { + } + return pid; + } + + public void after() throws Exception { + drain(); + if (started != null && started.isAlive()) { + latch.await(10, TimeUnit.SECONDS); + Map metrics = VirtualMachineMetrics.fetch(getPid()); + this.memory = VirtualMachineMetrics.total(metrics); + this.heap = VirtualMachineMetrics.heap(metrics); + this.classes = metrics.get("Classes").intValue(); + System.out.println( + "Stopped " + mainClass + ": " + started.destroyForcibly().waitFor()); + } + } + + private BufferedReader getBuffer() { + return this.buffer; + } + + public void run() throws Exception { + List jvmArgs = new ArrayList<>(this.args); + customize(jvmArgs); + started = exec(jvmArgs.toArray(new String[0]), this.progs.toArray(new String[0])); + InputStream stream = started.getInputStream(); + this.buffer = new BufferedReader(new InputStreamReader(stream)); + monitor(); + } + + public void before() throws Exception { + int classpath = args.indexOf("-cp"); + if (classpath >= 0 && args.get(classpath + 1).length() == 0) { + args.set(classpath + 1, getClasspath()); + } + } + + protected void customize(List args) { + } + + protected Process exec(String[] jvmArgs, String... progArgs) { + List args = new ArrayList<>(Arrays.asList(jvmArgs)); + args.add(0, System.getProperty("java.home") + "/bin/java"); + if (mainClass.length() > 0) { + args.add(mainClass); + } + int classpath = args.indexOf("-cp"); + if (classpath >= 0 && args.get(classpath + 1).length() == 0) { + args.set(classpath + 1, getClasspath()); + } + args.addAll(Arrays.asList(progArgs)); + ProcessBuilder builder = new ProcessBuilder(args); + builder.redirectErrorStream(true); + builder.directory(getHome()); + if (!"false".equals(System.getProperty("debug", "false"))) { + System.out.println("Executing: " + builder.command()); + } + Process started; + try { + started = builder.start(); + return started; + } + catch (Exception e) { + e.printStackTrace(); + throw new IllegalStateException("Cannot calculate classpath"); + } + } + + protected void monitor() throws Exception { + // use this method to wait for an app to start + output(getBuffer(), StartupApplicationListener.MARKER); + } + + protected void finish() throws Exception { + // use this method to wait for an app to stop + output(getBuffer(), ShutdownApplicationListener.MARKER); + } + + protected void drain() throws Exception { + System.out.println("Draining console buffer"); + output(getBuffer(), null); + latch.countDown(); + } + + protected void output(BufferedReader br, String marker) throws Exception { + StringBuilder sb = new StringBuilder(); + String line = null; + if (!"false".equals(System.getProperty("debug", "false"))) { + System.err.println("Scanning for: " + marker); + } + while ((marker != null || br.ready()) && (line = br.readLine()) != null + && (marker == null || !line.contains(marker))) { + sb.append(line + System.getProperty("line.separator")); + if (!"false".equals(System.getProperty("debug", "false"))) { + System.out.println(line); + } + if (line.contains(CLASS_COUNT_MARKER)) { + classes = Integer + .valueOf(line.substring(line.lastIndexOf("=") + 1).trim()); + } + if (line.contains(BEAN_COUNT_MARKER)) { + int count = Integer + .valueOf(line.substring(line.lastIndexOf("=") + 1).trim()); + beans = count > beans ? count : beans; + } + line = null; + } + if (line != null) { + sb.append(line + System.getProperty("line.separator")); + } + if ("false".equals(System.getProperty("debug", "false"))) { + System.out.println(sb.toString()); + } + } + + public File getHome() { + return home; + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherStateTests.java b/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherStateTests.java new file mode 100644 index 000000000..b40484b98 --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/ProcessLauncherStateTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016-2017 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. + */ + +package app.main; + +import java.net.URL; + +import app.main.CaptureSystemOutput.OutputCapture; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Dave Syer + * + */ +public class ProcessLauncherStateTests { + + @Test + @CaptureSystemOutput + public void vanilla(OutputCapture output) throws Exception { + // System.setProperty("bench.args", "-verbose:class"); + ProcessLauncherState state = new ProcessLauncherState("target") { + @Override + public void run() throws Exception { + super.run(); + try { + new URL("http://localhost:8080/owners").getContent(); + } + catch (Exception e) { + // ignore + } + } + }; + state.addArgs("-Dinitialize=true"); + state.setMainClass(SampleApplication.class.getName()); + // state.setProfiles("intg"); + state.before(); + state.run(); + state.after(); + assertThat(output.toString()).contains("Benchmark app started"); + assertThat(state.getHeap()).isGreaterThan(0); + assertThat(state.getClasses()).isGreaterThan(7000); + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/SampleApplicationTests.java b/samples/vanilla-orm/src/test/java/app/main/SampleApplicationTests.java new file mode 100644 index 000000000..f48c83bed --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/SampleApplicationTests.java @@ -0,0 +1,52 @@ +/* + * Copyright 2018 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. + */ + +package app.main; + +import org.junit.Before; +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.server.WebHandler; + +/** + * @author Dave Syer + * + */ +@SpringBootTest +@AutoConfigureWebTestClient +public class SampleApplicationTests { + + @Autowired + private WebHandler webHandler; + + @Autowired + private WebTestClient client; + + @Before + public void init() { + client = WebTestClient.bindToWebHandler(webHandler).build(); + } + + @Test + public void test() { + client.get().uri("/").exchange().expectBody(String.class).isEqualTo("{\"value\":\"Hello\"}"); + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/SimpleBenchmark.java b/samples/vanilla-orm/src/test/java/app/main/SimpleBenchmark.java new file mode 100644 index 000000000..95a8d9a90 --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/SimpleBenchmark.java @@ -0,0 +1,133 @@ +/* + * Copyright 2016-2017 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. + */ +package app.main; + +import java.net.URL; + +import jmh.mbr.junit5.Microbenchmark; +import org.openjdk.jmh.annotations.AuxCounters; +import org.openjdk.jmh.annotations.AuxCounters.Type; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; + +@Measurement(iterations = 5, time = 1) +@Warmup(iterations = 1, time = 1) +@Fork(value = 2, warmups = 0) +@BenchmarkMode(Mode.AverageTime) +@Microbenchmark +public class SimpleBenchmark { + + @Benchmark + public void main(MainState state) throws Exception { + state.setMainClass(state.sample.getConfig().getName()); + state.run(); + if (state.profile.toString().startsWith("first")) { + try { + System.out.println("Loading /"); + new URL("http://localhost:8080/").getContent(); + } + catch (Exception e) { + // ignore + } + } + } + + @State(Scope.Thread) + @AuxCounters(Type.EVENTS) + public static class MainState extends ProcessLauncherState { + + public static enum Profile { + + demo, first; + + } + + public static enum Sample { + + auto; + + private Class config; + + private Sample(Class config) { + this.config = config; + } + + private Sample() { + this.config = SampleApplication.class; + } + + public Class getConfig() { + return config; + } + + } + + @Param // ("auto") + private Sample sample; + + @Param + private Profile profile; + + public MainState() { + super("target"); + } + + @Override + public int getClasses() { + return super.getClasses(); + } + + @Override + public int getBeans() { + return super.getBeans(); + } + + @Override + public double getMemory() { + return super.getMemory(); + } + + @Override + public double getHeap() { + return super.getHeap(); + } + + @TearDown(Level.Invocation) + public void stop() throws Exception { + super.after(); + } + + @Setup(Level.Trial) + public void start() throws Exception { + if (profile != Profile.demo) { + setProfiles(profile.toString()); + } + super.before(); + } + + } + +} diff --git a/samples/vanilla-orm/src/test/java/app/main/VirtualMachineMetrics.java b/samples/vanilla-orm/src/test/java/app/main/VirtualMachineMetrics.java new file mode 100644 index 000000000..14000f7fb --- /dev/null +++ b/samples/vanilla-orm/src/test/java/app/main/VirtualMachineMetrics.java @@ -0,0 +1,198 @@ +/* + * Copyright 2016-2017 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. + */ +package app.main; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import javax.management.MBeanServerConnection; +import javax.management.ObjectName; +import javax.management.openmbean.CompositeData; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; + +import com.sun.tools.attach.VirtualMachine; + +/** + * @author Dave Syer + * + */ +@SuppressWarnings("restriction") +public class VirtualMachineMetrics { + + static final String CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress"; + + public static Map fetch(String pid) { + if (pid == null) { + return Collections.emptyMap(); + } + try { + VirtualMachine vm = VirtualMachine.attach(pid); + vm.startLocalManagementAgent(); + String connectorAddress = vm.getAgentProperties() + .getProperty(CONNECTOR_ADDRESS); + JMXServiceURL url = new JMXServiceURL(connectorAddress); + JMXConnector connector = JMXConnectorFactory.connect(url); + MBeanServerConnection connection = connector.getMBeanServerConnection(); + gc(connection); + Map metrics = new HashMap<>( + new BufferPools(connection).getMetrics()); + metrics.putAll(new Threads(connection).getMetrics()); + metrics.putAll(new Classes(connection).getMetrics()); + vm.detach(); + return metrics; + } + catch (Exception e) { + return Collections.emptyMap(); + } + } + + private static void gc(MBeanServerConnection mBeanServer) { + try { + final ObjectName on = new ObjectName("java.lang:type=Memory"); + mBeanServer.getMBeanInfo(on); + mBeanServer.invoke(on, "gc", new Object[0], new String[0]); + } + catch (Exception ignored) { + System.err.println("Unable to gc"); + } + } + + public static long total(Map metrics) { + return BufferPools.total(metrics); + } + + public static long heap(Map metrics) { + return BufferPools.heap(metrics); + } + +} + +class Threads { + + private final MBeanServerConnection mBeanServer; + + public Threads(MBeanServerConnection mBeanServer) { + this.mBeanServer = mBeanServer; + } + + public Map getMetrics() { + final Map gauges = new HashMap<>(); + final String name = "Threads"; + try { + final ObjectName on = new ObjectName("java.lang:type=Threading"); + mBeanServer.getMBeanInfo(on); + Integer value = (Integer) mBeanServer.getAttribute(on, "ThreadCount"); + gauges.put(name(name), Long.valueOf(value) * 1024 * 1024); + } + catch (Exception ignored) { + System.err.println("Unable to load thread pool MBeans: " + name); + } + return Collections.unmodifiableMap(gauges); + } + + private static String name(String name) { + return name.replace(" ", "-"); + } + +} + +class Classes { + + private final MBeanServerConnection mBeanServer; + + public Classes(MBeanServerConnection mBeanServer) { + this.mBeanServer = mBeanServer; + } + + public Map getMetrics() { + final Map gauges = new HashMap<>(); + final String name = "Classes"; + try { + final ObjectName on = new ObjectName("java.lang:type=ClassLoading"); + mBeanServer.getMBeanInfo(on); + Integer value = (Integer) mBeanServer.getAttribute(on, "LoadedClassCount"); + gauges.put(name(name), Long.valueOf(value)); + } + catch (Exception ignored) { + System.err.println("Unable to load thread pool MBeans: " + name); + } + return Collections.unmodifiableMap(gauges); + } + + private static String name(String name) { + return name.replace(" ", "-"); + } + +} + +class BufferPools { + + private static final String[] ATTRIBUTES = { "HeapMemoryUsage", + "NonHeapMemoryUsage" }; + + private final MBeanServerConnection mBeanServer; + + public BufferPools(MBeanServerConnection mBeanServer) { + this.mBeanServer = mBeanServer; + } + + public static long total(Map metrics) { + long total = 0; + System.err.println(metrics); + for (int i = 0; i < ATTRIBUTES.length; i++) { + final String name = name(ATTRIBUTES[i]); + total += metrics.containsKey(name) ? metrics.get(name) : 0; + } + total += metrics.getOrDefault("Threads", 0L); + return total; + } + + public static long heap(Map metrics) { + long total = 0; + for (int i = 0; i < ATTRIBUTES.length; i++) { + final String name = name(ATTRIBUTES[i]); + if (name.startsWith("Heap")) { + total += metrics.containsKey(name) ? metrics.get(name) : 0; + } + } + return total; + } + + public Map getMetrics() { + final Map gauges = new HashMap<>(); + for (int i = 0; i < ATTRIBUTES.length; i++) { + try { + final ObjectName on = new ObjectName("java.lang:type=Memory"); + final String name = ATTRIBUTES[i]; + mBeanServer.getMBeanInfo(on); + CompositeData value = (CompositeData) mBeanServer.getAttribute(on, name); + gauges.put(name(name), (Long) value.get("used")); + } + catch (Exception ignored) { + System.err.println("Unable to load memory pool MBeans"); + } + } + return Collections.unmodifiableMap(gauges); + } + + private static String name(String name) { + return name.replace(" ", "-"); + } + +} diff --git a/samples/vanilla-orm/src/test/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory b/samples/vanilla-orm/src/test/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory new file mode 100644 index 000000000..03fd95b1d --- /dev/null +++ b/samples/vanilla-orm/src/test/resources/META-INF/services/jmh.mbr.core.ResultsWriterFactory @@ -0,0 +1 @@ +app.main.CsvResultsWriterFactory \ No newline at end of file diff --git a/samples/vanilla-orm/src/test/resources/logback.xml b/samples/vanilla-orm/src/test/resources/logback.xml new file mode 100644 index 000000000..a6fe8de4a --- /dev/null +++ b/samples/vanilla-orm/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/vanilla-rabbit/compile.sh b/samples/vanilla-rabbit/compile.sh new file mode 100755 index 000000000..97966f74f --- /dev/null +++ b/samples/vanilla-rabbit/compile.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="vanilla-rabbit-0.0.1.BUILD-SNAPSHOT.jar" +rm rabbit +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP app.main.SampleApplication + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=rabbit\ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP app.main.SampleApplication + + #--debug-attach \ +mv rabbit ../../.. + +printf "\n\nCompiled app (demo)\n" +cd ../../.. +time ./rabbit + diff --git a/samples/vanilla-rabbit/pom.xml b/samples/vanilla-rabbit/pom.xml new file mode 100644 index 000000000..ffc806cd3 --- /dev/null +++ b/samples/vanilla-rabbit/pom.xml @@ -0,0 +1,125 @@ + + + 4.0.0 + + org.springframework.experimental + vanilla-rabbit + 0.0.1.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + + org.reactivestreams + reactive-streams + 1.0.3 + + + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-json + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.springframework.boot.experimental + spring-boot-thin-launcher + ${thin.version} + test + + + + + 1.8 + app.main.SampleApplication + 1.21 + 1.0.22.RELEASE + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + false + + + true + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + true + + + true + + + + + diff --git a/samples/vanilla-rabbit/src/main/java/app/main/Receiver.java b/samples/vanilla-rabbit/src/main/java/app/main/Receiver.java new file mode 100644 index 000000000..1df066e74 --- /dev/null +++ b/samples/vanilla-rabbit/src/main/java/app/main/Receiver.java @@ -0,0 +1,28 @@ +package app.main; + +import app.main.model.Foo; + +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.stereotype.Component; + +@Component +public class Receiver { + + private RabbitTemplate template; + + public Receiver(RabbitTemplate template) { + this.template = template; + } + + /** + * Send a message with empty routing key, JSON content and + * content_type=application/json in the properties. + */ + @RabbitListener(queues = "#{queue.name}") + public void receive(Foo message) { + System.out.println("Received <" + message + ">"); + template.convertAndSend(new Foo(message.getValue().toUpperCase())); + } + +} \ No newline at end of file diff --git a/samples/vanilla-rabbit/src/main/java/app/main/SampleApplication.java b/samples/vanilla-rabbit/src/main/java/app/main/SampleApplication.java new file mode 100644 index 000000000..28b774f86 --- /dev/null +++ b/samples/vanilla-rabbit/src/main/java/app/main/SampleApplication.java @@ -0,0 +1,63 @@ +package app.main; + +import app.main.model.Foo; + +import org.springframework.amqp.core.AnonymousQueue; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.support.converter.ClassMapper; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication(proxyBeanMethods = false) +public class SampleApplication { + + @Bean + Queue queue() { + return new AnonymousQueue(); + } + + @Bean + TopicExchange input() { + return new TopicExchange("input"); + } + + @Bean + TopicExchange output() { + return new TopicExchange("output"); + } + + @Bean + Binding binding(Queue queue, @Qualifier("input") TopicExchange exchange) { + return BindingBuilder.bind(queue).to(exchange).with("#"); + } + + + @Bean + Jackson2JsonMessageConverter jackson2JsonMessageConverter() { + Jackson2JsonMessageConverter converter = new Jackson2JsonMessageConverter(); + converter.setClassMapper(new ClassMapper() { + + @Override + public Class toClass(MessageProperties properties) { + return Foo.class; + } + + @Override + public void fromClass(Class clazz, MessageProperties properties) { + } + }); + return converter; + } + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} diff --git a/samples/vanilla-rabbit/src/main/java/app/main/model/Foo.java b/samples/vanilla-rabbit/src/main/java/app/main/model/Foo.java new file mode 100644 index 000000000..cefb6171e --- /dev/null +++ b/samples/vanilla-rabbit/src/main/java/app/main/model/Foo.java @@ -0,0 +1,23 @@ +package app.main.model; + +public class Foo { + private long id; + private String value; + public Foo() { + } + public Foo(String value) { + this.value = value; + } + public String getValue() { + return this.value; + } + public void setValue(String value) { + this.value = value; + } + @Override + public String toString() { + return String.format( + "Foo[id=%d, value='%s']", + id, value); + } +} diff --git a/samples/vanilla-rabbit/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-rabbit/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..1ae607c17 --- /dev/null +++ b/samples/vanilla-rabbit/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,12 @@ +[ + { + "name": "app.main.model.Foo", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "app.main.Receiver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] \ No newline at end of file diff --git a/samples/vanilla-rabbit/src/main/resources/application.properties b/samples/vanilla-rabbit/src/main/resources/application.properties new file mode 100644 index 000000000..ca58d9007 --- /dev/null +++ b/samples/vanilla-rabbit/src/main/resources/application.properties @@ -0,0 +1,2 @@ +spring.rabbitmq.template.exchange=output +spring.rabbitmq.template.routingKey= \ No newline at end of file diff --git a/samples/vanilla-rabbit/src/test/java/app/main/ClientApplication.java b/samples/vanilla-rabbit/src/test/java/app/main/ClientApplication.java new file mode 100644 index 000000000..b5c8d5464 --- /dev/null +++ b/samples/vanilla-rabbit/src/test/java/app/main/ClientApplication.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019-2019 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 app.main; + +import com.rabbitmq.client.impl.ClientVersion; + +/** + * @author Dave Syer + * + */ +public class ClientApplication { + + public static void main(String[] args) { + System.err.println(ClientVersion.VERSION); + } +} diff --git a/samples/vanilla-rabbit/src/test/java/app/main/SampleApplicationTests.java b/samples/vanilla-rabbit/src/test/java/app/main/SampleApplicationTests.java new file mode 100644 index 000000000..3f9fe5272 --- /dev/null +++ b/samples/vanilla-rabbit/src/test/java/app/main/SampleApplicationTests.java @@ -0,0 +1,36 @@ +/* + * Copyright 2018 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. + */ + +package app.main; + +import org.junit.jupiter.api.Test; + +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Dave Syer + * + */ +@SpringBootTest +@AutoConfigureWebTestClient +public class SampleApplicationTests { + + @Test + public void test() { + } + +} diff --git a/samples/vanilla-rabbit/src/test/resources/logback.xml b/samples/vanilla-rabbit/src/test/resources/logback.xml new file mode 100644 index 000000000..a6fe8de4a --- /dev/null +++ b/samples/vanilla-rabbit/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.jar b/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5fd4d5023f1463b5ba3970e33c460c1eb26d748d GIT binary patch literal 49502 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%0A$pwhjXzyA0ZzErH{1@M z22-6b1SQ!SMNyqj_7MXE2cwcEm)W)YwB)ji`3Y^5ABx--A11WB3mBQB<7K!~``j&@ z8PKJ^KSa>#M(rar$h}aBFuNI9sB5uAquDlzKW+hYB&WKf9i&+q$j5P;sz2u$f`uHS zaX8$!@N2b81<<0w<{CpXzQGqSZRpfVb3R%bjsw-Kl}2UH>}1M?MLA#ojYaagiYL!P z$_@7yOl~PbidzJ8yx{Jz9&4NS99(R5R&lf~X_{xjXj|tuvPgvzbyC}#ABy^+H+FN0 z8p5U!{kxOvdv3fr35|Kb`J(eXzo*GvF6`_5GI)&6EW}&OGp=!8n`W0mr_o~Xq-t?% z_pDDfIW#L^DmX?q#mA%Jz-f86KG`^7V|1zdA#4#<=}91g$#@J`gOqMu+7H&yMdNIt zp02(*8z*i{Zu;#S#uP#q!6oNjQzC|?>fgzorE(d+S#iv4$if+$-4$8&eo zuSZJ1>R2HJ^3T9dr{tn+#JMGv#x@&C$EZapW9)uhp0`rDsISKrv`~3j)08JZlP&}HwA!z^~-?Ma(x0_AS{@r z8!(Z}5d8+5f7`r3pw_a=Z`!0r6r4%OAGYBoq3T7^xI@9xG3prNo>`}k>@VAQk>(=DIy(szD&6@u?YVdC|pJLT@lx{=IZ; zIkO4)YWp*Dpp$`H$Ok#yf;yBmHvTb@)4j)jVNF-O?$nD25z7)I!cWQ|Yt zeS<_C{i|BS4HICD=}T(|)@vd(v!?P4t4>APo7`K5RJvcTpr_KgWeB~zMLknrKMgpx zyN-EI%es5e)FNho=}qGu$`98v(QDPUMUGrY4tq>?x$md>qgNO0@aAQLMLr8XD8z%; z2Osn1D>N^22w4Xb8{~fi^i~SthAo7%ZjNb)ikgj0_AsXqF_0+W6E_doOUi0uV6Lvg z98Xk#>IK|-YHx!XV64==b(nYKMEyqPF?D)yxE=~;LS?LI_0)|1!T3ZtLa?(qd|YlXdI-e$W z(3J*FbOe3cSXvDaTHU^Hqpf2i8aH+ZzqY$cFFIH;fxMtW^(AmiMkBtb9esujw?rte zoo&0%Afb~VBn6A1@R1!OFJ0)6)Fn72x{}7n z+b#5gMommvlyz7c@XE`{ zXj(%~zhQne`$UZ5#&JH0g={XdiEKUyUZwIMH1rZTl%r@(dsvBg5PwEk^<+f_Yd~a@ z%+u%0@?lPzTD>!bR(}RQoc>?JwI|dTEmoL`T?7B zYl^`d{9)rW)|4&_Uc3J=RW25@?ygT$C4l-nsr+B0>HjK~{|+nFYWkm77qP!iX}31a z^$Mj&DlEuh+s(y*%1DHpDT`(sv4|FUgw5IwR_k{lz0o=zIzuCNz|(LMNJwongUHy#|&`T5_TnHLo4d+5bE zo*yU%b=5~wR@CN3YB0To^mV?3SuD~%_?Q{LQ+U){I8r*?&}iWNtji=w&GuF9t~=Q2 z$1cFAw1BTAh23~s$Ht$w!S2!8I;ONwQnAJ;-P4$qOx-7&)dWgIoy-8{>qC8LE?LhJ zR-L4qCha@z*X+j|V<+C(v)-UZmK0CYB?5`xkI)g2KgKl-q&7(tjcrhp5ZaBma4wAd zn`{j>KNPG>Q$xr7zxX}iRo=M#@?>}?F`Sv+j6>G9tN!g@14LUf(YfA4e=z+4f zNpL4g?eJK`S${tcfA{wbn({8i+$wMaLhSJo`-Yp@G2i0Yq~@wdyFxoVH$w9{5Ql2t zFdKG?0$ zV7nmYC@PSsDhnELrvd8}+T=C6ZcR?`uapdWLc2eaww5vKtjQQgbvEr^)ga?IF;@1(?PAE8Xx5`Ej&qg|)5L}yQA1<^}Y zp7WZpk%}L9gMMyB^(mFrl&2Ng$@#Ox3@Z6r%eJ`sGDQbT0a9ruO`T|71C;oCFwTVT zaTnu)eVKURM`1QuvrBhj;1e>1TEZW54sKUfx0Z=N*;Jpdh~Aj-3WB zR|EYVGDxSvnjeA?xxGF41Wj?~loVahklw|zJ=v3pOEVZFJG^TvR z-tJN5m;wZp!E7=z;5J*Oaq%2bc|Jw!{|O+*sja+B(0D2_X`c2)nVkzP1S~LOj~xs!@>aN z3$K2^pW}@R-70K!X&s4DHHoV&BmGWTG4vi9P1H$JxmD|t_V{GlHZv(`yJ234IVuSr z~!;~#ublS8qdL8SJG@XRCwWhkZyg_EKH(sB2}QQSv4W}|CT0ntD_4Eyp519d1%yKvc33|`yW9QzeJ4*XLP7@l=td+bwxSL~jCf-ny)IDC^~u5s)E-y^FdtU?)hkN{82Y{Lo)bCWcBOx;Jbw;)Pg9bWQQTY-3RWehpok!>D>Sa2EcEOS@ua)#G3I+GxL_ra^92Y!}tMX zwAp*Fv-aAarn`ME7N#Uyim%ynre6u?KS15L#$#rKZSgLnXx;g8TP9suMpO055p278 z%o-6eT(3gdIVFN}Gb3k$zbTyrHYel1x6OxETsk&h0E?&}KUA4>2mi0len7~*;{Io~ znf+tX?|;&u^`Bk-KYtx6Rb6!y7F)kP<5OGX(;)+Re0Y;asCLP;3yO#p>BRy*>lC$}LiEEUGJHB!a=&3CddUu?Qw>{{zm)83wYRy%i}UV2s| z9e>ZXHzuMV#R1yJZato0-F|Jl_w2sUjAw@FzM=DxH}vM>dlB&bQ!>51aGc}&WAH`b z6M6iG$AyJIAJ7-c0+(;pf=2=!B=%yoM1i9r==Q+}CK3uW%##U1rP~mwjUb8PLsi8Q zq!aTLLYK4HQ$vN1sU;d3XW{oFA{u@1$tduWmdOqc(~AqWq+`V)G&?YOOwAK20x>{q zOgII2&A_FXPzVtgrD80Y5J+_SEmyUcdM2N%q);|ZF_m z)6PBcOcAAy3kN*`8ac%zPH3^61_zn6_2FT#NCOWYx>ezqZzCC;tzM%pJC^gFAFcTs ze6C3WE-a*=nt8tErPG9zfPRn$QHqB7aHe8x3w&rWT(0F54<2uBJDYtbB}y|@9V6T( zmM!t}T5SuwxyTCma14&l|yiQRw5Pn|OiDBkx z?4tUGrIVsC9zs=F{W>zl9XeknEc+~Mz7zCnefUPUF8iF?A)QJK8=84#-TLLxq?BTM z=VYjYW%TOhrBp>3D@K{vStlEUt%e{HRc=766AQ+s7V_F|1A!)P3?y*=gUgbZO;O39 zX*BC((-XbnoaRGxxhRQRVKCDG9|qC6?7TwCz{A{OZp$Wu(~0DFo(w^P3f>4gr8@P^ zl8`!vA=_fvwTZc%-Z42}m>Q;KQ~&v;ipZzbA2;}Peg*v}TlKRmU%4WNN<%qb!cLo= zoSx;XBrv4}ErykT!)z)Qar4o?(q6!mpWLNFe~Nz0S@yI{1)Lxt<0K=Q$~>*HH+Wbp zQ~fx0aup_lZb|e6*@IJOJjw~Ypiwdq69&Y2vthfGq6u1!Joy%;v;~4`B@B*S(}}i- zmZc^*aHOK(dd(geOKg)P+J4+*eThk;P@wRjvm}e)h|#EpsV9YoqqRW{)ABhRlvGA* zL$&k5w*_-X1ITCwXiH=)=5lzjxY5tQJTBrv<{dM7$98pdK%i;RGZtiJKaSGCji7w)aNrHu_9_IPGHS-mMN5AheTn_ia^YdunCzcp2ap8eI-RQEm zj(q7_CT)o|w_noPm@MVqIjv%H4Bdo6*9*!Zj)bLx!p9POp(`$dj1QW`V=;=|`Gx8QST=OnK5jlJX3!KBz>v7j$&5b5YrhIArRVL)1C^o{@DJ}*mk*s=< zDK{e2f%fG)mK_Mz*x@#ahOO)cQQ#VH+8Wef>NKWcu4J>PIc3iz8y6PwCmY|UQ(O3!B;HtsE&jvyv^XjL7Env5#i zH4-k5GzPr-%36#%+Hvw1*UiOIk3b7F^|1dPi!-i7C^ZWp~_KI%D!sGYb@@zXa?*{XfjZ~%Y^mT!kaK_>K8 z_jL78^ zS0eRdqZ0v~WWow1CE;vDBh#{w9R4JgB!})W9N{{D=p-RMnehZ#pH*ABzDP46ryZkt z4ek|LHS{CDhTTMQa3a5fO9OLg?y$+#Gi2}Fv>QD-+ZEQKX2Fv{jr~miXz1ZpPcXvJ zNvQT@kQbBz_Y4Kg)*`E2t;tPh5_7tSGvL-|-A`lgHX3uVG4jLev9>YCZUeNNzioL? z;OBD{z+=Gs3+*ph)#bO#7IHl|rOFfvpK%cF>W??Q!Nh&B@hByD&}g|>a?GJ4uhX3g zPJXKKAh&zWv&wITO66G{PuGLsxpWSqaadFsv>_vQt?LVslVob7wylsa+O`IYWySoO z$tw#v7=&7ZGZqS}N!c##5-bC%>ze*s0H9J%d|!JgE#uZ|k1_bAn*x(Y%r{c=(HLwNkPZOUT#@j4{YfG#@=49YJ{?7? zddbK}G-@Dod&^Vf`GOo)G|`n@kq?Z=o84x{889+?F*dQz(kr@9lQ-TXhGN`)^-Li1 zb}xO2W(FvB2)EA;%qAkHbDd&#h`iW06N1LYz%)9;A&A25joc!4x+4%D@w1R+doLs= z#@(A@oWJq?1*oT>$+4=V=UnuMvEk;IcEnp4kcC<_>x=Hw9~h+03Og7#DK(3y3ohIp z-gQ$-RQIJTx%0o@PDST|NW41VgAR?CH`Sj-OTS0)?Y*M_wo|92;Oz)aya`^I0@?S{ z<%^epAw!Tw(bvSmU_k~Im^%#|0`Xkcmxj;31jX2Gg?PbzdXp9Dg~P)PW+Xi%iWiCr zV-Vv9IR5guDS2lGV!lfTWxkD8w%yz=UB`2j2Zb0eg~arRA*Q6>`q=8#4&OC|L6O}8 z)!w(idG0yk-BF#~k@Avk>an9z_ibOP*Rb;db_PsakNWYdNoygT?yRG=+5>ud<6Vxhk?P9rk!+8?xMg!x5kD*f2XOd^`O3U zlO;ImEy0SYI_J05cMW{dk@%d@iZFCNhIVtOm8$viM>=zM+EKJG%c0)dZ0D$4*-psQ zW+Fq|WmbYkBh5|^-l$w-`Uy8#T#<+3=}z!(6RadEpFlr1f6OFuQ5sG735YicWaoYR z`wuEZT2dntHGC7G*Kzk$tsm?Fd25LTHJj?Zo2RH;9rW9WY1`;@t_O3NC};dayX;Ib zgq6afb4!50qL-o5%yzgcR-1Xm-l4SE!rE>o!L=E`Jeug(IoZ36piq6d)aek0AV)EJ zaha2uBM!>RkZHRN0#w07A=yf4(DBmy(IN6NdGe$?(7h?5H)*?(Li#GjB!M{nq@C3# z^y{4CK_XQKuO>(88PRb&&8LbRDW1Ib>gl6qu(7g}zSkf<8=nFPXE1~pvmOT3pn^sa z+6oK0Bn$TBMWYTmhJzk_6)$>>W)nF^N$ld9 z8f^Y^MLVz@5b}F0fZID^9%hRL#()Xw*%yhs&~|PK|MGI8zuO!f!FqbmX9icd zXU(JOCwac|Z|=Yr(>Q3)HsXl!^$8VSzsgI#)D2XkpZ2=WOBcFF!2&d;*nF%h0I!`mRHl$91jYzqtLfNHUoYzrMzjR)u zP_|Hti4^){G?Ge6L_T^zVdS@KHwtq^+*+aBNl=hVc6#KB-It()qb&8LhnVW9Yxn&S z&^s^u1OzB(d_ByXz=xm4cpJzNzV+Txh`~H(176n4RGlY6( zg?ed(a!J?4(oL}@UfBpgPL*)KrGtM_hMIdu!RywK@d!b-{YAY?(?w3yB@Fi3g|G)| zho%)<=%Q$Lo7S-BxEjTL;M74{y+`Q^Xg#j}VvF|Y>X7s+Ps~aqT--tJNd9U6;Ej&o zj@|!`{Xy90t_Zdb>+m8tCFJ@X(Y$mR>%)gv4Vt;oGr`idhQ7H1^L3v4<_2}-UoguorcscRfdgumUVa0mK7-Wm~#vbrnX9ro}@82q=9t;lM9nH<} zLL#=1L7*f+mQWfyFnETMi*fe8AI+gdY6BM7CkRS&i4$ZRv$v*=*`oo>TjZ84sYD&T zI!DgZ4ueeJKvjBAmHNu|A?R2>?p{kQCRy zRnGg@C%oB#-;H-o-n##G`wcPWhTviRCjB{?mR20|wE9Kn3m6(%Sf_oNXWP^b;dz7( zb{blETKwpl`AT#W7E6T|0*bl?%r{}-BYdwrn0zN(DZXM1~53hGjjP9xzr$p z>ZH?35!~7LHiD7yo7-zzH18eTSAZjW>7-q5TYzDvJ$$S$Z@q)h)ZnY(3YBl+_ZK~* zd6T1UEKdrzmv2xc>eFj2^eQPu;gqBdB@TLqWgPk|#WAS0c@!t08Ph)b>F3 zGP}9_Pfp;kelV05nUfnb%*Oa{h;3Yi^B5xyDM~1r@o%v#RYi-%EYfSYY&02eW#bGb zu8(H8i9zhyn%?kx5Txx^6 z2i}CK(HeQ_R2_u?PFp#6CK zjr}k8Cx#C?DFgP`uN<;}x*Gd$-JgG3J_i3s>fk@_Po}b|JNz=Dm+<{^51m=mO;n4B&azYm{>+VhB{iyxuW+j>w@>VHcJyoSBQi=hu0;p zPw3Aj?%Ai^UeD{ySPIqsf|v0L&f_fmE7oh(s|jwbkK5^AQ9F|;a5V}EdSE?fyxdgf zHTq!f0;+-V{0oF+l_~>rMGk?f~m^wDXlxqt1@+)6Zv?BNR$+%$i z*NF93f}~4d9H2C7@?IibyqUtLL!XZW2ap4fkkxMqDZuZ>`+AfWJQ%~O2WR}NoA=OP zieg@q!mP z?=qU=EE6L0_UpzXt0qwX2tF~}c|;`#MUY2TMz6k({hpkiSz>Dxt*4-PtkAdAA*0hn zk~CK6#V=*^m5 zg$tB6rSO-=9l>GAl^DjJBHdk0wD0(L!OrcZ?qmtYbl+}s(@rtE-O=RTx*1cZq~u~5 zQPVt(IB=*?Pm;Le%#i1SFxHY|>=Y$^RF-FGAUSkBpn`|+p!4RHyv-Q(XgZ5Xg5W}J z8RcT?+4FdVQ>z~9kP5By8eM95f_LDnsnA%K;i6`OpcuJS=^n|6nH-B2EhH=dLbO@Z zuw=Ug>7gsu33`Pzy3Lji0x8OCH={?VRqFEi;@oDIS<*?dG@9X1*tlYCm4YUIMhyfo zJ~=K@-X$D z<-4dH<-5o#yMj%f@U{nfWYVdrREJ}_o4&|c*_+M6gk z-Up9-i~jM-bwR;Bf0&C5wteli>r7ZjGi+mHk3aC4mS5 zPC^{w+G%menlWun+&<#i&DJ41thvk;OKZEB`S%sZ6 zzYpO2x_Ce@fa0LuIeC=7gRHN#os!MQ7h}m9k3@u68K2$&;_mSe2`>uvV<`RgC)TKX z`J}&Kb%*f{Oznj$%-QafB}Zb$Pi%@D&^ZTcgJ0+Bk6-iOJ-P|Q10)5ie2u0JzKb2r z2C@{f?ZBcPw5%h&aKG+6%Qvhw(t1Y{hZ82YE4(Tlk`2VCgE&1x;AUt+5U*$%>P|iWLeb_PJL!VX=b4#>#QM;TGjFHBNRy+d{v>2cVXFyqaLd300 zFHWrc8lB1KSOH3dkJClJ%A5oE^31WrQZ3^-3`Zk?1GqoV7Wr62=V9C=(;#R zhzXAT03)d z9OdZ|;CjSnqQeqF-CUNR=x9x76JYnpr|T+6u#$y=7cMVG72k4f*BJIG>l1NNvyv6NQzr4U`r;= z&%W1Ri2sI5p|8%q5~zM-AMptHj_eX7FzJN7t(%+2dA)efyFbePBsClxY_yMqWbEdT z+jm?SZgH3mCzU?e^psnyd8UK zfZ$^_^}C1WYB1-$m4qwT@#=wsAq$9Xj=%IRvc#V?1azEi|RSc;M zQn;3%Gjk3D)R+3`gZplB>Pt;g?#EiwRzxON;% z#P5IK*YAh1Md<$o21R}j^8Y#t#`fP`nErnb@&CkI{`XNXulcVIXwLcS%VE4i4-!8a zpj-q)#TqXkFg&z4G9pG45A-$B_Lfacr)H85ge*yqTLAb(oY1$6Xu7Rc%^aVOmzsKd z=WEXA40~hm@7FKD9t14nSRt)m0XWkP1YbAE009nIupf`md=v&J;C}estaY0%^Z;;lf>5AF-y%Xf1QEK(}4n+ zhKsTx^bQSpwM=UWd3WRcpEQfw>P%zuhLeEdY}s%cGitMZa14Ui*Mzm%=(7<#b2gHmJ?kdeymT7H+Z8k8tgd zp-dhC)R!P!)w(n%RgOi%^)LGZX)yxC%@f@d4x@IRbq{elrCHyIuphEE6qd6l6O`;B zi0WQg;j`hcu51uYTBSSYNvY{Lkn$iu=Ae0g6o1cSTRwXmEvNcNI zv;)Z_?g>?aG`Zp}*gY8%LGI}{>J#`x;v=*ykuY@z2Erz>@b*)tMp2>=C20MI8|{Z2 z9hbyDJ7d#MdWK&fyZB>Jdm!#x_uRw%>`OuM!&QMim}baa76{L|VAuq%1UpXVHsClm zPD4}hjj{lj`)aaD;x|PJ9v@?8gZ!t5hER6!b~HJ_l9P|(h&R6js3mAfrC|c+fcH^1 zPF*w*_~+k%_~6|eE;-x}zc%qi-D-UpTcAg|5@FCEbYw6FhECLo+mVn^>@s-RqkhuDbDmM~lo<4sa`|9|$AltN_;g>$|B}Qs zpWVSnKNq69{}?|I`EOT~owb>vzQg|?@OEL`xKtkxLeMnWZ@ejqjJ%orYIs!jq3 zTfqdNelN8sLy2|MAkv`bxx`RN?4Dq{EIvjMbjI57d*`pO?Ns{7jxNsbUp=rF$GCut z7#7Dm#Gvh}E8~2Tyhj2reA%=ji|G6yr%@QV{(90cE{JYOW$0F|2MO+TM^`cAu$B7s zmBV^{IqUIbw5~muv}st`dDdIxSU@Eb>xf3$qwEcg;H+vp1^ArN@A)RtQ4hrid2B{9 zb~pG8?SC3#xctpJXWRGXt=cx6Cw!IqoJrK)kuLL&`UYYB{R6Dw)k9nKy>R#q_X|V* z%zVsST$=d(HozVBc|=9<175^~M$v$hL9azT^)TL7BIA#qt>N2^iWvMQgt;!YZt~cv zn!x^OB!3mOVj>^^{mloGiJhLI4qy3Vt-148>9j~d8coH)q|Cg5P89Xj>>hjtzq5iT z%go41Nhi}x7ZztTWj|deVpj>Oc#IrI{NxIm;qhnuNlvNZ0}d=DVa}=H0}Vi-I+wKK z*1uD=0_)b-!9S^5#(%_>3jcS-mv^;yFtq$1)!wGk2QP%=EbpoW++nvbFgbun1Eqri z<%yp)iPo|>^$*IHm@*O74Jve%nSmDeNGrZ&)N9 z)1rSz4ib+_{4ss2rSXRiDy zgh(descvk^&W|y)Oj#V@#)C658!**J#=ckpxGniX#zs0tA~NG>E#Hn3Q3wdKBfMG& zK}2y#|FLt}E`UQ6t3jK#G&e22bMBc3=C)LyqU706frdCAqa;~Q0L5)KJ4?@h*FFu4 z!s=hOC;G?Q)BRKJ1q_XJ9W5LLejp1L*187&5Bo4Of)k>T=WpQl3v#4iX$574fW`p+ z3m}r-F8Gjv1m3yTia=+2An1+E&psbXKjH2{<1xMb37`|D<%7c`0`~m0r>AQD^%nUJ`%PxS>)*{i zg?VHw)ju!$@$>xGszUyM_BsCF3*%>rxVZ8vrYB?PvDBBHQWz04T&UpxKU7{ zrb~8R4W>e)){FrKo^O5ts8O^r^t70=!se(2-(8&aTdaFU2;SR=dyECLBp|MVU@JIt z)z$TAHMKRnyX*5;O<*xm+(>Fo41G;Tk0w01ilh#uFJa{teQne`QCOHZp`&du5gkAWr@9Ywz%@P@KB0bD{lXo7PmrPC%J!A z%orlB>F}qRa$`XC2Ai_4L56#h2GWm;>sScPxhMO5a*guk2 z+56H}PZnq-sxASPn!B~W#8B1W=OQPf-lEbhOh%>%{AND;w%w;t<8%a%HNk`LQ0GpT z6au2l)=Brql2Fq{Kw316jHdW-WF<{46(Xad0uxi%3aEARVi*dKaR^jjW)$<$7QEiF z0uK-~dQ@|hxT5M|t$pBl+9IJig2o;?4>qY%<|sZ4Rk0Dc{ud;zd`g$&UcwLjY))aV z4jh&lc(;hjQaWB)K9EB@b^I)LQ~N_;SFEEWA&}`)g!E7-wzF%J8)yZaSOeR=igBiM zaU=T>5*oyz3jYaqv-RSC;r$%d^Z(cbLGwTQiT+3KCMt*OBOD@rPZ}8;)1_*l<5aBp zjl{A?HiE$Y6$NWUgPY(x@k^9)A|CC#nqZ?B&q-ceGE;Y7F{@0{lQuPnsj0~YX(VoZ zdJ})6X8821kH4_0vt$gocDeSve(SuROm_bM98&+q72$1m(x?A;;)@TWyuVXQV!{#( z41CN;(vq_a|56Yny*sb>5`lt+>?dvF0++3L!wQ_eJmXi)z_1UAmNi80_bG^|J$GZs zK^|0X@8jq9pyPt$dpiWWAG)mNg7X_BME=&UYoq>nc0gtk_YoXNb5hYb!hG ztf(P(6Bcy6`wroiv-5NLLjVBx&|;W6WwKMmB+ph%7$AJfV95||OktlFlTMqdKP0i#Y*rj`(XeYUz=adk`3hA(LvO`y z|0%R3GMWC#x}RbCNX_Cf;_wEOS}%lqj#-CXQDIpi8Qis%Radz>q0vjbY&8DdR>jXU zmvR%au!=9lMN?P=hzQpNGOJRw?Cn8@B@kEp4r5$bgdM0?Fdua~*H~mGTf}17rZog% z!Kj#>m=l>Po$A`_fcT-pHy*aya+n%rXmG0CJ6a{nF%>TfyzKC2Dit7a;!8r;X^G$~ zS03MClV}lI)S^Py2I2rLnpjR64L!#Fl!mCP0td}~3GFB3?F31>5JCwIC zC~8VAun2Z}@%MZ{PlIWpU@CJ06F_<61le-_Ws+FSmJ@j>XyyV(BH@K!JRR^~iGjAh zQ+NnRD1C)ttcyijf*{xky2tyhTpJvac8m%=FR-LL@s>rN`?kMDGf2yMliwkYj= zwEEJ0wlFp%TmE6|fiti_^wVrxJ#gh7z@f0+P!kS>c>;BHH)N`PW0JHTqA?B~fz6H+ zdQq>iwU2Kne+4kR2e~l2`>(-^qqujX*@|w7k>s=e)Y-lwoI{$Tx_2}&y$9LZzKG-w z{TH06d?a9;01ze%EvqDCEt;qAaOYdf@X)zT)ScQs**7gQ**A5+o9p#P*X5~lMpNl2 z6p=Ecy7#f++P2sk;I2Nd`w-!5Y^3QHV0RVy2<55pqQ z&Q&b+JIKTf&6N(UjwrECT(BwKhkdpc#(Aq= zyG*N2frC~4B2Ko7O)bOHP8(}XKc;_(GP&+{?#dJ;Y$YXT$y<%YZmc>C?Sik?i?6E1 zk~VKGMLlNws0d#wk-11tBrAf?Tbes4F)oqxr_*7R-?Yn4IlyyP_ce6(J&tXSFI~P^ zYG1K1&Y@OY%nE}Gsa8~iq!!=l4a+yi7?Rxi#owl|2CnVfey<;AkI<2^CN^r`;-)ob zX7Ccao0G6Ic0ENcm7#3(8Y>}hb9aL6Gi?llW(Kss_CW07Z*0rgVhbod7+2-z3EC%( zq7QLJy|>bn^fyDVwISg;I%*4-lpnL5wLoe=B5sV^!Vdseg%7piW`#>KU*HD}MZ&J=jCFG;)9zqX;~A15Xsg;+mAtJruykiiD4Qc5$;lWT@^-j>F$$|0*{U zmrM6Kwy7I0>uJ&DC#8>dW7&)!1!_uGQ@Mvr)n^bH?_w|*J_E0?B{C&x%7+%$9&Umb zMv=?f8jwV=X`(6MfQLkyXGt_A~#T^(h~B7+v?~%F6k&ziM^m_Cqb!a zf0y+(L*8N@-&FfWsxPx%V97(F{QW`L&>2NJyB_}HBTWa|xRs*TT-y}_qovhF=%OCJ zf)sDf8#yYtG3ySQ*(qqz9dXI;CfS6yLi>4H9w9ii-!j5NwHL>oEN83>IsEP+V_1~u z`?}q?(o8RjDY5V?z9HC@t*0V_hFqA|HyZ8k)T!UJQ`KEKMLlNlIq<$2s!x;)o#SW0?w*zVYU?yc(v(2qyZg z0(^T!7Qzhpm)`?PLS7z|(>s+ZUO?_>f0y8LjB9{7he}@4-%l99L!vhyLW=yQr!);4vCSd-wC1QX-%H=?#UM-D_Wg8t3W z0*rY0Q4xwb5i(lBSOs^u(IgRSP$j!PkhbcIr^rh}e})V_kU5jW{q)m0CALP$`wKi& z?444cDxl;D;SqSw0^h%eA6Ro@BhxmD!}qpGb6OxRi6;iFai!)ctW|gmF3jQz2*O}Z z*TPvZAxFr1-Dd!53U_WQMQh$aauyVf;O60e>&G;Mg83(TOZt!6;s2KT{}By>k&-_m zA1YA0q3ID6fx`!qxy=@dYO@Rn%rEb~7P_%;Dxvl(WAfiJUtti0?~ah#_1`K#A}P2n z7^D~GQL#`hC}2w`btD`i%)VBWnn*jWF=d!kI*6T5-wBdsT)$EZD=mrn&EhxJQ^3>1 zbLeDA3&BIDAv=kWsp0t6>a3lITA;khMX^(B8Ecb^U%P-|RNGB@XLq*Q5a zR9aZ8RFNDYvD`dcva-5ti*`CcV%ltLG;emYG)5Hvo^Boe6!Fu0ekZ(k<<5G3_4>Mg z-?ILGT9yB`Gy?Cnu(PO#(bsKyf9>@F_MJQFZFaBE?dA7x40K@HNwA20g&JE&q z6&$MUcmsL)Sq;;@a9!*!?ct(XynVCJutm{pZ5w3Xci1lQ!9oB`xCdL! z6i6sX5X8iljX<8L4KC)P_hyjfBo3W=8BfQ5^inG|_NhXI*k)fvrDRq;Mtl#IdM%t^ zo(9yQnnQj}I{C__YBGYykMvG(5)bL%7>X@vm&+vnDMvZ(QMVC;#;@DZ9#6!r74JA`7phVA#`JE` z>BU^K@B>jj8Maz2m^>t$!%J^m)e|Ylem4L>e=OHtOVBCDy{0or$Np^VjdNl=g3xT8 zqsE*&O{Q9{>LhP;F2vpR<1t@fO4^Fbd{cO753U@l zLFAlS*(cze1w03?ZyLxG9S&n_udo?=8ddzgt#cv5fKd+uyogyl;44IK1&z^wj=!YK zzUD&kgK%`pt9A4nks?WMImECKCAt*xUXcPbo9e1&PmWU$X9~!}HO|j@r(`+=V^^Lc zcLMKF*Yj`EaS|pmb1uaDbkZvx6m%4{=z+MdgTuv?mT=4T&n?h7T_tQNFYhz$`~(DF zx4T%9nS-@(gWPm3?tZwJIpHDGWzAJ__zZKP;Hw>~%&n=s$Pn?6CaJ>bJzY?o)(O#~ z1fxWpkgP7ukZGyitR1C364Jp*?#{WzBom;9o=XrY;V#_Y5@5*}T5v*hcW#I;Sb)H; z6^g4&{fOcGP0zWCURc5J$ExdSY5s?r-^r#;|BS)8NjQH2--6b}!Q-Aa$mx_pNnz4q z(1_zCdqOu|4b4oo+-*jjTTV_j3WmL9=u`0(l@>00B5Vg?4f?fqwWRCX*2JwC(Yd+i z5A-Rm0r4e~4ceSJnEmWF6Nk>Q;(7sYyQ<-CgPa1fO8m6_pu=Maf0e2hd92Q#i7j?U z-VR;%F~r=@Xs>J2`Nx))UK=X`Shhg3AWzbwE<#%hM+KSQ)y~F!~7j*2}qu zgT9Z6kE4Z|n9Leb=N0%JnFI$AeNrV+!>E(WT7dyOjN~44BhNVL4(%Eo(1JGjS^)Oc zjSPsu`3wT8k`$>Na;G3pMU(9;+ov}PpiRt6*)WNMy(rEUak-14^(K`73yJ1#LZna? zS)ypsH=xt_ z1V%Pk;E@JqJeE1&xI}|JylZJSsu+mw#r=)G*5DBGv*`Q|1AC+!MW979QEZ{H5*8ZW z_U8EI1(M1LDjG^#yy~(OGH)?SdmR~=ma_^2Q#k>)`v#$t=~Ih|79!ZutXQTK^S&w` z1)ONotPDL(cz!_@bFBBOo6W@;7Zz--d9JaOs{)ss4P|Mr%>FaiMR=(fn-Y3SA->6~ zp`5h}dOcY_YfweZB*^el7qqa$&_r-Lg-I+9~U z`JxVCD<$VmoiR$g^3dU%7Sij)XYi*?$#ihSxCBHGOaRRr|Lo9+E}O~M>I}tnokI`}F32Aty#b8rpABEKl|B;*o8ge^^)Kyk z0!(>gFV=c)Q2Y%>gz+sa3xYTUy_X`rK5ca{{erC9WJ3EPKG{|Nng_-78kAD{oh_=K zn*wopK3cG}MBJf%6=}9YouD;zyWbjRt%A#pWc1zb3@FB`_Q~~UI!uvse(FQfl zUt=Qy2DSjwpzAUJ048~^;@Yo{C56R_8nZEeF}vm)0xoYe0y|tYI!>Y(d}mSro0`z; zeb6Eg*(a2{5Ypj8S$-_~L)+IlozZn|Iak`$jQKd63hldhts0=m>k~HC&`@|~;XaG6 zLVxC))8>^?13P*mV#ydlkC0V6AWK(BjWpqu| zbh7#bkKuL<kv5;Emm4zkF;X>rfbzAc7!Z)i};f=*bypYUD zho5-B5n;)FP(nzq8FG3TH?7l0vS{G}G9@~zxY>CqbX^mb$|JncS3I_2RD@?I9bz>LbX13A0N_LQmd(!3AxqmR_;3bJavc81%v z)Q~pDm0d1VrVe~>X?GOUOz94e6Nbt|fe6(S@cN64Gy6{i*TPukTmfvgPR>+qe>)@w z8mS6=rvR0~cqVfEWFsL|kZ3t~m-iV}va(IjJ;Hh4R9uISa6;@9d{D+7CwskGx!7MGZ6|rdE_I{cMD}-` zoi0%doDSznN-Evavf!_d@UNJt*Fl;hNrnVT2Fal8iBh(LU^l>8I1%x!q=6A@zO6O} zs0R@~z(6E;t~6L7tclb6A}zwwIvS;W`?F>>P)INWt6N9r4JbH*;&^6B!lHNAY+v3R zwCVoTTSL`1XtRZ_9vWH*(HcV?PImcNBOtbC4{U(v-HA~xMdpP8<);Xv0y_e1i%t|f zdyL`MtgjoC^Z-wGt@&6(9Wx>;qYcYwopK7H4iejT?T|>BSm)-fV&7yB;ANW4ZRzzc z?^;uh#-bDq@QjjBiIf-00TSw~)V;r?BHNEpDb(dLsJ_Z!zT7<{oC-V^NTEs|MeD0- zzuH~jmz>@&JaYIW>X&?~S>~+R!;wQOq|+{tI&#vV^n%|7ksh!vXzONlSb4zc!X;}> zMaUjix==sr4oMiHxL@~MPL%PrMzU{DPuz`9zWln9XnqKqNo3TZc;22OZ{ zy(90FLmd!qHIv!b-q){c(0@VYnzE(k5#rf~N5m{u-X za_J$`vM`7Bh@_`N%&n~35!O^m^pyWGR65?W@EH_fG}veT4I>@L72iny$1yuwBopv> zsSxe4Htw2+2f`M-+7|iva$OjEp*e=6r{J`{W_IyMTo#x0Yayp+V8z~17Hx&~6G%t? zN=#7bc$BWFl&qzMvU^iRl>Rvj(_`fR9T%ZBYX1?fg((%9FgbGrBl_7^rRQW9GA*@E zLN~c4F@W|oNmH$kHZ)4U$u(P4S;GSPDy671d;6L8z}?RfSb0PHN)PsKViOm_PLB-7 z+-+jjpC&oGWj(BQ{|L#DFOC3+-%fvGOOx^u^Ysxsq)Ox4^;}rM$!;(?`m@wtkXb~%u$Zx% za#IBD9hq=no-2H90jB}1^>TfWp)=Sb1v9w#UAHvYbn1PpHFbB+hwSXWK(ta=^8VN< z^j!PhT^ZXf#;?$ZWkn?(vJ20u-_SsGO1os)z;s=hI)d6iN-4mC9>EtcU@Mybflo@| z82lRHB)FEu4k@P9W+a)>t{^Jl;)gL&tWZBy(gWmfXX8XiUdnU>LtbceRd2RogiprV zK3KHRpSd5n#Hy5wQ!-Fg;{(9?K%pRuAEZwPR-E)JGeljq?MUmP=K$zkEO46*td&DL z%C4c|+^C204zq3rsTdE?%Y;lc1vKitClZ79P)GU-k`VCL5(kX_>5D{)C18r$^duj) zab$~pZ#$FLi^ihhytr80x6p2DsA3IsHPguaQ&s4izcL;7qGj1rPQM)4uc!I=d^j7S zs{`eqUlX0}s<8@_Iij-NBLD<2BE3VJ&k4Z6H;z?!7!7-XeeC-aX{Tl6ml!93m*cFJ z#Z5Q7fr}UC|2wXN*{|KEWPZ(V^*agnsVlrYkAd651IAl&yHxt9OnMCJBht5xn*lR2&NabYN zSWC^|d16K9!d@LjLiX4uEhz;%>2G#@i;bdI;t=8bK>y@P)WT!mDr~z}pG- zRg0M$Qpz0mbKF!xENTw8!Wwu{`9|04Gou}nTQ_L@`rl58B6UT^4~-?*}V`fYfKSaDIH zavlsK6XsL9-WmdH$C72oMpwJp)?;)Z4K6Es0B$SXP*QhM!gvpdUyI?}p1c2yYhY~r z_VvRqI~hi$_97U@cE5#Z{Zhy&EqB*`vAMpf?Ya?h{;uuk-}E1T!ah4kx_Q*9mOjl* zv62c1x-eMCSfQ*b3b|P6*~#_2>fN2y=iJQy-I$q_TIV>AHLGvxzY#v#{w}OBR>mny zZ+4AXVq%F7d*h&{U!c8&&KUXS@X->Bu@pTF71|eeQVYw8ns~h`7|n?)2@d35c_1Jn zeG)5*kFZ<}MejgYN(?7Nw?Mod)k5v*wm{$@osr)Ywv-QvXpeI;3Qku^T}zo`go?co z|65!$tORilITCe4GfhNoqaj~NtO|@obiA%Tub@&qQ)*Sn14oz#=<2osGcxe*+@PL< zyx=_nR&*Un8g$Iu#el1FV8xS6kKlqt6Q_nLmsoyCCicctlpM=xVMApO3V7u00mxNJ zn8H5H7~1cY0)_}KJSfc2QSG+HDoQlkX^Iwi_%Qb4&1XPlDw$%cwf-dlhzTK+<_D-) z&P@=34aLr)@%x%0WcLNFBZ4im4biAYc zX48#WytT#YP@@jEfGgaR&J#HZzJa@HjxyMYHe{pLPnxkn;~Nj*Rk*wS5*frI0o^@# z&G3U*-hF=Y_v1Euf&ZeY$+hsoi~%M`iq}OU5nnKjI6qCo7#tk{_f3pIO(8(pMmgCr#+;(8d(-5n@oY{gBKSFB;sfY zEGd8%M6}wgw88w$*dURSw+YzI2N!gycd}~V$*T@AlPt*-f=web80-YsRGL; zIurEoITNgt(oy6p0G%)TAq})jmI~qDOTd#8SWUAuE(*k}kk&NIGfR#?MWZ&@WgOiL z>$#C7>im5ft}NgVUz#o-;GS~3h`u>vuPTQ6J_?slXE&+uSm7V8X2xqGN*g32wQVF? z60uDVd}|BtzXW}IHl+O9$Y${gL@oN<={bc5POfF*UaM4*ulAX=jeCFG9716kCF{ap z+Aa!D*;gIqFWp_D0@7TOln&`G=|&m}X{5WP1i2vScNypR7x`wGaTX8H zJ@~rx)5+w$k^uMixVE%C0WLCO~Q+tBA;H0@eFG) z9eC{^DN&Wg*!QSPZ&6UQTXd8o&~Nom);LFsVoC&=vbu|xNN`s-1=AH*8)z4To#%#y zdd$@UB#=RyuU6;>-mgB-YAnr|4VG~L%5Zu?2?e8cV@hX1%$C z-Y!`@^OUFtA7Pe=$M(LJiXU=J1!QUEtKOP0NQ3X zL0EH2;5m@t@SxuG%G+4`P52~ZYSYtf<5_!E_05F>!Og3NVhP<3((hbndMVWA>MlDv zn$&G-7+NQ3%TTa+SwC{12rdHZ(>d@r=%m6}QzK^c#Jf1mYV4ihwfN65H)@P8$MxDc zTjl)d2R0#MAxtC@z=02~@CN4)F3cc@}c$eNk#9s}m0 zCQU1m>8KltX-7??Rz`KAa9O`78vwc z96b`^On^}8Uq2X$nJstj(oDH3I)|mIuLz zwkCtM6CN9f((dN*4jqG4{_r(Wh z2u?7~;PfTgKZy`BNs+soV7l`vUoj0Zs59#tk&2GGS#}^vM~n9_o1()DH&=e+1J8g6 z?KqAZE{5+wu z^h1JTDHbTO>mUG#C?;6@CZ1@94=<&=#wE65{;Up>sTq@gJ?nsNSa6zE7ZoR|eSK`& ziwVJeio-qK&1`}djVaTPBHAtX-iedlv!W}@HqzoQ&gu~oM(#ZleNhagi2S^z0$`*2 zvXv*_l*3vp7N$6SniJ6keA;%N);Z;F2X+yzHXEKK>|!l-K+oBIB9Rg(r?T)}`0nwz zW>J5H2T!yBBQv!CV3wS!?e?ao$JZGHB3>?^p;I0oEq1rFbn-K-z1;UX^Zco(t|y{F z&aaht8|ducgto&gzsFOSGgDA6d{NN+DwNR7IvD2_ztxv{`PTvRQAD{R>ii;bqI6H$ zi~7*gkXL6sk*D( zRfRn^T)TGZOa5H8)%KL|b$feS+tmm`x=ir7xA_SFtXdrfwMW*l6LlqDsdN9czC4LZ zxQ1hx2G%}RlTH8PFjxmCx{XLh9X)5F)BD@x`3Yu(w&|MQ@Wn))MQ5P40oe6lq zj6&YQ)Y$fsl?yoMn2DRKmBXL&;#5@wIec)ey+_r)wLWKQ$%Nl|=)1S>2v2Br1GB0z z{26J4KqT_fthh6KL4A_nUGh|M?rQeB3d2M>f>?eF=%>&KBi ztb~177I8YO@8HV-(xw2pP4vCgNM_ODMc*XT)Vb84bZ$(aRZCi0SD4Vb5~0yzn-7uD z8&6`h4|PfG#@4O=sM;eev2gieyH}I*Rnq8!MO>k8@S&aMNX9c!hpUjKeRDUN*M<4& z`yP541rMR2;EXAYLf51%0hfLwoLO*VT(v!KEHyrD(8{a*@p_=xOtG6Ck0QfS>k&u_69rGu_Jt&YG97L`S7&3_{l%EQ)VAjX z2UV7D9)#I1Jv#8Fd6X+dOxjZTXFW0vpAv0)rZ!Ck6!Fz&&ZCezKS|5 z__!pv3>!#(zZ}MQfb=Bz4!aBypX`XnE#6B?yfTCmP8;tZVe#%QC2|cSbs$Q7mx9Wk zrhgq}S`lflHu@AX)_|0m0Dgy%FGt|ZP!H;(BN8Ff)p``6P$lT2Z4~=eFDFmYJt6Yd zs+IG46y)X4Cg=VU%>5u$6hq|9hlX$~MPeX{3SWik%ZBMETV^`}7l|$=T9oPv=>MfAuVpVuT?xQI-5MnhAwB~WKF3p#jb^%x)hgQ5w zEYy^HY%m(3qgTb0>_xhyGy49WgkavN*iwr9){qxmZ}0h)}ji`R&Z0sEAcs4@JVrXS$uNXI67&^So5DE z_wSSV)|hizP*Za+cCTn0^tCx`&1B`kM^^O^qqM)Or4WgFyEKhu_AWCV(8q?&7iiv8?d=$)b z1MCx)Px;%)v~QO*(UKzoMpj-f68L&<9G&jy%k26a6l~xWa27d=0zy9Y?Knv>uTy3B z#R4dYL0;(wG{B!VU<) zL0dQ}cE7}kSnh!@UA2Nn@KkO8%G$oaXs^?*bXW`@IS`edO zPr)lZK}u7D_99TTzwi<#blDq<%z2HzF#{9rVJal40r))tDNA4@UK9YkbOz5og)RphDfLoH8TaTJ5@i1x@Ntowsmz3c5mldGTpqbAC8z+-y z3YUgK2;tdm95YQ4$o=gR_I;ot|JG0jq~!w!JryDgGKTgLd#SK)h0Z1kh907bO~U(% zT6jiFnX@TWSv@xNo`&z|2;9Rf1$ArDtzSTk!BFYr;&ymtj4Bt1vK|q*ut&Efy?Wd; zk}_qM;ziWm-`?rC{al#%^wRcw6wOCC6Gv|Oa7>zIK{tOroHE9p3-q;DwTZq9(y|SP zOB|hi75t%%z@ZErp@owZiI?H$xHMR7h2k#XwmQmT>7xof5gx@XC`fVWVA~cioSE&K zoAYasmf;04$arj zg1&eL7=I?+WRf^o3qFw^#Y?d9v=-_zeL94x2|usB_;~yo&#*;J>I2Yf+qzIM|Bzwn zf!lXOXQspLmvN-cJ7Fy^Z9K-=NwWY4W8RL-q!b82mgurWTar+^3SwpU*Swg_MY|-s469h*lM(kJ74z%e#v1B%~p6k+k`Zr4M;9Y)5 zrQ#%yC8mb5QdUfV#)WRwxc!2-9CA{=B zX*|`We_=f<%xhLdJy`#KbR#+lj|R6pJG@ZTcZtr=Ff(n00MTQyi<~xkl6_QIxuYG4 zAn6QsfWJSaT0)kmDQ#9{(H8{k;(F3zbIvl5oA9MZn}6VxAW4VEuDJQJ_tvW3^8<=i zgp3DjuXDefv#|&0?0j(&4lc6i2+%kQ@a&fm9)1GxAuGZrRy#lIac(Y6!xvAGHrz|( z)4AuuEkq7`w4@FDUqah3+{y7xTbMo!P#&kbRy-1zFRXRTL}Q62x?q@Ltwnr zqyF|*{ZdFu!MG|}fKcf)Jk0y#Qk3t&@IZLWry+1U{!CF4(R_B8fZnVnvN#y`yJk&8 z5o|-I$t$7DEs@z0(ie7=MpaKrn9UfAR;(N*a)J1eej0*KIXkIFx?K6bYtjN0RG<87MN5Ph zVo*0Xd;_STda7fc?U{jG%U9FOdo7NOGFCBEBwR&j;4Q&)m*JVsL7mSZgs;+{K}z*uLldQDk~pDMMpTRSMayDpW3jXcP-aFaK4SRwhOg43SAApaG6v=#1q zJc}I6RObkNMZVE@gW2>|4+xVVmeNu`#F_MzWq24w2tz{n%bb;&u07(#9!N=hc`@qKm@EtkN&lDJr;L zvk}HQSsd&o7#d_Yb%Py=9{clqy|F19S81|cMmz<+n!5J&3Ck5~Y}=}arb30r5}^V2 zwD^K-=syNKf8H+4r==Oz7M~|D34$w9WiTg+r6;uognB=hj*}U3^eWO|j0up?kWWmA zbEER8t!`eQ+ApRkQmsrzPN32!_e#P_Bfh6aGOTD3gOGBH=Ob&R+Zi30Sc%Aea9H~7 zEB4j%17ym*rkGd>UA_HLZ^3@`9`Eu;NC;;HEL3An;iEgR+j-;5@XGL#4o02(SG@?! zmNW>y;+PQTA_i>3r%-PIQ`x*!@b_24mk5(I-0 zzIJW*ZBIgn{B;FFhh;m=5q`WK>P;)21@!H0ON)E1P2mW93!PsfiMK!~#1#~LLfyQC z=}TF_5|H{5J7GF~A2vvJiJs7KH5%w}$Y@iz%2sMQefiYTC#VW!XWSEusTc6L|ImO) zFuc>MCylPg;Rn_By}7kLshEh9A0guK0m6Y_KKvx}_MX5@{;8^|M4lHz59q-^n>s3N%P-)wu*Apy1c*uY%ls6{?1UoxSMsVN7r!vmY$4U1ZpCFZp zSB*$nRK#ut<0W7!D`6u+bGR?I9e<3Zx6iW5FM1YNJ5roEjQwT4gD$elG@b7S?XgGj z6?8Gv(sGLkkFv-Bz!vs_FSNi1>W-{uoLZyfxL5}8Z{yqaEK9mx*?8EyKbB&|oe3nO z8VPv6K-BGik_oh;MUxzP=SHYz+sWoU*_Pc|ZAp%rEG2OgkyA{O@|sV48aj}*$c=#ReFzE9^##pCm4G| z2ExX>|7BshOX&F%0r(Syy*@UGUX!?ky}6Zz8#t5q|1GZL;`G!$N@DbUPo4((w_%ge zvSuqV7dVNPK^Ue9v@t}A{2cJ=Vt!H6_jWRDXA_0fHLnagK+aM{WcrW(C(d1S@nS3RlL zUYh7&54coZVswV%&><$802)Ds6(5Ty!)=(|2PPPUY}b*5H@uVe7@L=Qb0@q9St`u+ zN_!X`!fP90I@Pzd3+=S%-p@UT)RD36;vT`l)y>59$+Nk(IHfmD3&VHLW5m_Y`<9v9=7o^jo4Lz36MNl!%1 z3c{>#C-z6vmYddm?8F5!nukB?&9Qdzs!KMBj{!#L!8zi1kBIRuP=&b|uHG%D0++Ww zKF=0w;?gq+M!;#eX^_}Pr4<(R>gE(Ur;1)gwTux=f1IQG>fb4lRG zauq6JTk=W;nN0r%g|iMMZts2#+~Kw1kA-3nBBM<2&r;0npESg~K6u!!V7Y-zgy%jr z!=09xB~ev~Jcp)_SGwX7G$-j)q(48uz%aSH{(e4l252lUj``uz&I8@A_=KdyUZ?@Q(rXR552h$Wp&%Sm$b-Okpa9CMXW*$|8A3#-)8|R{nX6* zrI}P?wPY7piep=yrIXLRu5>57uq2UvzR<1~NwK~f8JrI9srnbs2UA;5UgdfyLRR&X zAXqb}GL2YZjX`a)UZ~1kU9Bst!uiUq9|M?TT{2V70AVJ|-z~5F6{)i=C=%eGKF6%Y z7Ft=6dZdWTXx8KXRhtxFSRyM*AuF=@3GUfDy+`L!cV z`(^xDDBY+K4#OC;>}DddEs8FK>ce{#!e2#ud;xxKyt5wP;!mD`4l^XIWLkqgMWo%f zaflwyB3@QC!jweeSK)r;DGG-cCu&bG3U3{ikLdi;H(v7DU?2%M?3qCC8b93Hb2PJ8 z@QeX-JYCs{mGVMLlFvfm&_dn3r$3Xx;jR^+ts(ChilDJchx+!Diue#c4B z*?P;?K7WLbI!9T{JovmNd>w<{$E!;H66`ObfV*qFGyRM4F5w9=Avky7CqrbX!vrp)1mkD1rC#mdLXdN5pFSJ z*(*Zoh!M$6Z&r2Qz%JRl;UnMd*_o@|;^NH2X#LxwMlEsQulGJjB@VuxX*cV4`Lws> zjl|ByKhtDk-fUo=Yh_xY^aZC}aF!_|(lIkA7TzQRY(t0p>Gd&tc> zes@Omai_pyi@$|MbZVE&ERRd{jvv1`xy40nO-yXFC#y+=4&S)Sp)+(Djck1bYeH4! zm3cZ@u`K`0Js)Lp=f+iJs`n|0M3vE<8>IBf1WpRk4Sn<9nsijK^v9}F8FXx52olT* z%Rek&eO%wFlj3mYQhb}!v=YZXUUOO=$D~YwDZ#~m7 z44|QAFF^b`OSw!ZP+^L^zK)1>UerWGO_E%p^2sP({CtErlFQfrt$O>4 zcuslow^_3ri0HuWcigZz2w%Q*7cm;>40)1o@kz}pysE50TzoIPQwuXFW}elhNffQq ztZ)$Oz@XwhOmbLQ@ zHdq2g<@TQ%lSARCV#zL2X2O~fLkuTD81 z;n(NWjoQXwD1@m_!wBJ5PzLd0<=A+CCKTW<`dnOI=yAmO5HaW9zyjJ<0ws*rHnyd_&^78n&clLII+-hONNCDg>?d-5cWDLC_b)9n6o{P1CU-$7L407s-_ z-pN>_?^HhHRDQmVX3NRF#4(=Jdi27iXbVZSm@Te&4UHIPDSbLIRgksrcMi!}LH8kx zi1kkV?^GlM!Caxc9^)p1vBDD=F(&PD^l79>spQ`#vz{QD@ z9VQiviBfRP&y$x0E-FU?(j7DNYgz5FnO9-1U7Fj10D;J3`ywYGRtdNp5Y>Qo+1-P@|$#4vrd!{It&D4(5 z88MK>t&(M*q{{bk+gKz8BV8NoUls7#Pa(Gk7HG*!WO1MnoAKw=-;D)9T2XpobRN@;R9$ zdDZ*TNdMDRe3pcxxWT#?Gvz6$N>L_At8M<_Nu!G9BUfJBQ zeod4i4j8la+F6~Ch&@o#a%JWXtFx6-@5vSL5;@>X>|ze$N=4Jovjt5>8c*=P)os?J z=UlsoH#$Jz7vfg0g=+%Jf)w{Z(Z%^d5W}1#^0}%BgEhRzNs8I2&P7V?GtK0o$CS>y zS%AH91idyPyNX-#5}K5@2VRQ>?Da%6Q(1)*NzRxW9-2LG&+L zW9v~&N*UPrd!ao6TTvM1O*2z1?grU81wdZsv-2#9){B=Yo58FPq{90cNRy?PdBzqr zbXR&i)#}mnzKE|yj_#pCV$njDr<`4a;0d&q@G_^+74Q(M$6rW^ZRcZS?r=zYm%#Gj z!Sc1I-ZxAVPnlVmU2ukuW86&QC4@4nDGZNmY%^`PdC5+u~%7?p{5Ihg@E{qe%G7|%$x8>B2lP60{y^WAi!)2f5_jj zyAZ&Czma_OcZ!1f$!-?4yN(KE{v8Flf2F|VM_l1=DI&Z}(RBvZ-?=MJurdV+bx}qc zMM>r#Mp-#9xf(Dlj7$ur%9-=K=m+1QT9ro_U?#&Wv%M{`+o5WT)8b>jv9 z{(W;{+`KsjQAHU^2{m;l1<5DCcK8k!lt%~8FU9>xGEa>%xpxcvNwk|}rEBVH6gs&y zcc%2{>C}&E29pz0OWd`^u-ES8cTVPzX`)(qt=d?&K@&=Rotx78SlqgrEVG_qUo)_mC$8U`F#qlHOCD&RSroexT?YJLzvne^0W z@;=|QRR6AVW@n3W0fEJOGM5gbEhzW#FFa{0FL+k>kgt~r3DnajgxZvn2mk*LWvgsJNdYFw~S!X4cFe+Q;Q-_W%N z9+%cg5D+rIfU$v>NB;`!-|$Y|w(+s#2VpgER|yU}|IL~d1DHEF1OAnnMj?dmwqP?|!Tm)27hExl-^LX;b^(CT z!UODGtX!?!0czl=9(xOLEjt>6{g40iN!)JVBc;&q!{D7LBTNX0>kPC%g@yXJ??CR3 z^oF;AH}dO}OTni1fx&;Ra!+t5|8G{gf|ZL4*w`O!41NfJAE&N>zi#R(&V#)+FzyN% z_g90{z|?BLiTfv@hp{u@$1u7B_-1N#iJ#RBzM2BR!2c8QKQ->n9NpJB+kXlz_@(`y zApg-W%GVs=-$=u6Jp_Mfr34rf;5=qxnT`lG`0>Z&B#n)_ODW`1+jPPicN} zhgOBZJau)7R=(j9e&@_!Y{d>iX#+|6|i>`&Q={(}Kji+O zpFcjFOMd9Ss|3O?C362PVeDvZY6)PztKhZE=cg?HTJXn${I25H4xgVwR(eM*+@Z8Irh^0H1^@(vM%fLB8x9<0IcS*cf20Th OJOEd-=rxTO#Qy`$*1Hh^ literal 0 HcmV?d00001 diff --git a/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.properties b/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..c954cec91 --- /dev/null +++ b/samples/vanilla-thymeleaf/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip diff --git a/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.jar b/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5fd4d5023f1463b5ba3970e33c460c1eb26d748d GIT binary patch literal 49502 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%0A$pwhjXzyA0ZzErH{1@M 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 OJOEd-=rxTO#Qy`$*1Hh^ literal 0 HcmV?d00001 diff --git a/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.properties b/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..c954cec91 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip diff --git a/samples/vanilla-thymeleaf/bin/compile.sh b/samples/vanilla-thymeleaf/bin/compile.sh new file mode 100755 index 000000000..05ccd9d67 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/compile.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="vanilla-thymeleaf-0.1.0.jar" +rm thymeleaf +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP hello.Application + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-boot-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=thymeleaf \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -H:+TraceClassInitialization=io.netty.bootstrap.AbstractBootstrap \ + -DremoveUnusedAutoconfig=true \ + -cp $CP hello.Application + + #--debug-attach \ +mv thymeleaf ../../.. + +printf "\n\nCompiled app (demo)\n" +cd ../../.. +./thymeleaf + diff --git a/samples/vanilla-thymeleaf/bin/mvnw b/samples/vanilla-thymeleaf/bin/mvnw new file mode 100755 index 000000000..a1ba1bf55 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/mvnw @@ -0,0 +1,233 @@ +#!/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 +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 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 + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + 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 Migwn, 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)`" + # TODO classpath? +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 + +# 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"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# 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} "$@" diff --git a/samples/vanilla-thymeleaf/bin/mvnw.cmd b/samples/vanilla-thymeleaf/bin/mvnw.cmd new file mode 100755 index 000000000..2b934e89d --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/mvnw.cmd @@ -0,0 +1,145 @@ +@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 Maven2 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 key stroke 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 enable echoing my 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 + +set MAVEN_CMD_LINE_ARGS=%* + +@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="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +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/samples/vanilla-thymeleaf/bin/pom.xml b/samples/vanilla-thymeleaf/bin/pom.xml new file mode 100644 index 000000000..22d9f88bc --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + + com.example + vanilla-thymeleaf + 0.1.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.BUILD-SNAPSHOT + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-test + test + + + + + 1.8 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + diff --git a/samples/vanilla-thymeleaf/bin/src/main/java/hello/Application.class b/samples/vanilla-thymeleaf/bin/src/main/java/hello/Application.class new file mode 100644 index 0000000000000000000000000000000000000000..d794ab0b1065c40abf5871dcf278427193d87a5b GIT binary patch literal 1304 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 N6@0EI;~7|mwco!)9fSY? literal 0 HcmV?d00001 diff --git a/samples/vanilla-thymeleaf/bin/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-thymeleaf/bin/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..378693672 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,7 @@ +[ + { + "name": "hello.Greeting", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/bin/src/main/resources/static/index.html b/samples/vanilla-thymeleaf/bin/src/main/resources/static/index.html new file mode 100644 index 000000000..33e9b7e90 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + Getting Started: Serving Web Content + + + +

Get your greeting here

+ + diff --git a/samples/vanilla-thymeleaf/bin/src/main/resources/templates/greeting.html b/samples/vanilla-thymeleaf/bin/src/main/resources/templates/greeting.html new file mode 100644 index 000000000..b4c497975 --- /dev/null +++ b/samples/vanilla-thymeleaf/bin/src/main/resources/templates/greeting.html @@ -0,0 +1,10 @@ + + + + Getting Started: Serving Web Content + + + +

+ + diff --git a/samples/vanilla-thymeleaf/bin/src/test/java/hello/ApplicationTests.class b/samples/vanilla-thymeleaf/bin/src/test/java/hello/ApplicationTests.class new file mode 100644 index 0000000000000000000000000000000000000000..318271e18c9a9993f68d03a0d8f9907a067992bd GIT binary patch literal 2273 zcmeHIOK%e~5FRIKUQOu(3Y79NRaKHpa^XY~QdPC6(iRX+TXA$YZsM|QuWWB9zk;8G z1X8*4qY&fWByAzm29*mEhh2NT-+rDMkH7i;@3-hqM=4?r5!a8Rkjh z)1lPds9?am)K_67f`pYuTH!XKc}A<1cAwKO_XCC-(1`i(S&!4TB)B!&RoqZ?hK9`g zeQFWQ&ms)yG8WU)bZKci7nq|`x8Q;R#8Un z$B0)e2ay)ChgivV1{4cnH;B(%F=QZLYR)avZYycE3W7}Pz0j4Vu++qn11o8)j(w{% zO)IkApu*5tcKL~rc$z2p1?Mad(3cBzlkUgpU5cViy5moYFM+C+afxQcai#P#uv4UE z=gjAkyLuT^f3od?b?28s_}{lwR4&*36nU#c4Hj!qfh7XVb?-v5(xh)XNt^eu9$&>_ zhwC;&l_Id@DW3&LObcfZ7Ynv8P#rhCX+#-?v)or9e}mdapgJxaRO@>LDq|b<)pJL? zS%wD$&Zkk&?7KT=�-tU8ERuZzvnskw#OeV26flOv8bSwa<5j%klEBeJl}k%#Alx zJxt-ufCVvP=UfLH2^X%*A;H7G#KpJ=q*uAi=63D>}6mcw} z<;F6O1sp3-#rJH|dh<8~sD6doyQLeSVc{EEIk<^$_Xoft+`@M)MZq%MMvK55WXQu^ zoIAdi@eZrF@;mSS3A}3)c^_Vzw=t3T(Y1M76L|ALG2R?ze3apJwCng3adibYege`~ BlJNil literal 0 HcmV?d00001 diff --git a/samples/vanilla-thymeleaf/compile.sh b/samples/vanilla-thymeleaf/compile.sh new file mode 100755 index 000000000..519d3dcc0 --- /dev/null +++ b/samples/vanilla-thymeleaf/compile.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="vanilla-thymeleaf-0.1.0.jar" +rm thymeleaf +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP hello.Application + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=thymeleaf \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP hello.Application + + + #--debug-attach \ +mv thymeleaf ../../.. + +printf "\n\nCompiled app (demo)\n" +cd ../../.. +./thymeleaf + diff --git a/samples/vanilla-thymeleaf/mvnw b/samples/vanilla-thymeleaf/mvnw new file mode 100755 index 000000000..a1ba1bf55 --- /dev/null +++ b/samples/vanilla-thymeleaf/mvnw @@ -0,0 +1,233 @@ +#!/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 +# +# 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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 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 + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + 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 Migwn, 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)`" + # TODO classpath? +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 + +# 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"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# 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} "$@" diff --git a/samples/vanilla-thymeleaf/mvnw.cmd b/samples/vanilla-thymeleaf/mvnw.cmd new file mode 100755 index 000000000..2b934e89d --- /dev/null +++ b/samples/vanilla-thymeleaf/mvnw.cmd @@ -0,0 +1,145 @@ +@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 Maven2 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 key stroke 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 enable echoing my 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 + +set MAVEN_CMD_LINE_ARGS=%* + +@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="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +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/samples/vanilla-thymeleaf/pom.xml b/samples/vanilla-thymeleaf/pom.xml new file mode 100644 index 000000000..aabed5eae --- /dev/null +++ b/samples/vanilla-thymeleaf/pom.xml @@ -0,0 +1,138 @@ + + + 4.0.0 + + com.example + vanilla-thymeleaf + 0.1.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-starter-logging + + + org.hibernate.validator + hibernate-validator + + + io.netty + netty-transport-native-epoll + + + io.netty + netty-codec-http2 + + + jakarta.validation + jakarta.validation-api + + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-test + test + + + + + 1.8 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + diff --git a/samples/vanilla-thymeleaf/src/main/java/hello/Application.java b/samples/vanilla-thymeleaf/src/main/java/hello/Application.java new file mode 100644 index 000000000..59428955b --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/java/hello/Application.java @@ -0,0 +1,13 @@ +package hello; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(proxyBeanMethods = false) +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/samples/vanilla-thymeleaf/src/main/java/hello/GreetingController.java b/samples/vanilla-thymeleaf/src/main/java/hello/GreetingController.java new file mode 100644 index 000000000..bcf1ffc66 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/java/hello/GreetingController.java @@ -0,0 +1,54 @@ +package hello; + +import java.util.UUID; + +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class GreetingController { + + @GetMapping("/greeting") + public String greeting( + @RequestParam(name = "name", required = false, defaultValue = "World") String name, + Model model) { + model.addAttribute("greeting", new Greeting(name)); + return "greeting"; + } + +} + +class Greeting { + + private String id = UUID.randomUUID().toString(); + + private String msg; + + @SuppressWarnings("unused") + private Greeting() { + } + + public Greeting(String msg) { + this.msg = msg; + } + + public String getId() { + return id; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + @Override + public String toString() { + return "Greeting [msg=" + msg + "]"; + } + +} \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/src/main/resources/META-INF/native-image/reflect-config.json b/samples/vanilla-thymeleaf/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 000000000..378693672 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,7 @@ +[ + { + "name": "hello.Greeting", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] \ No newline at end of file diff --git a/samples/vanilla-thymeleaf/src/main/resources/static/index.html b/samples/vanilla-thymeleaf/src/main/resources/static/index.html new file mode 100644 index 000000000..33e9b7e90 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + Getting Started: Serving Web Content + + + +

Get your greeting here

+ + diff --git a/samples/vanilla-thymeleaf/src/main/resources/templates/greeting.html b/samples/vanilla-thymeleaf/src/main/resources/templates/greeting.html new file mode 100644 index 000000000..b4c497975 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/main/resources/templates/greeting.html @@ -0,0 +1,10 @@ + + + + Getting Started: Serving Web Content + + + +

+ + diff --git a/samples/vanilla-thymeleaf/src/test/java/hello/ApplicationTests.java b/samples/vanilla-thymeleaf/src/test/java/hello/ApplicationTests.java new file mode 100644 index 000000000..f69292552 --- /dev/null +++ b/samples/vanilla-thymeleaf/src/test/java/hello/ApplicationTests.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012-2018 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. + */ + +package hello; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.reactive.server.WebTestClient; + +@RunWith(SpringRunner.class) +@WebFluxTest(controllers = GreetingController.class) +public class ApplicationTests { + + @Autowired + private WebTestClient client; + + @Test + public void homePage() throws Exception { + // N.B. jsoup can be useful for asserting HTML content + client.get().uri("/index.html").exchange().expectBody(String.class).consumeWith( + content -> content.getResponseBody().contains("Get your greeting")); + } + + @Test + public void greeting() throws Exception { + client.get().uri("/greeting").exchange().expectBody(String.class).consumeWith( + content -> content.getResponseBody().contains("Hello, World!")); + } + + @Test + public void greetingWithUser() throws Exception { + client.get().uri("/greeting?name={name}", "Greg").exchange() + .expectBody(String.class).consumeWith( + content -> content.getResponseBody().contains("Hello, Greg!")); + } + +} diff --git a/samples/vanilla-tx/compile.sh b/samples/vanilla-tx/compile.sh new file mode 100755 index 000000000..0a2253ae2 --- /dev/null +++ b/samples/vanilla-tx/compile.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +../../mvnw -DskipTests clean package + +export JAR="vanilla-tx-0.0.1.BUILD-SNAPSHOT.jar" +rm tx +printf "Unpacking $JAR" +rm -rf unpack +mkdir unpack +cd unpack +jar -xvf ../target/$JAR >/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP hello.Application + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:Name=tx \ + -H:+ReportExceptionStackTraces \ + -H:+TraceClassInitialization \ + --no-fallback \ + --allow-incomplete-classpath \ + -H:EnableURLProtocols=https \ + --report-unsupported-elements-at-runtime \ + -DremoveUnusedAutoconfig=true \ + -cp $CP app.main.SampleApplication + + #--debug-attach \ +mv tx ../../.. + +printf "\n\nCompiled app \n" +cd ../../.. +./tx + diff --git a/samples/vanilla-tx/pom.xml b/samples/vanilla-tx/pom.xml new file mode 100644 index 000000000..62624d669 --- /dev/null +++ b/samples/vanilla-tx/pom.xml @@ -0,0 +1,133 @@ + + + 4.0.0 + + org.springframework.experimental + vanilla-tx + 0.0.1.BUILD-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-webflux + + + io.netty + netty-transport-native-epoll + + + jakarta.validation + jakarta.validation-api + + + org.springframework.boot + spring-boot-starter-validation + + + org.hibernate.validator + hibernate-validator + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + + + 1.8 + app.main.SampleApplication + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + false + + + true + + + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + true + + + true + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + true + + + true + + + + + diff --git a/samples/vanilla-tx/src/main/java/app/main/Finder.java b/samples/vanilla-tx/src/main/java/app/main/Finder.java new file mode 100644 index 000000000..d37fa2f8d --- /dev/null +++ b/samples/vanilla-tx/src/main/java/app/main/Finder.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019-2019 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 app.main; + +/** + * @author Dave Syer + * + */ +public interface Finder { + + T find(long id); + +} diff --git a/samples/vanilla-tx/src/main/java/app/main/Runner.java b/samples/vanilla-tx/src/main/java/app/main/Runner.java new file mode 100644 index 000000000..4c61577bd --- /dev/null +++ b/samples/vanilla-tx/src/main/java/app/main/Runner.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019-2019 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 app.main; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import app.main.model.Foo; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.Assert; + +/** + * @author Dave Syer + */ +@Component +public class Runner implements CommandLineRunner, Finder { + + private static final String GET_FOO = "SELECT VALUE from FOOS where ID=?"; + + private static final String ADD_FOO = "INSERT into FOOS (ID, VALUE) values (?, ?)"; + + private final JdbcTemplate entities; + + private final FooMapper mapper = new FooMapper(); + + public Runner(JdbcTemplate entities) { + this.entities = entities; + } + + @Override + @Transactional + public void run(String... args) throws Exception { + Assert.isTrue(TransactionSynchronizationManager.isActualTransactionActive(), "Expected transaction"); + try { + find(1L); + } + catch (EmptyResultDataAccessException e) { + entities.update(ADD_FOO, 1L, "Hello"); + } + } + + class FooMapper implements RowMapper { + + @Override + public Foo mapRow(ResultSet rs, int rowNum) throws SQLException { + return new Foo(rs.getString(1)); + } + + } + + @Override + public Foo find(long id) { + return entities.queryForObject(GET_FOO, mapper, id); + } + +} \ No newline at end of file diff --git a/samples/vanilla-tx/src/main/java/app/main/SampleApplication.java b/samples/vanilla-tx/src/main/java/app/main/SampleApplication.java new file mode 100644 index 000000000..af8b15e86 --- /dev/null +++ b/samples/vanilla-tx/src/main/java/app/main/SampleApplication.java @@ -0,0 +1,29 @@ +package app.main; + +import app.main.model.Foo; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.web.reactive.function.server.RouterFunction; + +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; +import static org.springframework.web.reactive.function.server.ServerResponse.ok; + +@SpringBootApplication(proxyBeanMethods = false) +public class SampleApplication { + + @Bean + public RouterFunction userEndpoints(Finder entities) { + return route(GET("/"), request -> ok() + .body(Mono.fromCallable(() -> entities.find(1L)).subscribeOn(Schedulers.elastic()), Foo.class)); + } + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } + +} diff --git a/samples/vanilla-tx/src/main/java/app/main/model/Foo.java b/samples/vanilla-tx/src/main/java/app/main/model/Foo.java new file mode 100644 index 000000000..2bea6ba42 --- /dev/null +++ b/samples/vanilla-tx/src/main/java/app/main/model/Foo.java @@ -0,0 +1,27 @@ +package app.main.model; + +public class Foo { + + private String value; + + public Foo() { + } + + public Foo(String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return String.format("Foo[value='%s']", value); + } + +} diff --git a/samples/vanilla-tx/src/main/resources/application.properties b/samples/vanilla-tx/src/main/resources/application.properties new file mode 100644 index 000000000..f64911866 --- /dev/null +++ b/samples/vanilla-tx/src/main/resources/application.properties @@ -0,0 +1,2 @@ +logging.level.slim=debug +spring.aop.proxy-target-class=false \ No newline at end of file diff --git a/samples/vanilla-tx/src/main/resources/schema.sql b/samples/vanilla-tx/src/main/resources/schema.sql new file mode 100644 index 000000000..88e3db1fb --- /dev/null +++ b/samples/vanilla-tx/src/main/resources/schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE FOOS ( + id INTEGER IDENTITY PRIMARY KEY, + value VARCHAR(30) +); \ No newline at end of file diff --git a/samples/vanilla-tx/src/test/java/app/main/SampleApplicationTests.java b/samples/vanilla-tx/src/test/java/app/main/SampleApplicationTests.java new file mode 100644 index 000000000..01a4ab0fa --- /dev/null +++ b/samples/vanilla-tx/src/test/java/app/main/SampleApplicationTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2018 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. + */ + +package app.main; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.reactive.server.WebTestClient; + +/** + * @author Dave Syer + * + */ +@SpringBootTest +@AutoConfigureWebTestClient +public class SampleApplicationTests { + + @Autowired + private WebTestClient client; + + @Test + public void test() { + client.get().uri("/").exchange().expectBody(String.class).isEqualTo("{\"value\":\"Hello\"}"); + } + +} diff --git a/samples/vanilla-tx/src/test/resources/logback.xml b/samples/vanilla-tx/src/test/resources/logback.xml new file mode 100644 index 000000000..a6fe8de4a --- /dev/null +++ b/samples/vanilla-tx/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/webflux-netty/.mvn/wrapper/MavenWrapperDownloader.java b/samples/webflux-netty/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 000000000..72308aa47 --- /dev/null +++ b/samples/webflux-netty/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,114 @@ +/* +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. +*/ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Properties; + +public class MavenWrapperDownloader { + + /** + * 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/0.4.2/maven-wrapper-0.4.2.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 direcrory '" + 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 { + 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/samples/webflux-netty/.mvn/wrapper/maven-wrapper.jar b/samples/webflux-netty/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..01e67997377a393fd672c7dcde9dccbedf0cb1e9 GIT binary patch literal 48337 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/dev/null 2>&1 +cp -R META-INF BOOT-INF/classes + +cd BOOT-INF/classes +export LIBPATH=`find ../../BOOT-INF/lib | tr '\n' ':'` +export CP=.:$LIBPATH + +# This would run it here... (as an exploded jar) +#java -classpath $CP com.example.demo.DemoApplication + +# Our feature being on the classpath is what triggers it +export CP=$CP:../../../../../target/spring-graal-feature-0.5.0.BUILD-SNAPSHOT.jar + +printf "\n\nCompile\n" +native-image \ + -Dio.netty.noUnsafe=true \ + --no-server \ + -H:+TraceClassInitialization \ + -H:Name=webflux-netty \ + -H:+ReportExceptionStackTraces \ + --no-fallback \ + --allow-incomplete-classpath \ + --report-unsupported-elements-at-runtime \ + -cp $CP com.example.demo.DemoApplication + +# -DremoveUnusedAutoconfig=true \ +mv webflux-netty ../../.. + +printf "\n\nCompiled app (webflux-netty)\n" +cd ../../.. +time ./webflux-netty + diff --git a/samples/webflux-netty/mvnw b/samples/webflux-netty/mvnw new file mode 100644 index 000000000..8b9da3b8b --- /dev/null +++ b/samples/webflux-netty/mvnw @@ -0,0 +1,286 @@ +#!/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. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 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)`" + # TODO classpath? +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 + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + 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 command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + wget "$jarUrl" -O "$wrapperJarPath" + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + curl -o "$wrapperJarPath" "$jarUrl" + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + 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 + +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 "$@" diff --git a/samples/webflux-netty/mvnw.cmd b/samples/webflux-netty/mvnw.cmd new file mode 100644 index 000000000..fef5a8f7f --- /dev/null +++ b/samples/webflux-netty/mvnw.cmd @@ -0,0 +1,161 @@ +@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 Maven2 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 key stroke 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 my 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.4.2/maven-wrapper-0.4.2.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% ( + echo Found %WRAPPER_JAR% +) else ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" + echo Finished downloading %WRAPPER_JAR% +) +@REM End of extension + +%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% diff --git a/samples/webflux-netty/pom.xml b/samples/webflux-netty/pom.xml new file mode 100644 index 000000000..68f7e0ef3 --- /dev/null +++ b/samples/webflux-netty/pom.xml @@ -0,0 +1,133 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.0.M5 + + + com.example + webflux-netty + 0.0.1-SNAPSHOT + demo + Demo project for Spring Boot + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.hibernate.validator + hibernate-validator + + + io.netty + netty-transport-native-epoll + + + io.netty + netty-codec-http2 + + + jakarta.validation + jakarta.validation-api + + + + + org.springframework + spring-context-indexer + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + + central + https://repo.maven.apache.org/maven2 + + false + + + + spring-release + Spring release + https://repo.spring.io/release + + false + + + + spring-snapshot + Spring Snapshots + https://repo.spring.io/snapshot + + true + + + + spring-milestone + Spring Milestone + https://repo.spring.io/milestone + + false + + + + + diff --git a/samples/webflux-netty/src/main/java/com/example/demo/DemoApplication.java b/samples/webflux-netty/src/main/java/com/example/demo/DemoApplication.java new file mode 100644 index 000000000..1db6e609b --- /dev/null +++ b/samples/webflux-netty/src/main/java/com/example/demo/DemoApplication.java @@ -0,0 +1,28 @@ +package com.example.demo; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication(proxyBeanMethods = false) +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + + @RestController + class Foo { + + @GetMapping("/") + public String greet() { + return "hi!"; + } + } + +} diff --git a/samples/webflux-netty/src/main/java/com/example/demo/Foobar.java b/samples/webflux-netty/src/main/java/com/example/demo/Foobar.java new file mode 100644 index 000000000..251fb8335 --- /dev/null +++ b/samples/webflux-netty/src/main/java/com/example/demo/Foobar.java @@ -0,0 +1,13 @@ +package com.example.demo; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class Foobar { + + @GetMapping("/x") + public String greet2() { + return "hix!"; + } +} \ No newline at end of file diff --git a/samples/webflux-netty/src/main/resources/application.properties b/samples/webflux-netty/src/main/resources/application.properties new file mode 100644 index 000000000..e69de29bb diff --git a/samples/webflux-netty/src/main/resources/logging.properties b/samples/webflux-netty/src/main/resources/logging.properties new file mode 100644 index 000000000..70a31b86a --- /dev/null +++ b/samples/webflux-netty/src/main/resources/logging.properties @@ -0,0 +1,12 @@ +handlers =java.util.logging.ConsoleHandler +.level = INFO + +java.util.logging.ConsoleHandler.formatter = org.springframework.boot.logging.java.SimpleFormatter +java.util.logging.ConsoleHandler.level = ALL + +org.hibernate.validator.internal.util.Version.level = WARNING +org.apache.coyote.http11.Http11NioProtocol.level = WARNING +org.apache.tomcat.util.net.NioSelectorPool.level = WARNING +org.apache.catalina.startup.DigesterFactory.level = SEVERE +org.apache.catalina.util.LifecycleBase.level = SEVERE +org.eclipse.jetty.util.component.AbstractLifeCycle.level = SEVERE diff --git a/samples/webflux-netty/src/test/java/com/example/demo/DemoApplicationTests.java b/samples/webflux-netty/src/test/java/com/example/demo/DemoApplicationTests.java new file mode 100644 index 000000000..b4c8f5df3 --- /dev/null +++ b/samples/webflux-netty/src/test/java/com/example/demo/DemoApplicationTests.java @@ -0,0 +1,16 @@ +//package com.example.demo; +// +//import org.junit.Test; +//import org.junit.runner.RunWith; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.test.context.junit4.SpringRunner; +// +//@RunWith(SpringRunner.class) +//@SpringBootTest +//public class DemoApplicationTests { +// +// @Test +// public void contextLoads() { +// } +// +//} diff --git a/src/json-shade/README.adoc b/src/json-shade/README.adoc new file mode 100644 index 000000000..654800694 --- /dev/null +++ b/src/json-shade/README.adoc @@ -0,0 +1,5 @@ +## Shaded JSON + +This source was originally taken from `com.vaadin.external.google:android-json` which +provides a clean room re-implementation of the `org.json` APIs and does not include the +"Do not use for evil" clause. diff --git a/src/json-shade/java/org/springframework/graal/json/JSON.java b/src/json-shade/java/org/springframework/graal/json/JSON.java new file mode 100644 index 000000000..034179dfc --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSON.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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. + */ + +package org.springframework.graal.json; + +class JSON { + + static double checkDouble(double d) throws JSONException { + if (Double.isInfinite(d) || Double.isNaN(d)) { + throw new JSONException("Forbidden numeric value: " + d); + } + return d; + } + + static Boolean toBoolean(Object value) { + if (value instanceof Boolean) { + return (Boolean) value; + } + if (value instanceof String) { + String stringValue = (String) value; + if ("true".equalsIgnoreCase(stringValue)) { + return true; + } + if ("false".equalsIgnoreCase(stringValue)) { + return false; + } + } + return null; + } + + static Double toDouble(Object value) { + if (value instanceof Double) { + return (Double) value; + } + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + if (value instanceof String) { + try { + return Double.valueOf((String) value); + } + catch (NumberFormatException ignored) { + } + } + return null; + } + + static Integer toInteger(Object value) { + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + if (value instanceof String) { + try { + return (int) Double.parseDouble((String) value); + } + catch (NumberFormatException ignored) { + } + } + return null; + } + + static Long toLong(Object value) { + if (value instanceof Long) { + return (Long) value; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + if (value instanceof String) { + try { + return (long) Double.parseDouble((String) value); + } + catch (NumberFormatException ignored) { + } + } + return null; + } + + static String toString(Object value) { + if (value instanceof String) { + return (String) value; + } + if (value != null) { + return String.valueOf(value); + } + return null; + } + + public static JSONException typeMismatch(Object indexOrName, Object actual, + String requiredType) throws JSONException { + if (actual == null) { + throw new JSONException("Value at " + indexOrName + " is null."); + } + throw new JSONException("Value " + actual + " at " + indexOrName + " of type " + + actual.getClass().getName() + " cannot be converted to " + + requiredType); + } + + public static JSONException typeMismatch(Object actual, String requiredType) + throws JSONException { + if (actual == null) { + throw new JSONException("Value is null."); + } + throw new JSONException( + "Value " + actual + " of type " + actual.getClass().getName() + + " cannot be converted to " + requiredType); + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONArray.java b/src/json-shade/java/org/springframework/graal/json/JSONArray.java new file mode 100644 index 000000000..300644e92 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONArray.java @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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. + */ + +package org.springframework.graal.json; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * A dense indexed sequence of values. Values may be any mix of {@link JSONObject + * JSONObjects}, other {@link JSONArray JSONArrays}, Strings, Booleans, Integers, Longs, + * Doubles, {@code null} or {@link JSONObject#NULL}. Values may not be + * {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not + * listed here. + *

+ * {@code JSONArray} has the same type coercion behavior and optional/mandatory accessors + * as {@link JSONObject}. See that class' documentation for details. + *

+ * Warning: this class represents null in two incompatible ways: the + * standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}. + * In particular, {@code get} fails if the requested index holds the null reference, but + * succeeds if it holds {@code JSONObject.NULL}. + *

+ * Instances of this class are not thread safe. Although this class is nonfinal, it was + * not designed for inheritance and should not be subclassed. In particular, self-use by + * overridable methods is not specified. See Effective Java Item 17, "Design and + * Document or inheritance or else prohibit it" for further information. + */ +public class JSONArray { + + private final List values; + + /** + * Creates a {@code JSONArray} with no values. + */ + public JSONArray() { + this.values = new ArrayList<>(); + } + + /** + * Creates a new {@code JSONArray} by copying all values from the given collection. + * @param copyFrom a collection whose values are of supported types. Unsupported + * values are not permitted and will yield an array in an inconsistent state. + */ + /* Accept a raw type for API compatibility */ + @SuppressWarnings("rawtypes") + public JSONArray(Collection copyFrom) { + this(); + if (copyFrom != null) { + for (Iterator it = copyFrom.iterator(); it.hasNext();) { + put(JSONObject.wrap(it.next())); + } + } + } + + /** + * Creates a new {@code JSONArray} with values from the next array in the tokener. + * @param readFrom a tokener whose nextValue() method will yield a {@code JSONArray}. + * @throws JSONException if the parse fails or doesn't yield a {@code JSONArray}. + * @throws JSONException if processing of json failed + */ + public JSONArray(JSONTokener readFrom) throws JSONException { + /* + * Getting the parser to populate this could get tricky. Instead, just parse to + * temporary JSONArray and then steal the data from that. + */ + Object object = readFrom.nextValue(); + if (object instanceof JSONArray) { + this.values = ((JSONArray) object).values; + } + else { + throw JSON.typeMismatch(object, "JSONArray"); + } + } + + /** + * Creates a new {@code JSONArray} with values from the JSON string. + * @param json a JSON-encoded string containing an array. + * @throws JSONException if the parse fails or doesn't yield a {@code + * JSONArray}. + */ + public JSONArray(String json) throws JSONException { + this(new JSONTokener(json)); + } + + /** + * Creates a new {@code JSONArray} with values from the given primitive array. + * @param array a primitive array + * @throws JSONException if processing of json failed + */ + public JSONArray(Object array) throws JSONException { + if (!array.getClass().isArray()) { + throw new JSONException("Not a primitive array: " + array.getClass()); + } + final int length = Array.getLength(array); + this.values = new ArrayList<>(length); + for (int i = 0; i < length; ++i) { + put(JSONObject.wrap(Array.get(array, i))); + } + } + + /** + * Returns the number of values in this array. + * @return the length of this array + */ + public int length() { + return this.values.size(); + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value the value + * @return this array. + */ + public JSONArray put(boolean value) { + this.values.add(value); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(double value) throws JSONException { + this.values.add(JSON.checkDouble(value)); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * @param value the value + * @return this array. + */ + public JSONArray put(int value) { + this.values.add(value); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * @param value the value + * @return this array. + */ + public JSONArray put(long value) { + this.values.add(value); + return this; + } + + /** + * Appends {@code value} to the end of this array. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. Unsupported + * values are not permitted and will cause the array to be in an inconsistent state. + * @return this array. + */ + public JSONArray put(Object value) { + this.values.add(value); + return this; + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value the value + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, boolean value) throws JSONException { + return put(index, (Boolean) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, double value) throws JSONException { + return put(index, (Double) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value the value + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, int value) throws JSONException { + return put(index, (Integer) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value the value + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, long value) throws JSONException { + return put(index, (Long) value); + } + + /** + * Sets the value at {@code index} to {@code value}, null padding this array to the + * required length if necessary. If a value already exists at {@code + * index}, it will be replaced. + * @param index the index to set the value to + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be + * {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. + * @return this array. + * @throws JSONException if processing of json failed + */ + public JSONArray put(int index, Object value) throws JSONException { + if (value instanceof Number) { + // deviate from the original by checking all Numbers, not just floats & + // doubles + JSON.checkDouble(((Number) value).doubleValue()); + } + while (this.values.size() <= index) { + this.values.add(null); + } + this.values.set(index, value); + return this; + } + + /** + * Returns true if this array has no value at {@code index}, or if its value is the + * {@code null} reference or {@link JSONObject#NULL}. + * @param index the index to set the value to + * @return true if this array has no value at {@code index} + */ + public boolean isNull(int index) { + Object value = opt(index); + return value == null || value == JSONObject.NULL; + } + + /** + * Returns the value at {@code index}. + * @param index the index to get the value from + * @return the value at {@code index}. + * @throws JSONException if this array has no value at {@code index}, or if that value + * is the {@code null} reference. This method returns normally if the value is + * {@code JSONObject#NULL}. + */ + public Object get(int index) throws JSONException { + try { + Object value = this.values.get(index); + if (value == null) { + throw new JSONException("Value at " + index + " is null."); + } + return value; + } + catch (IndexOutOfBoundsException e) { + throw new JSONException( + "Index " + index + " out of range [0.." + this.values.size() + ")"); + } + } + + /** + * Returns the value at {@code index}, or null if the array has no value at + * {@code index}. + * @param index the index to get the value from + * @return the value at {@code index} or {@code null} + */ + public Object opt(int index) { + if (index < 0 || index >= this.values.size()) { + return null; + } + return this.values.get(index); + } + + /** + * Removes and returns the value at {@code index}, or null if the array has no value + * at {@code index}. + * @param index the index of the value to remove + * @return the previous value at {@code index} + */ + public Object remove(int index) { + if (index < 0 || index >= this.values.size()) { + return null; + } + return this.values.remove(index); + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can be coerced + * to a boolean. + * @param index the index to get the value from + * @return the value at {@code index} + * @throws JSONException if the value at {@code index} doesn't exist or cannot be + * coerced to a boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = get(index); + Boolean result = JSON.toBoolean(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "boolean"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can be coerced + * to a boolean. Returns false otherwise. + * @param index the index to get the value from + * @return the {@code value} or {@code false} + */ + public boolean optBoolean(int index) { + return optBoolean(index, false); + } + + /** + * Returns the value at {@code index} if it exists and is a boolean or can be coerced + * to a boolean. Returns {@code fallback} otherwise. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public boolean optBoolean(int index, boolean fallback) { + Object object = opt(index); + Boolean result = JSON.toBoolean(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is a double or can be coerced + * to a double. + * @param index the index to get the value from + * @return the {@code value} + * @throws JSONException if the value at {@code index} doesn't exist or cannot be + * coerced to a double. + */ + public double getDouble(int index) throws JSONException { + Object object = get(index); + Double result = JSON.toDouble(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "double"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is a double or can be coerced + * to a double. Returns {@code NaN} otherwise. + * @param index the index to get the value from + * @return the {@code value} or {@code NaN} + */ + public double optDouble(int index) { + return optDouble(index, Double.NaN); + } + + /** + * Returns the value at {@code index} if it exists and is a double or can be coerced + * to a double. Returns {@code fallback} otherwise. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public double optDouble(int index, double fallback) { + Object object = opt(index); + Double result = JSON.toDouble(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is an int or can be coerced to + * an int. + * @param index the index to get the value from + * @return the {@code value} + * @throws JSONException if the value at {@code index} doesn't exist or cannot be + * coerced to an int. + */ + public int getInt(int index) throws JSONException { + Object object = get(index); + Integer result = JSON.toInteger(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "int"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is an int or can be coerced to + * an int. Returns 0 otherwise. + * @param index the index to get the value from + * @return the {@code value} or {@code 0} + */ + public int optInt(int index) { + return optInt(index, 0); + } + + /** + * Returns the value at {@code index} if it exists and is an int or can be coerced to + * an int. Returns {@code fallback} otherwise. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public int optInt(int index, int fallback) { + Object object = opt(index); + Integer result = JSON.toInteger(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is a long or can be coerced to + * a long. + * @param index the index to get the value from + * @return the {@code value} + * + * @throws JSONException if the value at {@code index} doesn't exist or cannot be + * coerced to a long. + */ + public long getLong(int index) throws JSONException { + Object object = get(index); + Long result = JSON.toLong(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "long"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists and is a long or can be coerced to + * a long. Returns 0 otherwise. + * @param index the index to get the value from + * @return the {@code value} or {@code 0} + */ + public long optLong(int index) { + return optLong(index, 0L); + } + + /** + * Returns the value at {@code index} if it exists and is a long or can be coerced to + * a long. Returns {@code fallback} otherwise. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public long optLong(int index, long fallback) { + Object object = opt(index); + Long result = JSON.toLong(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists, coercing it if necessary. + * @param index the index to get the value from + * @return the {@code value} + * @throws JSONException if no such value exists. + */ + public String getString(int index) throws JSONException { + Object object = get(index); + String result = JSON.toString(object); + if (result == null) { + throw JSON.typeMismatch(index, object, "String"); + } + return result; + } + + /** + * Returns the value at {@code index} if it exists, coercing it if necessary. Returns + * the empty string if no such value exists. + * @param index the index to get the value from + * @return the {@code value} or an empty string + */ + public String optString(int index) { + return optString(index, ""); + } + + /** + * Returns the value at {@code index} if it exists, coercing it if necessary. Returns + * {@code fallback} if no such value exists. + * @param index the index to get the value from + * @param fallback the fallback value + * @return the value at {@code index} of {@code fallback} + */ + public String optString(int index, String fallback) { + Object object = opt(index); + String result = JSON.toString(object); + return result != null ? result : fallback; + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONArray}. + * @param index the index to get the value from + * @return the array at {@code index} + * @throws JSONException if the value doesn't exist or is not a {@code + * JSONArray}. + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + else { + throw JSON.typeMismatch(index, object, "JSONArray"); + } + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONArray}. Returns null otherwise. + * @param index the index to get the value from + * @return the array at {@code index} or {@code null} + */ + public JSONArray optJSONArray(int index) { + Object object = opt(index); + return object instanceof JSONArray ? (JSONArray) object : null; + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONObject}. + * @param index the index to get the value from + * @return the object at {@code index} + * @throws JSONException if the value doesn't exist or is not a {@code + * JSONObject}. + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + else { + throw JSON.typeMismatch(index, object, "JSONObject"); + } + } + + /** + * Returns the value at {@code index} if it exists and is a {@code + * JSONObject}. Returns null otherwise. + * @param index the index to get the value from + * @return the object at {@code index} or {@code null} + */ + public JSONObject optJSONObject(int index) { + Object object = opt(index); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Returns a new object whose values are the values in this array, and whose names are + * the values in {@code names}. Names and values are paired up by index from 0 through + * to the shorter array's length. Names that are not strings will be coerced to + * strings. This method returns null if either array is empty. + * @param names the property names + * @return a json object + * @throws JSONException if processing of json failed + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + JSONObject result = new JSONObject(); + int length = Math.min(names.length(), this.values.size()); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = JSON.toString(names.opt(i)); + result.put(name, opt(i)); + } + return result; + } + + /** + * Returns a new string by alternating this array's values with {@code + * separator}. This array's string values are quoted and have their special characters + * escaped. For example, the array containing the strings '12" pizza', 'taco' and + * 'soda' joined on '+' returns this:
"12\" pizza"+"taco"+"soda"
+ * @param separator the separator to use + * @return the joined value + * @throws JSONException if processing of json failed + */ + public String join(String separator) throws JSONException { + JSONStringer stringer = new JSONStringer(); + stringer.open(JSONStringer.Scope.NULL, ""); + for (int i = 0, size = this.values.size(); i < size; i++) { + if (i > 0) { + stringer.out.append(separator); + } + stringer.value(this.values.get(i)); + } + stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); + return stringer.out.toString(); + } + + /** + * Encodes this array as a compact JSON string, such as:
[94043,90210]
+ * @return a compact JSON string representation of this array + */ + @Override + public String toString() { + try { + JSONStringer stringer = new JSONStringer(); + writeTo(stringer); + return stringer.toString(); + } + catch (JSONException e) { + return null; + } + } + + /** + * Encodes this array as a human readable JSON string for debugging, such as:
+	 * [
+	 *     94043,
+	 *     90210
+	 * ]
+ * + * @param indentSpaces the number of spaces to indent for each level of nesting. + * @return a human readable JSON string of this array + * @throws JSONException if processing of json failed + */ + public String toString(int indentSpaces) throws JSONException { + JSONStringer stringer = new JSONStringer(indentSpaces); + writeTo(stringer); + return stringer.toString(); + } + + void writeTo(JSONStringer stringer) throws JSONException { + stringer.array(); + for (Object value : this.values) { + stringer.value(value); + } + stringer.endArray(); + } + + @Override + public boolean equals(Object o) { + return o instanceof JSONArray && ((JSONArray) o).values.equals(this.values); + } + + @Override + public int hashCode() { + // diverge from the original, which doesn't implement hashCode + return this.values.hashCode(); + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONException.java b/src/json-shade/java/org/springframework/graal/json/JSONException.java new file mode 100644 index 000000000..92dc2ad23 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONException.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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. + */ + +package org.springframework.graal.json; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * Thrown to indicate a problem with the JSON API. Such problems include: + *
    + *
  • Attempts to parse or construct malformed documents + *
  • Use of null as a name + *
  • Use of numeric types not available to JSON, such as {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + *
  • Lookups using an out of range index or nonexistent name + *
  • Type mismatches on lookups + *
+ *

+ * Although this is a checked exception, it is rarely recoverable. Most callers should + * simply wrap this exception in an unchecked exception and rethrow:

+ *     public JSONArray toJSONObject() {
+ *     try {
+ *         JSONObject result = new JSONObject();
+ *         ...
+ *     } catch (JSONException e) {
+ *         throw new RuntimeException(e);
+ *     }
+ * }
+ */ +public class JSONException extends Exception { + + private static final long serialVersionUID = 1L; + + public JSONException(String s) { + super(s); + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONObject.java b/src/json-shade/java/org/springframework/graal/json/JSONObject.java new file mode 100644 index 000000000..b59d369b0 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONObject.java @@ -0,0 +1,840 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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. + */ + +package org.springframework.graal.json; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * A modifiable set of name/value mappings. Names are unique, non-null strings. Values may + * be any mix of {@link JSONObject JSONObjects}, {@link JSONArray JSONArrays}, Strings, + * Booleans, Integers, Longs, Doubles or {@link #NULL}. Values may not be {@code null}, + * {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not + * listed here. + *

+ * This class can coerce values to another type when requested. + *

+ *

+ * This class can look up both mandatory and optional values: + *

    + *
  • Use getType() to retrieve a mandatory value. This fails with a + * {@code JSONException} if the requested name has no value or if the value cannot be + * coerced to the requested type. + *
  • Use optType() to retrieve an optional value. This returns a + * system- or user-supplied default if the requested name has no value or if the value + * cannot be coerced to the requested type. + *
+ *

+ * Warning: this class represents null in two incompatible ways: the + * standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}. + * In particular, calling {@code put(name, null)} removes the named entry from the object + * but {@code put(name, JSONObject.NULL)} stores an entry whose value is + * {@code JSONObject.NULL}. + *

+ * Instances of this class are not thread safe. Although this class is nonfinal, it was + * not designed for inheritance and should not be subclassed. In particular, self-use by + * overrideable methods is not specified. See Effective Java Item 17, "Design and + * Document or inheritance or else prohibit it" for further information. + */ +public class JSONObject { + + private static final Double NEGATIVE_ZERO = -0d; + + /** + * A sentinel value used to explicitly define a name with no value. Unlike + * {@code null}, names with this value: + *

    + *
  • show up in the {@link #names} array + *
  • show up in the {@link #keys} iterator + *
  • return {@code true} for {@link #has(String)} + *
  • do not throw on {@link #get(String)} + *
  • are included in the encoded JSON string. + *
+ *

+ * This value violates the general contract of {@link Object#equals} by returning true + * when compared to {@code null}. Its {@link #toString} method returns "null". + */ + public static final Object NULL = new Object() { + + @Override + public boolean equals(Object o) { + return o == this || o == null; // API specifies this broken equals + // implementation + } + + @Override + public String toString() { + return "null"; + } + + }; + + private final Map nameValuePairs; + + /** + * Creates a {@code JSONObject} with no name/value mappings. + */ + public JSONObject() { + this.nameValuePairs = new LinkedHashMap<>(); + } + + /** + * Creates a new {@code JSONObject} by copying all name/value mappings from the given + * map. + * + * @param copyFrom a map whose keys are of type {@link String} and whose values are of + * supported types. + * @throws NullPointerException if any of the map's keys are null. + */ + /* (accept a raw type for API compatibility) */ + @SuppressWarnings("rawtypes") + public JSONObject(Map copyFrom) { + this(); + Map contentsTyped = copyFrom; + for (Map.Entry entry : contentsTyped.entrySet()) { + /* + * Deviate from the original by checking that keys are non-null and of the + * proper type. (We still defer validating the values). + */ + String key = (String) entry.getKey(); + if (key == null) { + throw new NullPointerException("key == null"); + } + this.nameValuePairs.put(key, wrap(entry.getValue())); + } + } + + /** + * Creates a new {@code JSONObject} with name/value mappings from the next object in + * the tokener. + * @param readFrom a tokener whose nextValue() method will yield a {@code JSONObject}. + * @throws JSONException if the parse fails or doesn't yield a {@code JSONObject}. + */ + public JSONObject(JSONTokener readFrom) throws JSONException { + /* + * Getting the parser to populate this could get tricky. Instead, just parse to + * temporary JSONObject and then steal the data from that. + */ + Object object = readFrom.nextValue(); + if (object instanceof JSONObject) { + this.nameValuePairs = ((JSONObject) object).nameValuePairs; + } + else { + throw JSON.typeMismatch(object, "JSONObject"); + } + } + + /** + * Creates a new {@code JSONObject} with name/value mappings from the JSON string. + * @param json a JSON-encoded string containing an object. + * @throws JSONException if the parse fails or doesn't yield a {@code + * JSONObject}. + */ + public JSONObject(String json) throws JSONException { + this(new JSONTokener(json)); + } + + /** + * Creates a new {@code JSONObject} by copying mappings for the listed names from the + * given object. Names that aren't present in {@code copyFrom} will be skipped. + * @param copyFrom the source + * @param names the property names + * @throws JSONException if an error occurs + */ + public JSONObject(JSONObject copyFrom, String[] names) throws JSONException { + this(); + for (String name : names) { + Object value = copyFrom.opt(name); + if (value != null) { + this.nameValuePairs.put(name, value); + } + } + } + + /** + * Returns the number of name/value mappings in this object. + * @return the number of name/value mappings in this object + */ + public int length() { + return this.nameValuePairs.size(); + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. + * @param name the name of the property + * @param value the value of the property + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, boolean value) throws JSONException { + this.nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. + * @param name the name of the property + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, double value) throws JSONException { + this.nameValuePairs.put(checkName(name), JSON.checkDouble(value)); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. + * @param name the name of the property + * @param value the value of the property + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, int value) throws JSONException { + this.nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. + * @param name the name of the property + * @param value the value of the property + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, long value) throws JSONException { + this.nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Maps {@code name} to {@code value}, clobbering any existing name/value mapping with + * the same name. If the value is {@code null}, any existing mapping for {@code name} + * is removed. + * @param name the name of the property + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double, {@link #NULL}, or {@code null}. May not be {@link Double#isNaN() + * NaNs} or {@link Double#isInfinite() infinities}. + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject put(String name, Object value) throws JSONException { + if (value == null) { + this.nameValuePairs.remove(name); + return this; + } + if (value instanceof Number) { + // deviate from the original by checking all Numbers, not just floats & + // doubles + JSON.checkDouble(((Number) value).doubleValue()); + } + this.nameValuePairs.put(checkName(name), value); + return this; + } + + /** + * Equivalent to {@code put(name, value)} when both parameters are non-null; does + * nothing otherwise. + * @param name the name of the property + * @param value the value of the property + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject putOpt(String name, Object value) throws JSONException { + if (name == null || value == null) { + return this; + } + return put(name, value); + } + + /** + * Appends {@code value} to the array already mapped to {@code name}. If this object + * has no mapping for {@code name}, this inserts a new mapping. If the mapping exists + * but its value is not an array, the existing and new values are inserted in order + * into a new array which is itself mapped to {@code name}. In aggregate, this allows + * values to be added to a mapping one at a time. + * @param name the name of the property + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double, {@link #NULL} or null. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this object. + * @throws JSONException if an error occurs + */ + public JSONObject accumulate(String name, Object value) throws JSONException { + Object current = this.nameValuePairs.get(checkName(name)); + if (current == null) { + return put(name, value); + } + + // check in accumulate, since array.put(Object) doesn't do any checking + if (value instanceof Number) { + JSON.checkDouble(((Number) value).doubleValue()); + } + + if (current instanceof JSONArray) { + JSONArray array = (JSONArray) current; + array.put(value); + } + else { + JSONArray array = new JSONArray(); + array.put(current); + array.put(value); + this.nameValuePairs.put(name, array); + } + return this; + } + + String checkName(String name) throws JSONException { + if (name == null) { + throw new JSONException("Names must be non-null"); + } + return name; + } + + /** + * Removes the named mapping if it exists; does nothing otherwise. + * + * @param name the name of the property + * @return the value previously mapped by {@code name}, or null if there was no such + * mapping. + */ + public Object remove(String name) { + return this.nameValuePairs.remove(name); + } + + /** + * Returns true if this object has no mapping for {@code name} or if it has a mapping + * whose value is {@link #NULL}. + * @param name the name of the property + * @return true if this object has no mapping for {@code name} + */ + public boolean isNull(String name) { + Object value = this.nameValuePairs.get(name); + return value == null || value == NULL; + } + + /** + * Returns true if this object has a mapping for {@code name}. The mapping may be + * {@link #NULL}. + * @param name the name of the property + * @return true if this object has a mapping for {@code name} + */ + public boolean has(String name) { + return this.nameValuePairs.containsKey(name); + } + + /** + * Returns the value mapped by {@code name}. + * @param name the name of the property + * @return the value + * @throws JSONException if no such mapping exists. + */ + public Object get(String name) throws JSONException { + Object result = this.nameValuePairs.get(name); + if (result == null) { + throw new JSONException("No value for " + name); + } + return result; + } + + /** + * Returns the value mapped by {@code name}, or null if no such mapping exists. + * @param name the name of the property + * @return the value or {@code null} + */ + public Object opt(String name) { + return this.nameValuePairs.get(name); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or can be + * coerced to a boolean. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or cannot be coerced to a + * boolean. + */ + public boolean getBoolean(String name) throws JSONException { + Object object = get(name); + Boolean result = JSON.toBoolean(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "boolean"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or can be + * coerced to a boolean. Returns false otherwise. + * @param name the name of the property + * @return the value or {@code null} + */ + public boolean optBoolean(String name) { + return optBoolean(name, false); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a boolean or can be + * coerced to a boolean. Returns {@code fallback} otherwise. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public boolean optBoolean(String name, boolean fallback) { + Object object = opt(name); + Boolean result = JSON.toBoolean(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or can be + * coerced to a double. + * + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or cannot be coerced to a + * double. + */ + public double getDouble(String name) throws JSONException { + Object object = get(name); + Double result = JSON.toDouble(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "double"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or can be + * coerced to a double. Returns {@code NaN} otherwise. + * @param name the name of the property + * @return the value or {@code NaN} + */ + public double optDouble(String name) { + return optDouble(name, Double.NaN); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a double or can be + * coerced to a double. Returns {@code fallback} otherwise. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public double optDouble(String name, double fallback) { + Object object = opt(name); + Double result = JSON.toDouble(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or can be + * coerced to an int. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or cannot be coerced to an int. + */ + public int getInt(String name) throws JSONException { + Object object = get(name); + Integer result = JSON.toInteger(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "int"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or can be + * coerced to an int. Returns 0 otherwise. + * @param name the name of the property + * @return the value of {@code 0} + */ + public int optInt(String name) { + return optInt(name, 0); + } + + /** + * Returns the value mapped by {@code name} if it exists and is an int or can be + * coerced to an int. Returns {@code fallback} otherwise. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public int optInt(String name, int fallback) { + Object object = opt(name); + Integer result = JSON.toInteger(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or can be + * coerced to a long. Note that JSON represents numbers as doubles, so this is + * lossy; use strings to transfer numbers via JSON. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or cannot be coerced to a long. + */ + public long getLong(String name) throws JSONException { + Object object = get(name); + Long result = JSON.toLong(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "long"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or can be + * coerced to a long. Returns 0 otherwise. Note that JSON represents numbers as + * doubles, so this is lossy; use strings to transfer numbers via + * JSON. + * @param name the name of the property + * @return the value or {@code 0L} + */ + public long optLong(String name) { + return optLong(name, 0L); + } + + /** + * Returns the value mapped by {@code name} if it exists and is a long or can be + * coerced to a long. Returns {@code fallback} otherwise. Note that JSON represents + * numbers as doubles, so this is lossy; use strings to transfer + * numbers via JSON. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public long optLong(String name, long fallback) { + Object object = opt(name); + Long result = JSON.toLong(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if necessary. + * @param name the name of the property + * @return the value + * @throws JSONException if no such mapping exists. + */ + public String getString(String name) throws JSONException { + Object object = get(name); + String result = JSON.toString(object); + if (result == null) { + throw JSON.typeMismatch(name, object, "String"); + } + return result; + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if necessary. + * Returns the empty string if no such mapping exists. + * @param name the name of the property + * @return the value or an empty string + */ + public String optString(String name) { + return optString(name, ""); + } + + /** + * Returns the value mapped by {@code name} if it exists, coercing it if necessary. + * Returns {@code fallback} if no such mapping exists. + * @param name the name of the property + * @param fallback a fallback value + * @return the value or {@code fallback} + */ + public String optString(String name, String fallback) { + Object object = opt(name); + String result = JSON.toString(object); + return result != null ? result : fallback; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONArray}. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or is not a {@code + * JSONArray}. + */ + public JSONArray getJSONArray(String name) throws JSONException { + Object object = get(name); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + else { + throw JSON.typeMismatch(name, object, "JSONArray"); + } + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONArray}. Returns null otherwise. + * @param name the name of the property + * @return the value or {@code null} + */ + public JSONArray optJSONArray(String name) { + Object object = opt(name); + return object instanceof JSONArray ? (JSONArray) object : null; + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONObject}. + * @param name the name of the property + * @return the value + * @throws JSONException if the mapping doesn't exist or is not a {@code + * JSONObject}. + */ + public JSONObject getJSONObject(String name) throws JSONException { + Object object = get(name); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + else { + throw JSON.typeMismatch(name, object, "JSONObject"); + } + } + + /** + * Returns the value mapped by {@code name} if it exists and is a {@code + * JSONObject}. Returns null otherwise. + * @param name the name of the property + * @return the value or {@code null} + */ + public JSONObject optJSONObject(String name) { + Object object = opt(name); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Returns an array with the values corresponding to {@code names}. The array contains + * null for names that aren't mapped. This method returns null if {@code names} is + * either null or empty. + * @param names the names of the properties + * @return the array + */ + public JSONArray toJSONArray(JSONArray names) { + JSONArray result = new JSONArray(); + if (names == null) { + return null; + } + int length = names.length(); + if (length == 0) { + return null; + } + for (int i = 0; i < length; i++) { + String name = JSON.toString(names.opt(i)); + result.put(opt(name)); + } + return result; + } + + /** + * Returns an iterator of the {@code String} names in this object. The returned + * iterator supports {@link Iterator#remove() remove}, which will remove the + * corresponding mapping from this object. If this object is modified after the + * iterator is returned, the iterator's behavior is undefined. The order of the keys + * is undefined. + * @return the keys + */ + /* Return a raw type for API compatibility */ + @SuppressWarnings("rawtypes") + public Iterator keys() { + return this.nameValuePairs.keySet().iterator(); + } + + /** + * Returns an array containing the string names in this object. This method returns + * null if this object contains no mappings. + * @return the array + */ + public JSONArray names() { + return this.nameValuePairs.isEmpty() ? null + : new JSONArray(new ArrayList<>(this.nameValuePairs.keySet())); + } + + /** + * Encodes this object as a compact JSON string, such as: + *

{"query":"Pizza","locations":[94043,90210]}
+ * @return a string representation of the object. + */ + @Override + public String toString() { + try { + JSONStringer stringer = new JSONStringer(); + writeTo(stringer); + return stringer.toString(); + } + catch (JSONException e) { + return null; + } + } + + /** + * Encodes this object as a human readable JSON string for debugging, such as:
+	 * {
+	 *     "query": "Pizza",
+	 *     "locations": [
+	 *         94043,
+	 *         90210
+	 *     ]
+	 * }
+ * @param indentSpaces the number of spaces to indent for each level of nesting. + * @return a string representation of the object. + * @throws JSONException if an error occurs + */ + public String toString(int indentSpaces) throws JSONException { + JSONStringer stringer = new JSONStringer(indentSpaces); + writeTo(stringer); + return stringer.toString(); + } + + void writeTo(JSONStringer stringer) throws JSONException { + stringer.object(); + for (Map.Entry entry : this.nameValuePairs.entrySet()) { + stringer.key(entry.getKey()).value(entry.getValue()); + } + stringer.endObject(); + } + + /** + * Encodes the number as a JSON string. + * @param number a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return the encoded value + * @throws JSONException if an error occurs + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Number must be non-null"); + } + + double doubleValue = number.doubleValue(); + JSON.checkDouble(doubleValue); + + // the original returns "-0" instead of "-0.0" for negative zero + if (number.equals(NEGATIVE_ZERO)) { + return "-0"; + } + + long longValue = number.longValue(); + if (doubleValue == longValue) { + return Long.toString(longValue); + } + + return number.toString(); + } + + /** + * Encodes {@code data} as a JSON string. This applies quotes and any necessary + * character escaping. + * @param data the string to encode. Null will be interpreted as an empty string. + * @return the quoted value + */ + public static String quote(String data) { + if (data == null) { + return "\"\""; + } + try { + JSONStringer stringer = new JSONStringer(); + stringer.open(JSONStringer.Scope.NULL, ""); + stringer.value(data); + stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); + return stringer.toString(); + } + catch (JSONException e) { + throw new AssertionError(); + } + } + + /** + * Wraps the given object if necessary. + *

+ * If the object is null or , returns {@link #NULL}. If the object is a + * {@code JSONArray} or {@code JSONObject}, no wrapping is necessary. If the object is + * {@code NULL}, no wrapping is necessary. If the object is an array or + * {@code Collection}, returns an equivalent {@code JSONArray}. If the object is a + * {@code Map}, returns an equivalent {@code JSONObject}. If the object is a primitive + * wrapper type or {@code String}, returns the object. Otherwise if the object is from + * a {@code java} package, returns the result of {@code toString}. If wrapping fails, + * returns null. + * @param o the object to wrap + * @return the wrapped object + */ + @SuppressWarnings("rawtypes") + public static Object wrap(Object o) { + if (o == null) { + return NULL; + } + if (o instanceof JSONArray || o instanceof JSONObject) { + return o; + } + if (o.equals(NULL)) { + return o; + } + try { + if (o instanceof Collection) { + return new JSONArray((Collection) o); + } + else if (o.getClass().isArray()) { + return new JSONArray(o); + } + if (o instanceof Map) { + return new JSONObject((Map) o); + } + if (o instanceof Boolean || o instanceof Byte || o instanceof Character + || o instanceof Double || o instanceof Float || o instanceof Integer + || o instanceof Long || o instanceof Short || o instanceof String) { + return o; + } + if (o.getClass().getPackage().getName().startsWith("java.")) { + return o.toString(); + } + } + catch (Exception ignored) { + } + return null; + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONStringer.java b/src/json-shade/java/org/springframework/graal/json/JSONStringer.java new file mode 100644 index 000000000..3f6d98156 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONStringer.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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. + */ + +package org.springframework.graal.json; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most application + * developers should use those methods directly and disregard this API. For example:

+ * JSONObject object = ...
+ * String json = object.toString();
+ *

+ * Stringers only encode well-formed JSON strings. In particular: + *

    + *
  • The stringer must have exactly one top-level array or object. + *
  • Lexical scopes must be balanced: every call to {@link #array} must have a matching + * call to {@link #endArray} and every call to {@link #object} must have a matching call + * to {@link #endObject}. + *
  • Arrays may not contain keys (property names). + *
  • Objects must alternate keys (property names) and values. + *
  • Values are inserted with either literal {@link #value(Object) value} calls, or by + * nesting arrays or objects. + *
+ * Calls that would result in a malformed JSON string will fail with a + * {@link JSONException}. + *

+ * This class provides no facility for pretty-printing (ie. indenting) output. To encode + * indented output, use {@link JSONObject#toString(int)} or + * {@link JSONArray#toString(int)}. + *

+ * Some implementations of the API support at most 20 levels of nesting. Attempts to + * create more than 20 levels of nesting may fail with a {@link JSONException}. + *

+ * Each stringer may be used to encode a single top level value. Instances of this class + * are not thread safe. Although this class is nonfinal, it was not designed for + * inheritance and should not be subclassed. In particular, self-use by overrideable + * methods is not specified. See Effective Java Item 17, "Design and Document or + * inheritance or else prohibit it" for further information. + */ +public class JSONStringer { + + /** + * The output data, containing at most one top-level array or object. + */ + final StringBuilder out = new StringBuilder(); + + /** + * Lexical scoping elements within this stringer, necessary to insert the appropriate + * separator characters (ie. commas and colons) and to detect nesting errors. + */ + enum Scope { + + /** + * An array with no elements requires no separators or newlines before it is + * closed. + */ + EMPTY_ARRAY, + + /** + * An array with at least one value requires a comma and newline before the next + * element. + */ + NONEMPTY_ARRAY, + + /** + * An object with no keys or values requires no separators or newlines before it + * is closed. + */ + EMPTY_OBJECT, + + /** + * An object whose most recent element is a key. The next element must be a value. + */ + DANGLING_KEY, + + /** + * An object with at least one name/value pair requires a comma and newline before + * the next element. + */ + NONEMPTY_OBJECT, + + /** + * A special bracketless array needed by JSONStringer.join() and + * JSONObject.quote() only. Not used for JSON encoding. + */ + NULL + + } + + /** + * Unlike the original implementation, this stack isn't limited to 20 levels of + * nesting. + */ + private final List stack = new ArrayList<>(); + + /** + * A string containing a full set of spaces for a single level of indentation, or null + * for no pretty printing. + */ + private final String indent; + + public JSONStringer() { + this.indent = null; + } + + JSONStringer(int indentSpaces) { + char[] indentChars = new char[indentSpaces]; + Arrays.fill(indentChars, ' '); + this.indent = new String(indentChars); + } + + /** + * Begins encoding a new array. Each call to this method must be paired with a call to + * {@link #endArray}. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer array() throws JSONException { + return open(Scope.EMPTY_ARRAY, "["); + } + + /** + * Ends encoding the current array. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer endArray() throws JSONException { + return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); + } + + /** + * Begins encoding a new object. Each call to this method must be paired with a call + * to {@link #endObject}. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer object() throws JSONException { + return open(Scope.EMPTY_OBJECT, "{"); + } + + /** + * Ends encoding the current object. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer endObject() throws JSONException { + return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); + } + + /** + * Enters a new scope by appending any necessary whitespace and the given bracket. + * @param empty any necessary whitespace + * @param openBracket the open bracket + * @return this object + * @throws JSONException if processing of json failed + */ + JSONStringer open(Scope empty, String openBracket) throws JSONException { + if (this.stack.isEmpty() && this.out.length() > 0) { + throw new JSONException("Nesting problem: multiple top-level roots"); + } + beforeValue(); + this.stack.add(empty); + this.out.append(openBracket); + return this; + } + + /** + * Closes the current scope by appending any necessary whitespace and the given + * bracket. + * @param empty any necessary whitespace + * @param nonempty the current scope + * @param closeBracket the close bracket + * @return the JSON stringer + * @throws JSONException if processing of json failed + */ + JSONStringer close(Scope empty, Scope nonempty, String closeBracket) + throws JSONException { + Scope context = peek(); + if (context != nonempty && context != empty) { + throw new JSONException("Nesting problem"); + } + + this.stack.remove(this.stack.size() - 1); + if (context == nonempty) { + newline(); + } + this.out.append(closeBracket); + return this; + } + + /** + * Returns the value on the top of the stack. + * @return the scope + * @throws JSONException if processing of json failed + */ + private Scope peek() throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + return this.stack.get(this.stack.size() - 1); + } + + /** + * Replace the value on the top of the stack with the given value. + * @param topOfStack the scope at the top of the stack + */ + private void replaceTop(Scope topOfStack) { + this.stack.set(this.stack.size() - 1, topOfStack); + } + + /** + * Encodes {@code value}. + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, + * Long, Double or null. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer value(Object value) throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + + if (value instanceof JSONArray) { + ((JSONArray) value).writeTo(this); + return this; + + } + else if (value instanceof JSONObject) { + ((JSONObject) value).writeTo(this); + return this; + } + + beforeValue(); + + if (value == null || value instanceof Boolean || value == JSONObject.NULL) { + this.out.append(value); + + } + else if (value instanceof Number) { + this.out.append(JSONObject.numberToString((Number) value)); + + } + else { + string(value.toString()); + } + + return this; + } + + /** + * Encodes {@code value} to this stringer. + * @param value the value to encode + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer value(boolean value) throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + this.out.append(value); + return this; + } + + /** + * Encodes {@code value} to this stringer. + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer value(double value) throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + this.out.append(JSONObject.numberToString(value)); + return this; + } + + /** + * Encodes {@code value} to this stringer. + * @param value the value to encode + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer value(long value) throws JSONException { + if (this.stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + this.out.append(value); + return this; + } + + private void string(String value) { + this.out.append("\""); + for (int i = 0, length = value.length(); i < length; i++) { + char c = value.charAt(i); + + /* + * From RFC 4627, "All Unicode characters may be placed within the quotation + * marks except for the characters that must be escaped: quotation mark, + * reverse solidus, and the control characters (U+0000 through U+001F)." + */ + switch (c) { + case '"': + case '\\': + case '/': + this.out.append('\\').append(c); + break; + + case '\t': + this.out.append("\\t"); + break; + + case '\b': + this.out.append("\\b"); + break; + + case '\n': + this.out.append("\\n"); + break; + + case '\r': + this.out.append("\\r"); + break; + + case '\f': + this.out.append("\\f"); + break; + + default: + if (c <= 0x1F) { + this.out.append(String.format("\\u%04x", (int) c)); + } + else { + this.out.append(c); + } + break; + } + + } + this.out.append("\""); + } + + private void newline() { + if (this.indent == null) { + return; + } + + this.out.append("\n"); + for (int i = 0; i < this.stack.size(); i++) { + this.out.append(this.indent); + } + } + + /** + * Encodes the key (property name) to this stringer. + * @param name the name of the forthcoming value. May not be null. + * @return this stringer. + * @throws JSONException if processing of json failed + */ + public JSONStringer key(String name) throws JSONException { + if (name == null) { + throw new JSONException("Names must be non-null"); + } + beforeKey(); + string(name); + return this; + } + + /** + * Inserts any necessary separators and whitespace before a name. Also adjusts the + * stack to expect the key's value. + * @throws JSONException if processing of json failed + */ + private void beforeKey() throws JSONException { + Scope context = peek(); + if (context == Scope.NONEMPTY_OBJECT) { // first in object + this.out.append(','); + } + else if (context != Scope.EMPTY_OBJECT) { // not in an object! + throw new JSONException("Nesting problem"); + } + newline(); + replaceTop(Scope.DANGLING_KEY); + } + + /** + * Inserts any necessary separators and whitespace before a literal value, inline + * array, or inline object. Also adjusts the stack to expect either a closing bracket + * or another element. + * @throws JSONException if processing of json failed + */ + private void beforeValue() throws JSONException { + if (this.stack.isEmpty()) { + return; + } + + Scope context = peek(); + if (context == Scope.EMPTY_ARRAY) { // first in array + replaceTop(Scope.NONEMPTY_ARRAY); + newline(); + } + else if (context == Scope.NONEMPTY_ARRAY) { // another in array + this.out.append(','); + newline(); + } + else if (context == Scope.DANGLING_KEY) { // value for key + this.out.append(this.indent == null ? ":" : ": "); + replaceTop(Scope.NONEMPTY_OBJECT); + } + else if (context != Scope.NULL) { + throw new JSONException("Nesting problem"); + } + } + + /** + * Returns the encoded JSON string. + *

+ * If invoked with unterminated arrays or unclosed objects, this method's return value + * is undefined. + *

+ * Warning: although it contradicts the general contract of + * {@link Object#toString}, this method returns null if the stringer contains no data. + * @return the encoded JSON string. + */ + @Override + public String toString() { + return this.out.length() == 0 ? null : this.out.toString(); + } + +} diff --git a/src/json-shade/java/org/springframework/graal/json/JSONTokener.java b/src/json-shade/java/org/springframework/graal/json/JSONTokener.java new file mode 100644 index 000000000..64f20f632 --- /dev/null +++ b/src/json-shade/java/org/springframework/graal/json/JSONTokener.java @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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. + */ + +package org.springframework.graal.json; + +// Note: this class was written without inspecting the non-free org.json source code. + +/** + * Parses a JSON (RFC 4627) encoded + * string into the corresponding object. Most clients of this class will use only need the + * {@link #JSONTokener(String) constructor} and {@link #nextValue} method. Example usage: + *

+ * String json = "{"
+ *         + "  \"query\": \"Pizza\", "
+ *         + "  \"locations\": [ 94043, 90210 ] "
+ *         + "}";
+ *
+ * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ * String query = object.getString("query");
+ * JSONArray locations = object.getJSONArray("locations");
+ *

+ * For best interoperability and performance use JSON that complies with RFC 4627, such as + * that generated by {@link JSONStringer}. For legacy reasons this parser is lenient, so a + * successful parse does not indicate that the input string was valid JSON. All of the + * following syntax errors will be ignored: + *

    + *
  • End of line comments starting with {@code //} or {@code #} and ending with a + * newline character. + *
  • C-style comments starting with {@code /*} and ending with {@code *}{@code /}. Such + * comments may not be nested. + *
  • Strings that are unquoted or {@code 'single quoted'}. + *
  • Hexadecimal integers prefixed with {@code 0x} or {@code 0X}. + *
  • Octal integers prefixed with {@code 0}. + *
  • Array elements separated by {@code ;}. + *
  • Unnecessary array separators. These are interpreted as if null was the omitted + * value. + *
  • Key-value pairs separated by {@code =} or {@code =>}. + *
  • Key-value pairs separated by {@code ;}. + *
+ *

+ * Each tokener may be used to parse a single JSON string. Instances of this class are not + * thread safe. Although this class is nonfinal, it was not designed for inheritance and + * should not be subclassed. In particular, self-use by overrideable methods is not + * specified. See Effective Java Item 17, "Design and Document or inheritance or + * else prohibit it" for further information. + */ +public class JSONTokener { + + /** + * The input JSON. + */ + private final String in; + + /** + * The index of the next character to be returned by {@link #next}. When the input is + * exhausted, this equals the input's length. + */ + private int pos; + + /** + * @param in JSON encoded string. Null is not permitted and will yield a tokener that + * throws {@code NullPointerExceptions} when methods are called. + */ + public JSONTokener(String in) { + // consume an optional byte order mark (BOM) if it exists + if (in != null && in.startsWith("\ufeff")) { + in = in.substring(1); + } + this.in = in; + } + + /** + * Returns the next value from the input. + * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long, + * Double or {@link JSONObject#NULL}. + * @throws JSONException if the input is malformed. + */ + public Object nextValue() throws JSONException { + int c = nextCleanInternal(); + switch (c) { + case -1: + throw syntaxError("End of input"); + + case '{': + return readObject(); + + case '[': + return readArray(); + + case '\'': + case '"': + return nextString((char) c); + + default: + this.pos--; + return readLiteral(); + } + } + + private int nextCleanInternal() throws JSONException { + while (this.pos < this.in.length()) { + int c = this.in.charAt(this.pos++); + switch (c) { + case '\t': + case ' ': + case '\n': + case '\r': + continue; + + case '/': + if (this.pos == this.in.length()) { + return c; + } + + char peek = this.in.charAt(this.pos); + switch (peek) { + case '*': + // skip a /* c-style comment */ + this.pos++; + int commentEnd = this.in.indexOf("*/", this.pos); + if (commentEnd == -1) { + throw syntaxError("Unterminated comment"); + } + this.pos = commentEnd + 2; + continue; + + case '/': + // skip a // end-of-line comment + this.pos++; + skipToEndOfLine(); + continue; + + default: + return c; + } + + case '#': + /* + * Skip a # hash end-of-line comment. The JSON RFC doesn't specify this + * behavior, but it's required to parse existing documents. See + * http://b/2571423. + */ + skipToEndOfLine(); + continue; + + default: + return c; + } + } + + return -1; + } + + /** + * Advances the position until after the next newline character. If the line is + * terminated by "\r\n", the '\n' must be consumed as whitespace by the caller. + */ + private void skipToEndOfLine() { + for (; this.pos < this.in.length(); this.pos++) { + char c = this.in.charAt(this.pos); + if (c == '\r' || c == '\n') { + this.pos++; + break; + } + } + } + + /** + * Returns the string up to but not including {@code quote}, unescaping any character + * escape sequences encountered along the way. The opening quote should have already + * been read. This consumes the closing quote, but does not include it in the returned + * string. + * @param quote either ' or ". + * @return the string up to but not including {@code quote} + * @throws NumberFormatException if any unicode escape sequences are malformed. + * @throws JSONException if processing of json failed + */ + public String nextString(char quote) throws JSONException { + /* + * For strings that are free of escape sequences, we can just extract the result + * as a substring of the input. But if we encounter an escape sequence, we need to + * use a StringBuilder to compose the result. + */ + StringBuilder builder = null; + + /* the index of the first character not yet appended to the builder. */ + int start = this.pos; + + while (this.pos < this.in.length()) { + int c = this.in.charAt(this.pos++); + if (c == quote) { + if (builder == null) { + // a new string avoids leaking memory + return new String(this.in.substring(start, this.pos - 1)); + } + else { + builder.append(this.in, start, this.pos - 1); + return builder.toString(); + } + } + + if (c == '\\') { + if (this.pos == this.in.length()) { + throw syntaxError("Unterminated escape sequence"); + } + if (builder == null) { + builder = new StringBuilder(); + } + builder.append(this.in, start, this.pos - 1); + builder.append(readEscapeCharacter()); + start = this.pos; + } + } + + throw syntaxError("Unterminated string"); + } + + /** + * Unescapes the character identified by the character or characters that immediately + * follow a backslash. The backslash '\' should have already been read. This supports + * both unicode escapes "u000A" and two-character escapes "\n". + * @return the unescaped char + * @throws NumberFormatException if any unicode escape sequences are malformed. + * @throws JSONException if processing of json failed + */ + private char readEscapeCharacter() throws JSONException { + char escaped = this.in.charAt(this.pos++); + switch (escaped) { + case 'u': + if (this.pos + 4 > this.in.length()) { + throw syntaxError("Unterminated escape sequence"); + } + String hex = this.in.substring(this.pos, this.pos + 4); + this.pos += 4; + return (char) Integer.parseInt(hex, 16); + + case 't': + return '\t'; + + case 'b': + return '\b'; + + case 'n': + return '\n'; + + case 'r': + return '\r'; + + case 'f': + return '\f'; + + case '\'': + case '"': + case '\\': + default: + return escaped; + } + } + + /** + * Reads a null, boolean, numeric or unquoted string literal value. Numeric values + * will be returned as an Integer, Long, or Double, in that order of preference. + * @return a literal value + * @throws JSONException if processing of json failed + */ + private Object readLiteral() throws JSONException { + String literal = nextToInternal("{}[]/\\:,=;# \t\f"); + + if (literal.isEmpty()) { + throw syntaxError("Expected literal value"); + } + else if ("null".equalsIgnoreCase(literal)) { + return JSONObject.NULL; + } + else if ("true".equalsIgnoreCase(literal)) { + return Boolean.TRUE; + } + else if ("false".equalsIgnoreCase(literal)) { + return Boolean.FALSE; + } + + /* try to parse as an integral type... */ + if (literal.indexOf('.') == -1) { + int base = 10; + String number = literal; + if (number.startsWith("0x") || number.startsWith("0X")) { + number = number.substring(2); + base = 16; + } + else if (number.startsWith("0") && number.length() > 1) { + number = number.substring(1); + base = 8; + } + try { + long longValue = Long.parseLong(number, base); + if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { + return (int) longValue; + } + else { + return longValue; + } + } + catch (NumberFormatException e) { + /* + * This only happens for integral numbers greater than Long.MAX_VALUE, + * numbers in exponential form (5e-10) and unquoted strings. Fall through + * to try floating point. + */ + } + } + + /* ...next try to parse as a floating point... */ + try { + return Double.valueOf(literal); + } + catch (NumberFormatException ignored) { + } + + /* ... finally give up. We have an unquoted string */ + return new String(literal); // a new string avoids leaking memory + } + + /** + * Returns the string up to but not including any of the given characters or a newline + * character. This does not consume the excluded character. + * @return the string up to but not including any of the given characters or a newline + * character + */ + private String nextToInternal(String excluded) { + int start = this.pos; + for (; this.pos < this.in.length(); this.pos++) { + char c = this.in.charAt(this.pos); + if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { + return this.in.substring(start, this.pos); + } + } + return this.in.substring(start); + } + + /** + * Reads a sequence of key/value pairs and the trailing closing brace '}' of an + * object. The opening brace '{' should have already been read. + * @return an object + * @throws JSONException if processing of json failed + */ + private JSONObject readObject() throws JSONException { + JSONObject result = new JSONObject(); + + /* Peek to see if this is the empty object. */ + int first = nextCleanInternal(); + if (first == '}') { + return result; + } + else if (first != -1) { + this.pos--; + } + + while (true) { + Object name = nextValue(); + if (!(name instanceof String)) { + if (name == null) { + throw syntaxError("Names cannot be null"); + } + else { + throw syntaxError("Names must be strings, but " + name + + " is of type " + name.getClass().getName()); + } + } + + /* + * Expect the name/value separator to be either a colon ':', an equals sign + * '=', or an arrow "=>". The last two are bogus but we include them because + * that's what the original implementation did. + */ + int separator = nextCleanInternal(); + if (separator != ':' && separator != '=') { + throw syntaxError("Expected ':' after " + name); + } + if (this.pos < this.in.length() && this.in.charAt(this.pos) == '>') { + this.pos++; + } + + result.put((String) name, nextValue()); + + switch (nextCleanInternal()) { + case '}': + return result; + case ';': + case ',': + continue; + default: + throw syntaxError("Unterminated object"); + } + } + } + + /** + * Reads a sequence of values and the trailing closing brace ']' of an array. The + * opening brace '[' should have already been read. Note that "[]" yields an empty + * array, but "[,]" returns a two-element array equivalent to "[null,null]". + * @return an array + * @throws JSONException if processing of json failed + */ + private JSONArray readArray() throws JSONException { + JSONArray result = new JSONArray(); + + /* to cover input that ends with ",]". */ + boolean hasTrailingSeparator = false; + + while (true) { + switch (nextCleanInternal()) { + case -1: + throw syntaxError("Unterminated array"); + case ']': + if (hasTrailingSeparator) { + result.put(null); + } + return result; + case ',': + case ';': + /* A separator without a value first means "null". */ + result.put(null); + hasTrailingSeparator = true; + continue; + default: + this.pos--; + } + + result.put(nextValue()); + + switch (nextCleanInternal()) { + case ']': + return result; + case ',': + case ';': + hasTrailingSeparator = true; + continue; + default: + throw syntaxError("Unterminated array"); + } + } + } + + /** + * Returns an exception containing the given message plus the current position and the + * entire input string. + * @param message the message + * @return an exception + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this); + } + + /** + * Returns the current position and the entire input string. + * @return the current position and the entire input string. + */ + @Override + public String toString() { + // consistent with the original implementation + return " at character " + this.pos + " of " + this.in; + } + + /* + * Legacy APIs. + * + * None of the methods below are on the critical path of parsing JSON documents. They + * exist only because they were exposed by the original implementation and may be used + * by some clients. + */ + + public boolean more() { + return this.pos < this.in.length(); + } + + public char next() { + return this.pos < this.in.length() ? this.in.charAt(this.pos++) : '\0'; + } + + public char next(char c) throws JSONException { + char result = next(); + if (result != c) { + throw syntaxError("Expected " + c + " but was " + result); + } + return result; + } + + public char nextClean() throws JSONException { + int nextCleanInt = nextCleanInternal(); + return nextCleanInt == -1 ? '\0' : (char) nextCleanInt; + } + + public String next(int length) throws JSONException { + if (this.pos + length > this.in.length()) { + throw syntaxError(length + " is out of bounds"); + } + String result = this.in.substring(this.pos, this.pos + length); + this.pos += length; + return result; + } + + public String nextTo(String excluded) { + if (excluded == null) { + throw new NullPointerException("excluded == null"); + } + return nextToInternal(excluded).trim(); + } + + public String nextTo(char excluded) { + return nextToInternal(String.valueOf(excluded)).trim(); + } + + public void skipPast(String thru) { + int thruStart = this.in.indexOf(thru, this.pos); + this.pos = thruStart == -1 ? this.in.length() : (thruStart + thru.length()); + } + + public char skipTo(char to) { + int index = this.in.indexOf(to, this.pos); + if (index != -1) { + this.pos = index; + return to; + } + else { + return '\0'; + } + } + + public void back() { + if (--this.pos == -1) { + this.pos = 0; + } + } + + public static int dehexchar(char hex) { + if (hex >= '0' && hex <= '9') { + return hex - '0'; + } + else if (hex >= 'A' && hex <= 'F') { + return hex - 'A' + 10; + } + else if (hex >= 'a' && hex <= 'f') { + return hex - 'a' + 10; + } + else { + return -1; + } + } + +} diff --git a/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java b/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java new file mode 100644 index 000000000..be97bad53 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/CleanerJava6Substitution.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project 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: + * + * 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. + */ +package io.netty.util.internal.svm; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "io.netty.util.internal.CleanerJava6",onlyWith= NettyIsAround.class) +final class CleanerJava6Substitution { + private CleanerJava6Substitution() { + } + + @Alias + @RecomputeFieldValue( + kind = RecomputeFieldValue.Kind.FieldOffset, + declClassName = "java.nio.DirectByteBuffer", + name = "cleaner") + private static long CLEANER_FIELD_OFFSET; +} diff --git a/src/main/java/io/netty/util/internal/svm/NettyIsAround.java b/src/main/java/io/netty/util/internal/svm/NettyIsAround.java new file mode 100644 index 000000000..002aec085 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/NettyIsAround.java @@ -0,0 +1,17 @@ +package io.netty.util.internal.svm; + +import java.util.function.BooleanSupplier; + +public class NettyIsAround implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + try { + Class.forName("io.netty.util.internal.CleanerJava6"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + +} diff --git a/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java b/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java new file mode 100644 index 000000000..d4156e2a8 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/PlatformDependent0Substitution.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project 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: + * + * 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. + */ +package io.netty.util.internal.svm; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "io.netty.util.internal.PlatformDependent0",onlyWith=NettyIsAround.class) +final class PlatformDependent0Substitution { + private PlatformDependent0Substitution() { + } + + @Alias + @RecomputeFieldValue( + kind = RecomputeFieldValue.Kind.FieldOffset, + declClassName = "java.nio.Buffer", + name = "address") + private static long ADDRESS_FIELD_OFFSET; +} diff --git a/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java b/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java new file mode 100644 index 000000000..df49877db --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/PlatformDependentSubstitution.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project 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: + * + * 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. + */ +package io.netty.util.internal.svm; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "io.netty.util.internal.PlatformDependent",onlyWith=NettyIsAround.class) +final class PlatformDependentSubstitution { + private PlatformDependentSubstitution() { + } + + /** + * The class PlatformDependent caches the byte array base offset by reading the + * field from PlatformDependent0. The automatic recomputation of Substrate VM + * correctly recomputes the field in PlatformDependent0, but since the caching + * in PlatformDependent happens during image building, the non-recomputed value + * is cached. + */ + @Alias + @RecomputeFieldValue( + kind = RecomputeFieldValue.Kind.ArrayBaseOffset, + declClass = byte[].class) + private static long BYTE_ARRAY_BASE_OFFSET; +} diff --git a/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java b/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java new file mode 100644 index 000000000..f14bd3276 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/UnsafeRefArrayAccessSubstitution.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project 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: + * + * 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. + */ +package io.netty.util.internal.svm; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "io.netty.util.internal.shaded.org.jctools.util.UnsafeRefArrayAccess", onlyWith=NettyIsAround.class) +final class UnsafeRefArrayAccessSubstitution { + private UnsafeRefArrayAccessSubstitution() { + } + + @Alias + @RecomputeFieldValue( + kind = RecomputeFieldValue.Kind.ArrayIndexShift, + declClass = Object[].class) + public static int REF_ELEMENT_SHIFT; +} diff --git a/src/main/java/io/netty/util/internal/svm/package-info.java b/src/main/java/io/netty/util/internal/svm/package-info.java new file mode 100644 index 000000000..e9b82ec03 --- /dev/null +++ b/src/main/java/io/netty/util/internal/svm/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2019 The Netty Project + * + * The Netty Project 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: + * + * 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. + */ + +/** + * SVM substitutions for classes that will cause trouble while compiling + * into native image. + */ +package io.netty.util.internal.svm; diff --git a/src/main/java/org/springframework/dao/DataAccessException.java b/src/main/java/org/springframework/dao/DataAccessException.java new file mode 100644 index 000000000..187ec4470 --- /dev/null +++ b/src/main/java/org/springframework/dao/DataAccessException.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2017 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. + */ + +package org.springframework.dao; + +import org.springframework.core.NestedRuntimeException; +import org.springframework.lang.Nullable; + +/** + * Root of the hierarchy of data access exceptions discussed in + * Expert One-On-One J2EE Design and Development. + * Please see Chapter 9 of this book for detailed discussion of the + * motivation for this package. + * + *

This exception hierarchy aims to let user code find and handle the + * kind of error encountered without knowing the details of the particular + * data access API in use (e.g. JDBC). Thus it is possible to react to an + * optimistic locking failure without knowing that JDBC is being used. + * + *

As this class is a runtime exception, there is no need for user code + * to catch it or subclasses if any error is to be considered fatal + * (the usual case). + * + * @author Rod Johnson + */ +@SuppressWarnings("serial") +public abstract class DataAccessException extends NestedRuntimeException { + + /** + * Constructor for DataAccessException. + * @param msg the detail message + */ + public DataAccessException(String msg) { + super(msg); + } + + /** + * Constructor for DataAccessException. + * @param msg the detail message + * @param cause the root cause (usually from using a underlying + * data access API such as JDBC) + */ + public DataAccessException(@Nullable String msg, @Nullable Throwable cause) { + super(msg, cause); + } + +} diff --git a/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationDescriptor.java b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationDescriptor.java new file mode 100644 index 000000000..8e3f08164 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationDescriptor.java @@ -0,0 +1,117 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.buildtimeinit; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Andy Clement + */ +public class InitializationDescriptor { + + private final List buildtimeClasses; + + private final List buildtimePackages; + + private final List runtimeClasses; + + private final List runtimePackages; + + public InitializationDescriptor() { + this.buildtimeClasses = new ArrayList<>(); + this.buildtimePackages = new ArrayList<>(); + this.runtimeClasses = new ArrayList<>(); + this.runtimePackages = new ArrayList<>(); + } + + public InitializationDescriptor(InitializationDescriptor metadata) { + this.buildtimeClasses = new ArrayList<>(metadata.buildtimeClasses); + this.buildtimePackages = new ArrayList<>(metadata.buildtimePackages); + this.runtimeClasses = new ArrayList<>(metadata.runtimeClasses); + this.runtimePackages = new ArrayList<>(metadata.runtimePackages); + } + + public List getBuildtimeClasses() { + return this.buildtimeClasses; + } + + public List getBuildtimePackages() { + return this.buildtimePackages; + } + + public List getRuntimeClasses() { + return this.runtimeClasses; + } + + public List getRuntimePackages() { + return this.runtimePackages; + } + + public void addBuildtimeClass(String clazz) { + this.buildtimeClasses.add(clazz); + } + + public void addBuildtimePackage(String pkg) { + this.buildtimePackages.add(pkg); + } + + public void addRuntimeClass(String clazz) { + this.runtimeClasses.add(clazz); + } + + public void addRuntimePackage(String pkg) { + this.runtimePackages.add(pkg); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(String.format("InitializationDescriptor #%s buildtime-classes #%s buildtime-packages #%s runtime-classes #%s runtime-packages\n", + buildtimeClasses.size(), buildtimePackages.size(), runtimeClasses.size(), runtimePackages.size())); +// result.append("buildtime classes:\n"); +// this.buildtimeClasses.forEach(cd -> { +// result.append(String.format("%s\n",cd)); +// }); +// result.append("buildtime packages:\n"); +// this.buildtimePackages.forEach(cd -> { +// result.append(String.format("%s\n",cd)); +// }); +// result.append("runtime classes:\n"); +// this.runtimeClasses.forEach(cd -> { +// result.append(String.format("%s\n",cd)); +// }); +// result.append("runtime packages:\n"); +// this.runtimePackages.forEach(cd -> { +// result.append(String.format("%s\n",cd)); +// }); + return result.toString(); + } + + public boolean isEmpty() { + return buildtimeClasses.isEmpty() && runtimeClasses.isEmpty(); + } + + public static InitializationDescriptor of(String jsonString) { + try { + return InitializationJsonMarshaller.read(jsonString); + } catch (Exception e) { + throw new IllegalStateException("Unable to read json:\n"+jsonString, e); + } + } + +} diff --git a/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonConverter.java b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonConverter.java new file mode 100644 index 000000000..14265d0ef --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonConverter.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.buildtimeinit; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Converter to change resource descriptor objects into JSON objects + * + * @author Andy Clement + */ +class InitializationJsonConverter { + + public JSONObject toJsonArray(InitializationDescriptor metadata) throws Exception { + JSONObject object = new JSONObject(); + JSONArray jsonArray = new JSONArray(); + for (String p : metadata.getBuildtimeClasses()) { + jsonArray.put(toClassJsonObject(p)); + } + for (String p : metadata.getBuildtimePackages()) { + jsonArray.put(toPackageJsonObject(p)); + } + object.put("buildTimeInitialization", jsonArray); + for (String p : metadata.getRuntimeClasses()) { + jsonArray.put(toClassJsonObject(p)); + } + for (String p : metadata.getRuntimePackages()) { + jsonArray.put(toPackageJsonObject(p)); + } + object.put("runtimeInitialization", jsonArray); + return object; + } + + public JSONObject toPackageJsonObject(String pattern) throws Exception { + JSONObject object = new JSONObject(); + object.put("package", pattern); + return object; + } + + public JSONObject toClassJsonObject(String pattern) throws Exception { + JSONObject object = new JSONObject(); + object.put("class", pattern); + return object; + } +} diff --git a/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonMarshaller.java b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonMarshaller.java new file mode 100644 index 000000000..a319ea7b2 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/buildtimeinit/InitializationJsonMarshaller.java @@ -0,0 +1,131 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.buildtimeinit; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Marshaller to write {@link InitializationDescriptor} as JSON. + * + * @author Andy Clement + */ +public class InitializationJsonMarshaller { + + private static final int BUFFER_SIZE = 4098; + + public void write(InitializationDescriptor metadata, OutputStream outputStream) + throws IOException { + try { + InitializationJsonConverter converter = new InitializationJsonConverter(); + JSONObject jsonObject = converter.toJsonArray(metadata); + outputStream.write(jsonObject.toString(2).getBytes(StandardCharsets.UTF_8)); + } + catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException) ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new IllegalStateException(ex); + } + } + + public static InitializationDescriptor read(String input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + return read(bais); + } + } + + public static InitializationDescriptor read(byte[] input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input)) { + return read(bais); + } + } + + public static InitializationDescriptor read(InputStream inputStream) throws Exception { + InitializationDescriptor metadata = toDelayInitDescriptor(new JSONObject(toString(inputStream))); + return metadata; + } + + private static InitializationDescriptor toDelayInitDescriptor(JSONObject object) throws Exception { + InitializationDescriptor rd = new InitializationDescriptor(); + JSONArray array = object.getJSONArray("buildTimeInitialization"); + for (int i=0;i listOfParameterTypes = null; +// if (parameterTypes != null) { +// listOfParameterTypes = new ArrayList<>(); +// for (int i=0;i proxyDescriptors; + + public ProxiesDescriptor() { + this.proxyDescriptors = new ArrayList<>(); + } + + public ProxiesDescriptor(ProxiesDescriptor metadata) { + this.proxyDescriptors = new ArrayList<>(metadata.proxyDescriptors); + } + + public List getProxyDescriptors() { + return this.proxyDescriptors; + } + + public void add(ProxyDescriptor proxyDescriptor) { + this.proxyDescriptors.add(proxyDescriptor); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(String.format("ProxyDescriptors #%s\n",proxyDescriptors.size())); + this.proxyDescriptors.forEach(cd -> { + result.append(String.format("%s: \n",cd)); + }); + return result.toString(); + } + + public boolean isEmpty() { + return proxyDescriptors.isEmpty(); + } + + public static ProxiesDescriptor of(String jsonString) { + try { + return ProxiesDescriptorJsonMarshaller.read(jsonString); + } catch (Exception e) { + throw new IllegalStateException("Unable to read json:\n"+jsonString, e); + } + } + + public void consume(Consumer> consumer) { + proxyDescriptors.stream().forEach(pd -> consumer.accept(pd.getInterfaces())); + } + +// public boolean hasClassDescriptor(String string) { +// for (ProxyDescriptor cd: classDescriptors) { +// if (cd.getName().equals(string)) { +// return true; +// } +// } +// return false; +// } +// +// public ProxyDescriptor getClassDescriptor(String type) { +// for (ProxyDescriptor cd: classDescriptors) { +// if (cd.getName().equals(type)) { +// return cd; +// } +// } +// return null; +// } + +} diff --git a/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonConverter.java b/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonConverter.java new file mode 100644 index 000000000..f540d40fa --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonConverter.java @@ -0,0 +1,92 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.proxies; + +import java.util.List; + +import org.springframework.graal.json.JSONArray; + +/** + * Converter to change reflection descriptor objects into JSON objects + * + * @author Andy Clement + */ +class ProxiesDescriptorJsonConverter { + + public JSONArray toJsonArray(ProxiesDescriptor metadata) throws Exception { + JSONArray jsonArray = new JSONArray(); + for (ProxyDescriptor cd : metadata.getProxyDescriptors()) { + jsonArray.put(toJsonArray(cd)); + } + return jsonArray; + } + + public JSONArray toJsonArray(ProxyDescriptor pd) throws Exception { + JSONArray jsonArray = new JSONArray(); + List interfaces = pd.getInterfaces(); + for (String intface: interfaces) { + jsonArray.put(intface); + } + return jsonArray; +// JSONObject jsonObject = new JSONObject(); +// jsonObject.put("name", cd.getName()); +// Set flags = cd.getFlags(); +// if (flags != null) { +// for (Flag flag: Flag.values()) { +// if (flags.contains(flag)) { +// putTrueFlag(jsonObject,flag.name()); +// } +// } +// } +// List fds = cd.getFields(); +// if (fds != null) { +// JSONArray fieldJsonArray = new JSONArray(); +// for (FieldDescriptor fd: fds) { +// JSONObject fieldjo = new JSONObject(); +// fieldjo.put("name", fd.getName()); +// if (fd.isAllowWrite()) { +// fieldjo.put("allowWrite", "true"); +// } +// fieldJsonArray.put(fieldjo); +// } +// jsonObject.put("fields", fieldJsonArray); +// } +// List mds = cd.getMethods(); +// if (mds != null) { +// JSONArray methodsJsonArray = new JSONArray(); +// for (MethodDescriptor md: mds) { +// JSONObject methodJsonObject = new JSONObject(); +// methodJsonObject.put("name", md.getName()); +// List parameterTypes = md.getParameterTypes(); +// JSONArray parameterArray = new JSONArray(); +// if (parameterTypes != null) { +// for (String pt: parameterTypes) { +// parameterArray.put(pt); +// } +// } +// methodJsonObject.put("parameterTypes",parameterArray); +// methodsJsonArray.put(methodJsonObject); +// } +// jsonObject.put("methods", methodsJsonArray); +// } +// return jsonObject; + } + +// private void putTrueFlag(JSONObject jsonObject, String name) throws Exception { +// jsonObject.put(name, true); +// } +} diff --git a/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonMarshaller.java b/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonMarshaller.java new file mode 100644 index 000000000..413762dd3 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/proxies/ProxiesDescriptorJsonMarshaller.java @@ -0,0 +1,141 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.proxies; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.graal.json.JSONArray; + +/** + * Marshaller to write {@link ProxiesDescriptor} as JSON. + * + * @author Andy Clement + */ +public class ProxiesDescriptorJsonMarshaller { + + private static final int BUFFER_SIZE = 4098; + + public void write(ProxiesDescriptor metadata, OutputStream outputStream) + throws IOException { + try { + ProxiesDescriptorJsonConverter converter = new ProxiesDescriptorJsonConverter(); + JSONArray jsonArray = converter.toJsonArray(metadata); + outputStream.write(jsonArray.toString(2).getBytes(StandardCharsets.UTF_8)); + } + catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException) ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new IllegalStateException(ex); + } + } + + public static ProxiesDescriptor read(String input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + return read(bais); + } + } + + public static ProxiesDescriptor read(byte[] input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input)) { + return read(bais); + } + } + + public static ProxiesDescriptor read(InputStream inputStream) throws Exception { + ProxiesDescriptor metadata = toProxiesDescriptor(new JSONArray(toString(inputStream))); + return metadata; + } + + private static ProxiesDescriptor toProxiesDescriptor(JSONArray array) throws Exception { + ProxiesDescriptor pds = new ProxiesDescriptor(); + for (int i=0;i interfaces = new ArrayList<>(); + for (int i=0;i listOfParameterTypes = null; +// if (parameterTypes != null) { +// listOfParameterTypes = new ArrayList<>(); +// for (int i=0;i { + + private List interfaces; // e.g. java.io.Serializable + + ProxyDescriptor() { + } + + ProxyDescriptor(List interfaces) { + this.interfaces = interfaces; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProxyDescriptor other = (ProxyDescriptor) o; + boolean result = true; + result = result && nullSafeEquals(this.interfaces, other.interfaces); + return result; + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(this.interfaces); + return result; + } + + private boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + return o1.equals(o2); + } + + private int nullSafeHashCode(Object o) { + return (o != null) ? o.hashCode() : 0; + } + + @Override + public String toString() { + StringBuilder string = new StringBuilder(); + buildToStringProperty(string, "interfaces", this.interfaces); + return string.toString(); + } + + protected void buildToStringProperty(StringBuilder string, String property, Object value) { + if (value != null) { + string.append(" ").append(property).append(":").append(value); + } + } + + @Override + public int compareTo(ProxyDescriptor o) { + List l = this.interfaces; + List r = o.interfaces; + if (l.size() != r.size()) { + return l.size() - r.size(); + } + for (int i = 0; i < l.size(); i++) { + int cmpTo = l.get(i).compareTo(r.get(i)); + if (cmpTo != 0) { + return cmpTo; + } + } + return 0; // equal! + } + + public static ProxyDescriptor of(List interfaces) { + ProxyDescriptor pd = new ProxyDescriptor(); + pd.setInterfaces(interfaces); + return pd; + } + + public List getInterfaces() { + return this.interfaces; + } + + public void setInterfaces(List interfaces) { + this.interfaces = new ArrayList<>(); + this.interfaces.addAll(interfaces); + } + + /** + * Used when new data is to be added to an already existing class descriptor + * (additional members, flag settings). + * + * @param cd the ClassDescriptor to merge into this one + */ +// public void merge(ProxyDescriptor cd) { +// for (Flag flag : cd.getFlags()) { +// this.setFlag(flag); +// } +// for (FieldDescriptor fd : cd.getFields()) { +// FieldDescriptor existingSimilarOne = null; +// for (FieldDescriptor existingFd: getFields()) { +// if (existingFd.getName().equals(fd.getName())) { +// existingSimilarOne = existingFd; +// break; +// } +// } +// if (existingSimilarOne != null) { +// if (fd.isAllowWrite()) { +// existingSimilarOne.setAllowWrite(true); +// } +// } else { +// addFieldDescriptor(fd); +// } +// } +// for (MethodDescriptor methodDescriptor : cd.getMethods()) { +// if (!containsMethodDescriptor(methodDescriptor)) { +// addMethodDescriptor(methodDescriptor); +// } +// } +// } + + public boolean containsInterface(String intface) { + return interfaces.contains(intface); + } + +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/ClassDescriptor.java b/src/main/java/org/springframework/graal/domain/reflect/ClassDescriptor.java new file mode 100644 index 000000000..2087d47da --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/ClassDescriptor.java @@ -0,0 +1,232 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.reflect; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +/** + * Reflection information about a single class. + * + * @author Andy Clement + * @see ReflectionDescriptor + */ +public final class ClassDescriptor implements Comparable { + + private String name; // e.g. java.lang.Class + + private List fields; + + private List methods; // includes constructors "" + + private Set flags; // Inclusion in list indicates they are set + + public enum Flag { + allPublicFields, // + allDeclaredFields, // + allDeclaredConstructors, // + allPublicConstructors, // + allDeclaredMethods, // + allPublicMethods, // + allDeclaredClasses, // + allPublicClasses; + } + + ClassDescriptor() { + } + + ClassDescriptor(String name, List fields, List methods, Set flags) { + this.name = name; + this.fields = fields; + this.methods = methods; + this.flags = flags; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClassDescriptor other = (ClassDescriptor) o; + boolean result = true; + result = result && nullSafeEquals(this.name, other.name); + result = result && nullSafeEquals(this.flags, other.flags); + result = result && nullSafeEquals(this.fields, other.fields); + result = result && nullSafeEquals(this.methods, other.methods); + return result; + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(this.name); + result = 31 * result + nullSafeHashCode(this.flags); + result = 31 * result + nullSafeHashCode(this.fields); + result = 31 * result + nullSafeHashCode(this.methods); + return result; + } + + private boolean nullSafeEquals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null || o2 == null) { + return false; + } + return o1.equals(o2); + } + + private int nullSafeHashCode(Object o) { + return (o != null) ? o.hashCode() : 0; + } + + @Override + public String toString() { + StringBuilder string = new StringBuilder(this.name); + buildToStringProperty(string, "setFlags", this.flags); + buildToStringProperty(string, "fields", this.fields); + buildToStringProperty(string, "methods", this.methods); + return string.toString(); + } + + protected void buildToStringProperty(StringBuilder string, String property, Object value) { + if (value != null) { + string.append(" ").append(property).append(":").append(value); + } + } + + @Override + public int compareTo(ClassDescriptor o) { + return getName().compareTo(o.getName()); + } + + public Set getFlags() { + return this.flags; + } + + public List getFields() { + return this.fields; + } + + public List getMethods() { + return this.methods; + } + + public static ClassDescriptor of(String name) { + ClassDescriptor cd = new ClassDescriptor(); + cd.setName(name); + return cd; + } + +// public static ClassDescriptor newGroup(String name, String type, String sourceType, +// String sourceMethod) { +// return new ClassDescriptor(ItemType.GROUP, name, null, type, sourceType, +// sourceMethod, null, null, null); +// } +// +// public static ClassDescriptor newProperty(String prefix, String name, String type, +// String sourceType, String sourceMethod, String description, +// Object defaultValue, ItemDeprecation deprecation) { +// return new ClassDescriptor(ItemType.PROPERTY, prefix, name, type, sourceType, +// sourceMethod, description, defaultValue, deprecation); +// } +// +// public static String newItemMetadataPrefix(String prefix, String suffix) { +// return prefix.toLowerCase(Locale.ENGLISH) +// + ReflectionDescriptor.toDashedCase(suffix); +// } + + public void setFlag(Flag f) { + if (flags == null) { + flags = new TreeSet<>(); + } + flags.add(f); + } + + public void addMethodDescriptor(MethodDescriptor methodDescriptor) { + if (methods == null) { + methods = new ArrayList<>(); + } + methods.add(methodDescriptor); + } + + public void addFieldDescriptor(FieldDescriptor fieldDescriptor) { + if (fields == null) { + fields = new ArrayList<>(); + } + fields.add(fieldDescriptor); + } + + /** + * Used when new data is to be added to an already existing class descriptor (additional members, flag settings). + * + * @param cd the ClassDescriptor to merge into this one + */ + public void merge(ClassDescriptor cd) { + for (Flag flag : cd.getFlags()) { + this.setFlag(flag); + } + for (FieldDescriptor fd : cd.getFields()) { + FieldDescriptor existingSimilarOne = null; + for (FieldDescriptor existingFd: getFields()) { + if (existingFd.getName().equals(fd.getName())) { + existingSimilarOne = existingFd; + break; + } + } + if (existingSimilarOne != null) { + if (fd.isAllowWrite()) { + existingSimilarOne.setAllowWrite(true); + } + } else { + addFieldDescriptor(fd); + } + } + for (MethodDescriptor methodDescriptor : cd.getMethods()) { + if (!containsMethodDescriptor(methodDescriptor)) { + addMethodDescriptor(methodDescriptor); + } + } + } + + private boolean containsMethodDescriptor(MethodDescriptor methodDescriptor) { + return methods.contains(methodDescriptor); + } + + public MethodDescriptor getMethodDescriptor(String name,String...parameterTypes) { + MethodDescriptor searchmd = MethodDescriptor.of(name, parameterTypes); + for (MethodDescriptor md: methods) { + if (md.equals(searchmd)) { + return md; + } + } + return null; + } + +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/FieldDescriptor.java b/src/main/java/org/springframework/graal/domain/reflect/FieldDescriptor.java new file mode 100644 index 000000000..5462777b1 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/FieldDescriptor.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.reflect; + +/** + * + * @author Andy Clement + * @see ReflectionDescriptor + */ +public final class FieldDescriptor extends MemberDescriptor implements Comparable { + + private boolean allowWrite = false; + + private boolean allowUnsafeAccess = false; + + FieldDescriptor(String name, boolean allowWrite, boolean allowUnsafeAccess) { + super(name); + this.allowWrite = allowWrite; + this.allowUnsafeAccess = allowUnsafeAccess; + } + + public boolean isAllowWrite() { + return this.allowWrite; + } + + public boolean isAllowUnsafeAccess() { + return this.allowUnsafeAccess; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FieldDescriptor other = (FieldDescriptor) o; + boolean result = true; + result = result && nullSafeEquals(this.name, other.name); + result = result && nullSafeEquals(this.allowWrite, other.allowWrite); + result = result && nullSafeEquals(this.allowUnsafeAccess, other.allowUnsafeAccess); + return result; + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(this.name); + result = 31 * result + nullSafeHashCode(this.allowWrite); + result = 31 * result + nullSafeHashCode(this.allowUnsafeAccess); + return result; + } + + @Override + public String toString() { + StringBuilder string = new StringBuilder(this.name); + buildToStringProperty(string, "name", this.name); + buildToStringProperty(string, "allowWrite", this.allowWrite); + buildToStringProperty(string, "allowUnsafeAccess", this.allowUnsafeAccess); + return string.toString(); + } + + @Override + public int compareTo(FieldDescriptor o) { + return getName().compareTo(o.getName()); + } + + public void setAllowWrite(boolean b) { + this.allowWrite = b; + } + + public void setAllowUnsafeAccess(boolean b) { + this.allowUnsafeAccess = b; + } + +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/JsonConverter.java b/src/main/java/org/springframework/graal/domain/reflect/JsonConverter.java new file mode 100644 index 000000000..ada7b8602 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/JsonConverter.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.domain.reflect; + +import java.util.List; +import java.util.Set; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; +import org.springframework.graal.domain.reflect.ClassDescriptor.Flag; + +/** + * Converter to change reflection descriptor objects into JSON objects + * + * @author Andy Clement + */ +class JsonConverter { + + public JSONArray toJsonArray(ReflectionDescriptor metadata) throws Exception { + JSONArray jsonArray = new JSONArray(); + for (ClassDescriptor cd : metadata.getClassDescriptors()) { + jsonArray.put(toJsonObject(cd)); + } + return jsonArray; + } + + public JSONObject toJsonObject(ClassDescriptor cd) throws Exception { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", cd.getName()); + Set flags = cd.getFlags(); + if (flags != null) { + for (Flag flag: Flag.values()) { + if (flags.contains(flag)) { + putTrueFlag(jsonObject,flag.name()); + } + } + } + List fds = cd.getFields(); + if (fds != null) { + JSONArray fieldJsonArray = new JSONArray(); + for (FieldDescriptor fd: fds) { + JSONObject fieldjo = new JSONObject(); + fieldjo.put("name", fd.getName()); + if (fd.isAllowWrite()) { + fieldjo.put("allowWrite", "true"); + } + fieldJsonArray.put(fieldjo); + } + jsonObject.put("fields", fieldJsonArray); + } + List mds = cd.getMethods(); + if (mds != null) { + JSONArray methodsJsonArray = new JSONArray(); + for (MethodDescriptor md: mds) { + JSONObject methodJsonObject = new JSONObject(); + methodJsonObject.put("name", md.getName()); + List parameterTypes = md.getParameterTypes(); + JSONArray parameterArray = new JSONArray(); + if (parameterTypes != null) { + for (String pt: parameterTypes) { + parameterArray.put(pt); + } + } + methodJsonObject.put("parameterTypes",parameterArray); + methodsJsonArray.put(methodJsonObject); + } + jsonObject.put("methods", methodsJsonArray); + } + return jsonObject; + } + + private void putTrueFlag(JSONObject jsonObject, String name) throws Exception { + jsonObject.put(name, true); + } +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/JsonMarshaller.java b/src/main/java/org/springframework/graal/domain/reflect/JsonMarshaller.java new file mode 100644 index 000000000..6ba8b7d76 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/JsonMarshaller.java @@ -0,0 +1,142 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.domain.reflect; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.graal.domain.reflect.ClassDescriptor.Flag; +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Marshaller to write {@link ReflectionDescriptor} as JSON. + * + * @author Andy Clement + */ +public class JsonMarshaller { + + private static final int BUFFER_SIZE = 4098; + + public void write(ReflectionDescriptor metadata, OutputStream outputStream) + throws IOException { + try { + JsonConverter converter = new JsonConverter(); + JSONArray jsonArray = converter.toJsonArray(metadata); + outputStream.write(jsonArray.toString(2).getBytes(StandardCharsets.UTF_8)); + } + catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException) ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new IllegalStateException(ex); + } + } + + public static ReflectionDescriptor read(String input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + return read(bais); + } + } + + public static ReflectionDescriptor read(byte[] input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input)) { + return read(bais); + } + } + + public static ReflectionDescriptor read(InputStream inputStream) throws Exception { + ReflectionDescriptor metadata = toReflectionDescriptor(new JSONArray(toString(inputStream))); + return metadata; + } + + private static ReflectionDescriptor toReflectionDescriptor(JSONArray array) throws Exception { + ReflectionDescriptor rd = new ReflectionDescriptor(); + for (int i=0;i listOfParameterTypes = null; + if (parameterTypes != null) { + listOfParameterTypes = new ArrayList<>(); + for (int i=0;i { + + public final static String CONSTRUCTOR_NAME = ""; + + public final static List NO_PARAMS = Collections.emptyList(); + + private List parameterTypes; // e.g. char[], java.lang.String, java.lang.Object[] + + MethodDescriptor() { + } + + MethodDescriptor(String name, List parameterTypes) { + super(name); + this.parameterTypes = parameterTypes; + } + + public List getParameterTypes() { + return this.parameterTypes; + } + + public void setParameterTypes(List parameterTypes) { + this.parameterTypes = parameterTypes; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MethodDescriptor other = (MethodDescriptor) o; + boolean result = true; + result = result && nullSafeEquals(this.name, other.name); + result = result && nullSafeEquals(this.parameterTypes, other.parameterTypes); + return result; + } + + @Override + public int hashCode() { + int result = nullSafeHashCode(this.name); + result = 31 * result + nullSafeHashCode(this.parameterTypes); + return result; + } + + @Override + public String toString() { + StringBuilder string = new StringBuilder(this.name); + buildToStringProperty(string, "name", this.name); + buildToStringProperty(string, "parameterTypes", this.parameterTypes); + return string.toString(); + } + + @Override + public int compareTo(MethodDescriptor o) { + return getName().compareTo(o.getName()); + } + + public static MethodDescriptor of(String name, String... parameterTypes) { + MethodDescriptor md = new MethodDescriptor(); + md.setName(name); + if (parameterTypes != null) { + md.setParameterTypes(Arrays.asList(parameterTypes)); + } else { + md.setParameterTypes(NO_PARAMS); + } + return md; + } + +} diff --git a/src/main/java/org/springframework/graal/domain/reflect/ReflectionDescriptor.java b/src/main/java/org/springframework/graal/domain/reflect/ReflectionDescriptor.java new file mode 100644 index 000000000..56d89c867 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/reflect/ReflectionDescriptor.java @@ -0,0 +1,91 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.reflect; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md + * + * @author Andy Clement + */ +public class ReflectionDescriptor { + + private final List classDescriptors; + + public ReflectionDescriptor() { + this.classDescriptors = new ArrayList<>(); + } + + public ReflectionDescriptor(ReflectionDescriptor metadata) { + this.classDescriptors = new ArrayList<>(metadata.classDescriptors); + } + + public void sort() { + classDescriptors.sort((a,b) -> a.getName().compareTo(b.getName())); + } + + public List getClassDescriptors() { + return this.classDescriptors; + } + + public void add(ClassDescriptor classDescriptor) { + this.classDescriptors.add(classDescriptor); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(String.format("ClassDescriptors #%s\n",classDescriptors.size())); + this.classDescriptors.forEach(cd -> { + result.append(String.format("%s: \n",cd)); + }); + return result.toString(); + } + + public boolean isEmpty() { + return classDescriptors.isEmpty(); + } + + public static ReflectionDescriptor of(String jsonString) { + try { + return JsonMarshaller.read(jsonString); + } catch (Exception e) { + throw new IllegalStateException("Unable to read json:\n"+jsonString, e); + } + } + + public boolean hasClassDescriptor(String string) { + for (ClassDescriptor cd: classDescriptors) { + if (cd.getName().equals(string)) { + return true; + } + } + return false; + } + + public ClassDescriptor getClassDescriptor(String type) { + for (ClassDescriptor cd: classDescriptors) { + if (cd.getName().equals(type)) { + return cd; + } + } + return null; + } + +} diff --git a/src/main/java/org/springframework/graal/domain/resources/ResourcesDescriptor.java b/src/main/java/org/springframework/graal/domain/resources/ResourcesDescriptor.java new file mode 100644 index 000000000..0f8bcc95c --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/resources/ResourcesDescriptor.java @@ -0,0 +1,86 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.domain.resources; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://github.com/oracle/graal/blob/master/substratevm/RESOURCES.md + * + * @author Andy Clement + */ +public class ResourcesDescriptor { + + private final List patterns; + + public ResourcesDescriptor() { + this.patterns = new ArrayList<>(); + } + + public ResourcesDescriptor(ResourcesDescriptor metadata) { + this.patterns = new ArrayList<>(metadata.patterns); + } + + public List getPatterns() { + return this.patterns; + } + + public void add(String pattern) { + this.patterns.add(pattern); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(String.format("ResourcesDescriptors #%s\n",patterns.size())); + this.patterns.forEach(cd -> { + result.append(String.format("%s: \n",cd)); + }); + return result.toString(); + } + + public boolean isEmpty() { + return patterns.isEmpty(); + } + + public static ResourcesDescriptor of(String jsonString) { + try { + return ResourcesJsonMarshaller.read(jsonString); + } catch (Exception e) { + throw new IllegalStateException("Unable to read json:\n"+jsonString, e); + } + } + +// public boolean hasClassDescriptor(String string) { +// for (ProxyDescriptor cd: classDescriptors) { +// if (cd.getName().equals(string)) { +// return true; +// } +// } +// return false; +// } +// +// public ProxyDescriptor getClassDescriptor(String type) { +// for (ProxyDescriptor cd: classDescriptors) { +// if (cd.getName().equals(type)) { +// return cd; +// } +// } +// return null; +// } + +} diff --git a/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonConverter.java b/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonConverter.java new file mode 100644 index 000000000..11e152e44 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonConverter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.resources; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Converter to change resource descriptor objects into JSON objects + * + * @author Andy Clement + */ +class ResourcesJsonConverter { + + public JSONObject toJsonArray(ResourcesDescriptor metadata) throws Exception { + JSONObject object = new JSONObject(); + JSONArray jsonArray = new JSONArray(); + for (String p : metadata.getPatterns()) { + jsonArray.put(toJsonObject(p)); + } + object.put("resources", jsonArray); + return object; + } + + public JSONObject toJsonObject(String pattern) throws Exception { + JSONObject object = new JSONObject(); + object.put("pattern", pattern); + return object; +// JSONObject jsonObject = new JSONObject(); +// jsonObject.put("name", cd.getName()); +// Set flags = cd.getFlags(); +// if (flags != null) { +// for (Flag flag: Flag.values()) { +// if (flags.contains(flag)) { +// putTrueFlag(jsonObject,flag.name()); +// } +// } +// } +// List fds = cd.getFields(); +// if (fds != null) { +// JSONArray fieldJsonArray = new JSONArray(); +// for (FieldDescriptor fd: fds) { +// JSONObject fieldjo = new JSONObject(); +// fieldjo.put("name", fd.getName()); +// if (fd.isAllowWrite()) { +// fieldjo.put("allowWrite", "true"); +// } +// fieldJsonArray.put(fieldjo); +// } +// jsonObject.put("fields", fieldJsonArray); +// } +// List mds = cd.getMethods(); +// if (mds != null) { +// JSONArray methodsJsonArray = new JSONArray(); +// for (MethodDescriptor md: mds) { +// JSONObject methodJsonObject = new JSONObject(); +// methodJsonObject.put("name", md.getName()); +// List parameterTypes = md.getParameterTypes(); +// JSONArray parameterArray = new JSONArray(); +// if (parameterTypes != null) { +// for (String pt: parameterTypes) { +// parameterArray.put(pt); +// } +// } +// methodJsonObject.put("parameterTypes",parameterArray); +// methodsJsonArray.put(methodJsonObject); +// } +// jsonObject.put("methods", methodsJsonArray); +// } +// return jsonObject; + } + +// private void putTrueFlag(JSONObject jsonObject, String name) throws Exception { +// jsonObject.put(name, true); +// } +} diff --git a/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonMarshaller.java b/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonMarshaller.java new file mode 100644 index 000000000..b0f3e4117 --- /dev/null +++ b/src/main/java/org/springframework/graal/domain/resources/ResourcesJsonMarshaller.java @@ -0,0 +1,113 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.domain.resources; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import org.springframework.graal.json.JSONArray; +import org.springframework.graal.json.JSONObject; + +/** + * Marshaller to write {@link ResourcesDescriptor} as JSON. + * + * @author Andy Clement + */ +public class ResourcesJsonMarshaller { + + private static final int BUFFER_SIZE = 4098; + + public void write(ResourcesDescriptor metadata, OutputStream outputStream) + throws IOException { + try { + ResourcesJsonConverter converter = new ResourcesJsonConverter(); + JSONObject jsonObject = converter.toJsonArray(metadata); + outputStream.write(jsonObject.toString(2).getBytes(StandardCharsets.UTF_8)); + } + catch (Exception ex) { + if (ex instanceof IOException) { + throw (IOException) ex; + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new IllegalStateException(ex); + } + } + + public static ResourcesDescriptor read(String input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))) { + return read(bais); + } + } + + public static ResourcesDescriptor read(byte[] input) throws Exception { + try (ByteArrayInputStream bais = new ByteArrayInputStream(input)) { + return read(bais); + } + } + + public static ResourcesDescriptor read(InputStream inputStream) throws Exception { + ResourcesDescriptor metadata = toResourcesDescriptor(new JSONObject(toString(inputStream))); + return metadata; + } + + private static ResourcesDescriptor toResourcesDescriptor(JSONObject object) throws Exception { + ResourcesDescriptor rd = new ResourcesDescriptor(); + JSONArray array = object.getJSONArray("resources"); + for (int i=0;i listOfParameterTypes = null; +// if (parameterTypes != null) { +// listOfParameterTypes = new ArrayList<>(); +// for (int i=0;i> proxyRegisteringConsumer = interfaceNames -> { + System.out.println("- "+interfaceNames); + boolean isOK= true; + Class[] interfaces = new Class[interfaceNames.size()]; + for (int i = 0; i < interfaceNames.size(); i++) { + String className = interfaceNames.get(i); + Class clazz = imageClassLoader.findClassByName(className, false); + if (clazz == null) { + System.out.println("Skipping dynamic proxy registration due to missing type: "+className); + isOK=false; + break; + } + if (!clazz.isInterface()) { + throw new RuntimeException("The class \"" + className + "\" is not an interface."); + } + interfaces[i] = clazz; + } + if (isOK) { + /* The interfaces array can be empty. The java.lang.reflect.Proxy API allows it. */ + dynamicProxySupport.addProxyClass(interfaces); + } + }; + pd.consume(proxyRegisteringConsumer); + } +} diff --git a/src/main/java/org/springframework/graal/support/InitializationHandler.java b/src/main/java/org/springframework/graal/support/InitializationHandler.java new file mode 100644 index 000000000..a8935b5fa --- /dev/null +++ b/src/main/java/org/springframework/graal/support/InitializationHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.support; + +import java.io.InputStream; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess; +import org.springframework.graal.domain.buildtimeinit.InitializationDescriptor; +import org.springframework.graal.domain.buildtimeinit.InitializationJsonMarshaller; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; + +/** + * + * @author Andy Clement + */ +public class InitializationHandler { + + public InitializationDescriptor compute() { + try { + InputStream s = this.getClass().getResourceAsStream("/initialization.json"); + return InitializationJsonMarshaller.read(s); + } catch (Exception e) { + return null; + } + } + + public void register(BeforeAnalysisAccess access) { + InitializationDescriptor id = compute(); + System.out.println("SBG: forcing explicit class initialization at build or runtime:"); + System.out.println(id.toString()); + List collect = id.getBuildtimeClasses().stream() + .map(access::findClassByName).filter(Objects::nonNull).collect(Collectors.toList()); + RuntimeClassInitialization.initializeAtBuildTime(collect.toArray(new Class[] {})); + id.getRuntimeClasses().stream() + .map(access::findClassByName).filter(Objects::nonNull) + .forEach(RuntimeClassInitialization::initializeAtRunTime); + System.out.println("Registering these packages for buildtime initialization: \n"+id.getBuildtimePackages()); + RuntimeClassInitialization.initializeAtBuildTime(id.getBuildtimePackages().toArray(new String[] {})); + System.out.println("Registering these packages for runtime initialization: \n"+id.getRuntimePackages()); + RuntimeClassInitialization.initializeAtRunTime(id.getRuntimePackages().toArray(new String[] {})); + } + +} diff --git a/src/main/java/org/springframework/graal/support/ReflectionHandler.java b/src/main/java/org/springframework/graal/support/ReflectionHandler.java new file mode 100644 index 000000000..eab07fa15 --- /dev/null +++ b/src/main/java/org/springframework/graal/support/ReflectionHandler.java @@ -0,0 +1,341 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.support; + +import java.io.InputStream; +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Array; +import java.lang.reflect.Executable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature.DuringSetupAccess; +import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; +import org.graalvm.util.GuardedAnnotationAccess; +import org.springframework.graal.domain.reflect.ClassDescriptor; +import org.springframework.graal.domain.reflect.ClassDescriptor.Flag; +import org.springframework.graal.domain.reflect.FieldDescriptor; +import org.springframework.graal.domain.reflect.JsonMarshaller; +import org.springframework.graal.domain.reflect.MethodDescriptor; +import org.springframework.graal.domain.reflect.ReflectionDescriptor; + +import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.config.ReflectionRegistryAdapter; + +/** + * Loads up the constant data defined in resource file and registers reflective access being + * necessary with the image build. Also provides an method (addAccess(String typename, Flag... flags)} + * usable from elsewhere when needing to register reflective access to a type (e.g. used when resource + * processing). + * + * @author Andy Clement + */ +public class ReflectionHandler { + + private final static String RESOURCE_FILE = "/reflect.json"; + + private ReflectionRegistryAdapter rra; + + private ReflectionDescriptor constantReflectionDescriptor; + + private ImageClassLoader cl; + + public ReflectionDescriptor getConstantData() { + if (constantReflectionDescriptor == null) { + try { + InputStream s = this.getClass().getResourceAsStream(RESOURCE_FILE); + constantReflectionDescriptor = JsonMarshaller.read(s); + } catch (Exception e) { + throw new IllegalStateException("Unexpectedly can't load "+RESOURCE_FILE, e); + } + } + return constantReflectionDescriptor; + } + + public void register(DuringSetupAccess a) { + DuringSetupAccessImpl access = (DuringSetupAccessImpl) a; + RuntimeReflectionSupport rrs = ImageSingletons.lookup(RuntimeReflectionSupport.class); + cl = access.getImageClassLoader(); + rra = new ReflectionRegistryAdapter(rrs, cl); + ReflectionDescriptor reflectionDescriptor = getConstantData(); + + System.out.println("SBG: reflection registering #"+reflectionDescriptor.getClassDescriptors().size()+" entries"); + for (ClassDescriptor classDescriptor : reflectionDescriptor.getClassDescriptors()) { + Class type = null; + String n2 = classDescriptor.getName(); + if (n2.endsWith("[]")) { + System.out.println("ARRAY: "+n2.substring(0,n2.length()-2)); + type = rra.resolveType(n2.substring(0,n2.length()-2)); + System.out.println("Array base type resolved as "+type.getName()); + Object o = Array.newInstance(type, 1); + type = o.getClass(); + System.out.println("Class of array is "+type.getName()); + } else { + type = rra.resolveType(classDescriptor.getName()); + } + if (type == null) { + System.out.println("SBG: WARNING: "+RESOURCE_FILE+" included "+classDescriptor.getName()+" but it doesn't exist on the classpath, skipping..."); + continue; + } + rra.registerType(type); + Set flags = classDescriptor.getFlags(); + if (flags != null) { + for (Flag flag: flags) { + try { + switch (flag) { + case allDeclaredClasses: + rra.registerDeclaredClasses(type); + break; + case allDeclaredFields: + rra.registerDeclaredFields(type); + break; + case allPublicFields: + rra.registerPublicFields(type); + break; + case allDeclaredConstructors: + rra.registerDeclaredConstructors(type); + break; + case allPublicConstructors: + rra.registerPublicConstructors(type); + break; + case allDeclaredMethods: + rra.registerDeclaredMethods(type); + break; + case allPublicMethods: + rra.registerPublicMethods(type); + break; + case allPublicClasses: + rra.registerPublicClasses(type); + break; + } + } catch (NoClassDefFoundError ncdfe) { + System.out.println("SBG: ERROR: problem handling flag: "+flag+" for "+type.getName()+" because of missing "+ncdfe.getMessage()); + } + } + } + + // Process all specific methods defined in the input class descriptor (including constructors) + List methods = classDescriptor.getMethods(); + if (methods != null) { + for (MethodDescriptor methodDescriptor : methods) { + String n = methodDescriptor.getName(); + List parameterTypes = methodDescriptor.getParameterTypes(); + if (parameterTypes == null) { + if (n.equals("")) { + rra.registerAllConstructors(type); + } else { + rra.registerAllMethodsWithName(type, n); + } + } else { + List> collect = parameterTypes.stream().map(pname -> rra.resolveType(pname)) + .collect(Collectors.toList()); + try { + if (n.equals("")) { + rra.registerConstructor(type, collect); + } else { + rra.registerMethod(type, n, collect); + } + } catch (NoSuchMethodException nsme) { + throw new IllegalStateException("Couldn't find: " + methodDescriptor.toString(), nsme); + } + } + } + } + + // Process all specific fields defined in the input class descriptor + List fields = classDescriptor.getFields(); + if (fields != null) { + for (FieldDescriptor fieldDescriptor : fields) { + try { + rra.registerField(type, fieldDescriptor.getName(), fieldDescriptor.isAllowWrite(),fieldDescriptor.isAllowUnsafeAccess()); + } catch (NoSuchFieldException nsfe) { + throw new IllegalStateException("Couldn't find field: " + type.getName()+"."+fieldDescriptor.getName(), nsfe); +// System.out.println("SBG: WARNING: skipping reflection registration of field "+type.getName()+"."+fieldDescriptor.getName()+": field not found"); + } + } + } + } + registerLogback(); + } + + // TODO review - not strictly correct as they may ask with different flags (but right now they don't) + public static final Set added = new HashSet<>(); + + /** + * Record that reflective access to a type (and a selection of its members based on the flags) should + * be possible at runtime. This method will pre-emptively check all type references to ensure later + * native-image processing will not fail if, for example, it trips up over a type reference in a + * generic type that isn't on the image building classpath. NOTE: it is assumed that if elements are + * not accessible that the runtime doesn't need them (this is done under the spring model where + * conditional checks on auto configuration would cause no attempts to be made to types/members that + * aren't added here). + * + * @param typename the dotted type name for which to add reflective access + * @param flags any members that should be accessible via reflection + * @return the class, if the type was successfully registered for reflective access, otherwise null + */ + public Class addAccess(String typename, Flag...flags) { + if (!added.add(typename)) { + return null; + } + System.out.println("SBG: INFO: Registering reflective access to "+typename); + // This can return null if, for example, the supertype of the specified type is not + // on the classpath. In a simple app there may be a number of types coming in from + // spring-boot-autoconfigure but they extend types not on the classpath. + Class type = rra.resolveType(typename); + if (type == null) { + System.out.println("SBG: ERROR: CANNOT RESOLVE "+typename+" ???"); + return null; + } + if (constantReflectionDescriptor.hasClassDescriptor(typename)) { + System.out.println("SBG: WARNING: type "+typename+" being added dynamically whilst "+RESOURCE_FILE+ + " already contains it - does it need to be in the file? "); + } + rra.registerType(type); + for (Flag flag: flags) { + try { + switch (flag) { + case allDeclaredClasses: + if (verify(type.getDeclaredClasses())) { + rra.registerDeclaredClasses(type); + } + break; + case allDeclaredFields: + if (verify(type.getDeclaredFields())) { + rra.registerDeclaredFields(type); + } + break; + case allPublicFields: + if (verify(type.getFields())) { + rra.registerPublicFields(type); + } + break; + case allDeclaredConstructors: + if (verify(type.getDeclaredConstructors())) { + rra.registerDeclaredConstructors(type); + } + break; + case allPublicConstructors: + if (verify(type.getConstructors())) { + rra.registerPublicConstructors(type); + } + break; + case allDeclaredMethods: + if (verify(type.getDeclaredMethods())) { + rra.registerDeclaredMethods(type); + } + break; + case allPublicMethods: + if (verify(type.getMethods())) { + rra.registerPublicMethods(type); + } + break; + case allPublicClasses: + if (verify(type.getClasses())) { + rra.registerPublicClasses(type); + } + break; + } + } catch (NoClassDefFoundError ncdfe) { + System.out.println("SBG: ERROR: problem handling flag: "+flag+" for "+type.getName()+" because of missing "+ncdfe.getMessage()); + } + } + return type; + } + + + private boolean verify(Object[] things) { + for (Object o: things) { + try { + if (o instanceof Method) { + ((Method)o).getGenericReturnType(); + } + if (o instanceof Field) { + ((Field)o).getGenericType(); + } + if (o instanceof AccessibleObject) { + AccessibleObject accessibleObject = (AccessibleObject) o; + GuardedAnnotationAccess.getDeclaredAnnotations(accessibleObject); + } + + if (o instanceof Parameter) { + Parameter parameter = (Parameter) o; + parameter.getType(); + } + if (o instanceof Executable) { + Executable e = (Executable)o; + e.getGenericParameterTypes(); + e.getGenericExceptionTypes(); + e.getParameters(); + } + } catch (Exception e) { + System.out.println("REFLECTION PROBLEM LATER due to reference from "+o+" to "+e.getMessage()); + return false; + } + } + return true; + } + + + // TODO this is horrible, it should be packaged with logback + // from PatternLayout + private String logBackPatterns[] = new String[] { "ch.qos.logback.core.pattern.IdentityCompositeConverter", "ch.qos.logback.core.pattern.ReplacingCompositeConverter", + "DateConverter", "RelativeTimeConverter", "LevelConverter", "ThreadConverter", "LoggerConverter", + "MessageConverter", "ClassOfCallerConverter", "MethodOfCallerConverter", "LineOfCallerConverter", + "FileOfCallerConverter", "MDCConverter", "ThrowableProxyConverter", "RootCauseFirstThrowableProxyConverter", + "ExtendedThrowableProxyConverter", "NopThrowableInformationConverter", "ContextNameConverter", + "CallerDataConverter", "MarkerConverter", "PropertyConverter", "LineSeparatorConverter", + "color.BlackCompositeConverter", "color.RedCompositeConverter", "color.GreenCompositeConverter", + "color.YellowCompositeConverter", "color.BlueCompositeConverter", "color.MagentaCompositeConverter", + "color.CyanCompositeConverter", "color.WhiteCompositeConverter", "color.GrayCompositeConverter", + "color.BoldRedCompositeConverter", "color.BoldGreenCompositeConverter", + "color.BoldYellowCompositeConverter", "color.BoldBlueCompositeConverter", + "color.BoldMagentaCompositeConverter", "color.BoldCyanCompositeConverter", + "color.BoldWhiteCompositeConverter", "ch.qos.logback.classic.pattern.color.HighlightingCompositeConverter", + "LocalSequenceNumberConverter", "org.springframework.boot.logging.logback.ColorConverter", + "org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter", + "org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"}; +// what would a reflection hint look like here? Would it specify maven coords for logback as a requirement on the classpath? +// does logback have a feature? or meta data files for graal? + private void registerLogback() { + try { + addAccess("ch.qos.logback.core.Appender", Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + } catch (NoClassDefFoundError e) { + System.out.println("Logback not found, skipping registration logback types"); + return; + } + addAccess("org.springframework.boot.logging.logback.LogbackLoggingSystem", Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + for (String p: logBackPatterns) { + if (p.startsWith("org")) { + addAccess(p, Flag.allDeclaredConstructors,Flag.allDeclaredMethods); + } else if (p.startsWith("ch.")) { + addAccess(p, Flag.allDeclaredConstructors,Flag.allDeclaredMethods); + } else if (p.startsWith("color.")) { + addAccess("ch.qos.logback.core.pattern."+p,Flag.allDeclaredConstructors,Flag.allDeclaredMethods); + } else { + addAccess("ch.qos.logback.classic.pattern."+p,Flag.allDeclaredConstructors,Flag.allDeclaredMethods); + } + } + } + +} diff --git a/src/main/java/org/springframework/graal/support/ResourcesHandler.java b/src/main/java/org/springframework/graal/support/ResourcesHandler.java new file mode 100644 index 000000000..397fedbe8 --- /dev/null +++ b/src/main/java/org/springframework/graal/support/ResourcesHandler.java @@ -0,0 +1,610 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.support; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.hosted.Feature.BeforeAnalysisAccess; +import org.springframework.graal.domain.reflect.ClassDescriptor.Flag; +import org.springframework.graal.domain.resources.ResourcesDescriptor; +import org.springframework.graal.domain.resources.ResourcesJsonMarshaller; +import org.springframework.graal.type.HintDescriptor; +import org.springframework.graal.type.MissingTypeException; +import org.springframework.graal.type.Type; +import org.springframework.graal.type.TypeSystem; + +import com.oracle.svm.core.jdk.Resources; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.core.configure.ResourcesRegistry; + +public class ResourcesHandler { + + private TypeSystem ts; + + private ImageClassLoader cl; + + private ReflectionHandler reflectionHandler; + + private static boolean REMOVE_UNNECESSARY_CONFIGURATIONS; + + static { + REMOVE_UNNECESSARY_CONFIGURATIONS = Boolean.valueOf(System.getProperty("removeUnusedAutoconfig","false")); + System.out.println("Remove unused config = "+REMOVE_UNNECESSARY_CONFIGURATIONS); + } + + public ResourcesHandler(ReflectionHandler reflectionHandler) { + this.reflectionHandler = reflectionHandler; + } + + public ResourcesDescriptor compute() { + try { + InputStream s = this.getClass().getResourceAsStream("/resources.json"); + ResourcesDescriptor read = ResourcesJsonMarshaller.read(s); + return read; + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public void register(BeforeAnalysisAccess access) { + cl = ((BeforeAnalysisAccessImpl) access).getImageClassLoader(); + ts = TypeSystem.get(cl.getClasspath()); + ResourcesDescriptor rd = compute(); + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + // Patterns can be added to the registry, resources can be directly registered + // against Resources + // resourcesRegistry.addResources("*"); + // Resources.registerResource(relativePath, inputstream); + System.out.println("SBG: adding resources - #" + rd.getPatterns().size()+" patterns"); + + for (String pattern : rd.getPatterns()) { + if (pattern.equals("META-INF/spring.factories")) { + continue; // leave to special handling... + } +// if (pattern.contains("logging.properties")) { +// URL resource = cl.getClassLoader().getResource(pattern); +// System.out.println("Can I find "+pattern+"? "+resource); +// } + resourcesRegistry.addResources(pattern); + } + processSpringFactories(); + processSpringComponents(); + } + + public void processSpringComponents() { + Enumeration springComponents = fetchResources("META-INF/spring.components"); + if (springComponents.hasMoreElements()) { + log("Processing META-INF/spring.components files..."); + while (springComponents.hasMoreElements()) { + URL springFactory = springComponents.nextElement(); + processSpringComponents(ts, springFactory); + } + } else { +// System.out.println("No META-INF/spring.components found"); + System.out.println("Found no META-INF/spring.components -> generating one..."); + List> components = scanClasspathForIndexedStereotypes(); + List> filteredComponents = filterComponents(components); + Properties p = new Properties(); + for (Entry filteredComponent: filteredComponents) { + String k = filteredComponent.getKey(); + System.out.println("- "+k); + p.put(k, filteredComponent.getValue()); + reflectionHandler.addAccess(k,Flag.allDeclaredConstructors, Flag.allDeclaredMethods, Flag.allDeclaredClasses); + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + resourcesRegistry.addResources(k.replace(".", "/")+".class"); + processComponent(k, new HashSet<>()); + } + System.out.println("Computed spring.components is "); + System.out.println("vvv"); + p.list(System.out); + System.out.println("^^^"); + System.out.println(">>> "+p.toString()); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + p.store(baos,""); + baos.close(); + byte[] bs = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bs); + Resources.registerResource("META-INF/spring.components", bais); + System.out.println("BAOS: "+new String(bs)); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } + + private void processComponent(String typename, Set visited) { + if (!visited.add(typename)) { + return; + } + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + Type componentType = ts.resolveDotted(typename); + System.out.println("> Component processing: "+typename); + List conditionalTypes = componentType.findConditionalOnClassValue(); + if (conditionalTypes != null) { + for (String lDescriptor : conditionalTypes) { + Type t = ts.Lresolve(lDescriptor, true); + boolean exists = (t != null); + if (!exists) { + return; + } else { + try { + reflectionHandler.addAccess(lDescriptor.substring(1,lDescriptor.length()-1).replace("/", "."),Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(lDescriptor.substring(1,lDescriptor.length()-1)+".class"); + } catch (NoClassDefFoundError e) { + System.out.println("Conditional type "+fromLtoDotted(lDescriptor)+" not found for component "+componentType.getName()); + } + + } + } + } + try { + // String configNameDotted = configType.getName().replace("/","."); + System.out.println("Including auto-configuration "+typename); + reflectionHandler.addAccess(typename,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(typename.replace(".", "/")+".class"); + } catch (NoClassDefFoundError e) { + // Example: + // PROBLEM? Can't register Type:org/springframework/boot/autoconfigure/web/servlet/HttpEncodingAutoConfiguration because cannot find javax/servlet/Filter + // java.lang.NoClassDefFoundError: javax/servlet/Filter + // ... at com.oracle.svm.hosted.config.ReflectionRegistryAdapter.registerDeclaredConstructors(ReflectionRegistryAdapter.java:97) + System.out.println("PROBLEM? Can't register "+typename+" because cannot find "+e.getMessage()); + } + + Map> imports = componentType.findImports(); + if (imports != null) { + System.out.println("Imports found on "+typename+" are "+imports); + for (Map.Entry> importsEntry: imports.entrySet()) { + reflectionHandler.addAccess(importsEntry.getKey(),Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + for (String imported: importsEntry.getValue()) { + String importedName = fromLtoDotted(imported); + try { + Type t = ts.resolveDotted(importedName); + processComponent( t.getName().replace("/", "."), visited); + } catch (MissingTypeException mte) { + System.out.println("Cannot find imported "+importedName+" so skipping processing that"); + } + } + } + } + + // Without this code, error at: + // java.lang.ClassNotFoundException cannot be cast to java.lang.Class[] + // at org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector$ConfigurationPropertiesBeanRegistrar.lambda$collectClasses$1(EnableConfigurationPropertiesImportSelector.java:80) + List ecProperties = componentType.findEnableConfigurationPropertiesValue(); + if (ecProperties != null) { + for (String ecPropertyDescriptor: ecProperties) { + String ecPropertyName = fromLtoDotted(ecPropertyDescriptor); + System.out.println("ECP "+ecPropertyName); + try { + reflectionHandler.addAccess(ecPropertyName,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(ecPropertyName.replace(".", "/")+".class"); + } catch (NoClassDefFoundError e) { + System.out.println("Not found for registration: "+ecPropertyName); + } + } + } + + // Find @Bean methods and add them +// List methodsWithAtBean = configType.getMethodsWithAtBean(); +// if (methodsWithAtBean.size() != 0) { +// System.out.println(configType+" here they are: "+ +// methodsWithAtBean.stream().map(m -> m.getName()+m.getDesc()).collect(Collectors.toList())); +// for (Method m: methodsWithAtBean) { +// String desc = m.getDesc(); +// String retType = desc.substring(desc.lastIndexOf(")")+1); //Lorg/springframework/boot/task/TaskExecutorBuilder; +// System.out.println("@Bean return type "+retType); +// reflectionHandler.addAccess(fromLtoDotted(retType), Flag.allDeclaredConstructors, Flag.allDeclaredMethods); +// } +// } + + List nestedTypes = componentType.getNestedTypes(); + for (Type t: nestedTypes) { + if (visited.add(t.getName())) { + processComponent(t.getName().replace("/", "."), visited); + } + } + } + + private void processSpringComponents(TypeSystem ts, URL springComponentsFile) { + Properties p = new Properties(); + loadSpringFactoryFile(springComponentsFile, p); + // Example: + // com.example.demo.Foobar=org.springframework.stereotype.Component + // com.example.demo.DemoApplication=org.springframework.stereotype.Component + Enumeration keys = p.keys(); + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + while (keys.hasMoreElements()) { + String k = (String)keys.nextElement(); + System.out.println("Registering Spring Component: "+k); + reflectionHandler.addAccess(k,Flag.allDeclaredConstructors, Flag.allDeclaredMethods, Flag.allDeclaredClasses); + resourcesRegistry.addResources(k.replace(".", "/")+".class"); + // Register nested types of the component + Type baseType = ts.resolveDotted(k); + for (Type t: baseType.getNestedTypes()) { + String n = t.getName().replace("/", "."); + reflectionHandler.addAccess(n,Flag.allDeclaredConstructors, Flag.allDeclaredMethods, Flag.allDeclaredClasses); + resourcesRegistry.addResources(t.getName()+".class"); + } + registerHierarchy(baseType, new HashSet<>(), resourcesRegistry); + } + } + + public void registerHierarchy(Type t, Set visited, ResourcesRegistry resourcesRegistry) { + if (t == null || t.getName().equals("java/lang/Object") || !visited.add(t)) { + return; + } + String desc = t.getName(); + System.out.println("Hierarchy registration of "+t.getName()); + reflectionHandler.addAccess(desc.replace("/", "."),Flag.allDeclaredConstructors, Flag.allDeclaredMethods, Flag.allDeclaredClasses); + resourcesRegistry.addResources(desc.replace("$", ".")+".class"); + Type s = t.getSuperclass(); + registerHierarchy(s, visited, resourcesRegistry); + Type[] is = t.getInterfaces(); + for (Type i: is) { + registerHierarchy(i, visited, resourcesRegistry); + } + // TODO inners of those supertypes/interfaces? + } + + /** + * Find all META-INF/spring.factories - for any configurations listed in each, check if those configurations use ConditionalOnClass. + * If the classes listed in ConditionalOnClass can't be found, discard the configuration from spring.factories. Register either + * the unchanged or modified spring.factories files with the system. + */ + public void processSpringFactories() { + log("Processing META-INF/spring.factories files..."); + Enumeration springFactories = fetchResources("META-INF/spring.factories"); + while (springFactories.hasMoreElements()) { + URL springFactory = springFactories.nextElement(); + processSpringFactory(ts, springFactory); + } + } + + + private List> filterComponents(List> as) { + List> filtered = new ArrayList<>(); + List> subtypesToRemove = new ArrayList<>(); + for (Entry a: as) { + String type = a.getKey(); + subtypesToRemove.addAll(as.stream().filter(e -> e.getKey().startsWith(type+"$")).collect(Collectors.toList())); + } + filtered.addAll(as); + filtered.removeAll(subtypesToRemove); + return filtered; + } + + private List> scanClasspathForIndexedStereotypes() { + return findDirectories(ts.getClasspath()) + .flatMap(this::findClasses) + .map(this::typenameOfClass) + .map(this::isIndexedOrEntity) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + +// app.main.SampleTransactionManagementConfiguration=org.springframework.stereotype.Component +// app.main.model.Foo=javax.persistence.Entity +// app.main.model.FooRepository=org.springframework.data.repository.Repository +// app.main.SampleApplication=org.springframework.stereotype.Component + private Entry isIndexedOrEntity(String slashedClassname) { + Entry entry = ts.resolveSlashed(slashedClassname).isIndexedOrEntity(); +// if (entry != null) { +// System.out.println("isIndexed for "+slashedClassname+" returned "+entry); +// } + return entry; + } + + private String typenameOfClass(File f) { + return Utils.scanClass(f).getClassname(); + } + + private Stream findClasses(File dir) { + ArrayList classfiles = new ArrayList<>(); + walk(dir,classfiles); + return classfiles.stream(); + } + + private void walk(File dir, ArrayList classfiles) { + File[] fs = dir.listFiles(); + for (File f: fs) { + if (f.isDirectory()) { + walk(f,classfiles); + } else if (f.getName().endsWith(".class")) { + classfiles.add(f); + } + } + } + + private Stream findDirectories(List classpath) { + List directories = new ArrayList<>(); + for (String classpathEntry: classpath) { + File f = new File(classpathEntry); + if (f.isDirectory()) { + directories.add(f); + } + } + return directories.stream(); + } + + private void processSpringFactory(TypeSystem ts, URL springFactory) { + List forRemoval = new ArrayList<>(); + Properties p = new Properties(); + loadSpringFactoryFile(springFactory, p); + + Enumeration keyz = p.keys(); + // Handle all keys except EnableAutoConfiguration + while (keyz.hasMoreElements()) { + String k = (String)keyz.nextElement(); + if (!k.equals("org.springframework.boot.autoconfigure.EnableAutoConfiguration")) { + String classesList = p.getProperty(k); + for (String s: classesList.split(",")) { + try { + reflectionHandler.addAccess(s,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + System.out.println("NEEDS ADDING TO RESOURCE LIST? "+s); + } catch (NoClassDefFoundError ncdfe) { + System.out.println("SBG: WARNING: Whilst processing "+k+" problem adding access for type: "+s+" because of missing "+ncdfe.getMessage()); + } + } + } + } + + String configsString = (String) p.get("org.springframework.boot.autoconfigure.EnableAutoConfiguration"); + if (configsString != null) { + List configs = new ArrayList<>(); + for (String s: configsString.split(",")) { + configs.add(s); + } + // TODO what about ConditionalOnResource? + System.out.println( + "Spring.factories processing: looking at #" + configs.size() + " configuration references"); + for (Iterator iterator = configs.iterator(); iterator.hasNext();) { + String config = iterator.next(); + boolean needToAddThem = true; + if (!verifyType(config)) { + System.out.println("Excluding auto-configuration " + config); + System.out.println("= COC failed so just adding class forname access (no methods/ctors)"); + if (REMOVE_UNNECESSARY_CONFIGURATIONS) { + forRemoval.add(config); + needToAddThem = false; + } + } + if (needToAddThem) { + System.out.println("Resource Adding: "+config); + reflectionHandler.addAccess(config); // no flags as it isn't going to trigger + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + resourcesRegistry.addResources(config.replace(".", "/").replace("$", ".")+".class"); + } + } + configs.removeAll(forRemoval); + p.put("org.springframework.boot.autoconfigure.EnableAutoConfiguration", String.join(",", configs)); + } + try { + if (forRemoval.size() == 0) { + Resources.registerResource("META-INF/spring.factories", springFactory.openStream()); + } else { + System.out.println(" removed " + forRemoval.size() + " configurations"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + p.store(baos,""); + baos.close(); + byte[] bs = baos.toByteArray(); + ByteArrayInputStream bais = new ByteArrayInputStream(bs); + Resources.registerResource("META-INF/spring.factories", bais); + } + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private void loadSpringFactoryFile(URL springFactory, Properties p) { + try (InputStream is = springFactory.openStream()) { + p.load(is); + } catch (IOException e) { + throw new IllegalStateException("Unable to load spring.factories", e); + } + } + + /** + * For the specified type (dotted name) determine which types must be reflectable at runtime. This means + * looking at annotations and following any type references within those. + */ + private boolean verifyType(String name) { + return processType(name, new HashSet<>()); + } + + private boolean processType(String config, Set visited) { + return processType(ts.resolveDotted(config), visited, 0); + } + + private boolean processType(Type configType, Set visited, int depth) { + System.out.println(spaces(depth)+"Processing type "+configType.getName()); + ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); + + // This would fetch 'things we care about from a graal point of view' + // a list + // ConditionalOnClass (annotation instance) + // configType.getRelevantAnnotations(); + // Then for each of those give me the relevant 'types i have to look around for' + + + Set missing = ts.resolveCompleteFindMissingTypes(configType); + if (!missing.isEmpty()) { + // No point continuing with this type, it cannot be resolved against current classpath + // The assumption is it will never need to be accessed anyway + System.out.println(spaces(depth)+"for "+configType.getName()+" missing types are "+missing); + return false; + } + + Set missingAnnotationTypes = ts.resolveCompleteFindMissingAnnotationTypes(configType); + if (!missingAnnotationTypes.isEmpty()) { + // If only the annotations are missing, it is ok to reflect on the existence of the type, it is + // just not safe to reflect on the annotations on that type. + System.out.println(spaces(depth)+"for "+configType.getName()+" missing annotation types are "+missingAnnotationTypes); + } + boolean passesTests = true; + Set toMakeAccessible = new HashSet<>(); + Map> hints = configType.getHints(); + if (!hints.isEmpty()) { + int h=1; + for (Map.Entry> hint: hints.entrySet()) { + HintDescriptor hintDescriptor = hint.getKey(); + List typeReferences = hint.getValue(); + System.out.println(spaces(depth)+"checking @CompilationHint "+h+"/"+hints.size()+" "+hintDescriptor.getAnnotationChain()); + + String[] name = hintDescriptor.getName(); + if (name != null) { + // The CollectionHint included a list of types to worry about in the annotation + // itself (e.g. as used on import selector to specify. + for (String n: name) { + resourcesRegistry.addResources(n.replace(".", "/")+".class"); + reflectionHandler.addAccess(n,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + } + } + + if (h==1) { // only do this once all hints should have this in common, TODO polish this up... + // This handles the case for something like: + // ReactiveWebServerFactoryConfiguration$EmbeddedTomcat with ConditionalOnClass + // TODO is this too much repetition for certain types? + for (Type annotatedType : hintDescriptor.getAnnotationChain()) { + try { + System.out.println("Handling annotated thingy: "+annotatedType.getName()); + String t = annotatedType.getDescriptor(); + reflectionHandler.addAccess(t.substring(1,t.length()-1).replace("/", "."),Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(t.substring(1,t.length()-1).replace("$", ".")+".class"); + } catch (NoClassDefFoundError e) { + System.out.println(spaces(depth)+annotatedType.getName()+" not found for configuration "+configType.getName()); + } + + } + } + + if (typeReferences != null) { + for (String typeReference: typeReferences) { // La/b/C; + Type t = ts.Lresolve(typeReference, true); + boolean exists = (t != null); + System.out.println(spaces(depth)+" does "+fromLtoDotted(typeReference)+" exist? "+exists); + if (exists) { + // TODO should this specify what aspects of reflection are required (methods/fields/ctors/annotations) + toMakeAccessible.add(typeReference); + if (hintDescriptor.isFollow()) { + processType(t, visited, depth+1); + } + } else if (hintDescriptor.isSkipIfTypesMissing()) { + passesTests = false; + } + } + } + h++; + } + } + + if (passesTests || !REMOVE_UNNECESSARY_CONFIGURATIONS) { + for (String t: toMakeAccessible) { + try { + reflectionHandler.addAccess(t.substring(1,t.length()-1).replace("/", "."),Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + resourcesRegistry.addResources(t.substring(1,t.length()-1).replace("$", ".")+".class"); + } catch (NoClassDefFoundError e) { + System.out.println(spaces(depth)+"Conditional type "+fromLtoDotted(t)+" not found for configuration "+configType.getName()); + } + } + } + + if (passesTests) { + try { + String configNameDotted = configType.getName().replace("/","."); + System.out.println(spaces(depth)+"including reflective/resource access to "+configNameDotted); + visited.add(configType.getName()); + reflectionHandler.addAccess(configNameDotted,Flag.allDeclaredConstructors, Flag.allDeclaredMethods); + System.out.println("res: "+configType.getName().replace("$", ".")+".class"); + resourcesRegistry.addResources(configType.getName().replace("$", ".")+".class"); + // In some cases the superclass of the config needs to be accessible + // TODO need this guard? if (isConfiguration(configType)) { + registerHierarchy(configType, new HashSet<>(), resourcesRegistry); + } catch (NoClassDefFoundError e) { + // Example: + // PROBLEM? Can't register Type:org/springframework/boot/autoconfigure/web/servlet/HttpEncodingAutoConfiguration because cannot find javax/servlet/Filter + // java.lang.NoClassDefFoundError: javax/servlet/Filter + // ... at com.oracle.svm.hosted.config.ReflectionRegistryAdapter.registerDeclaredConstructors(ReflectionRegistryAdapter.java:97) + System.out.println("PROBLEM? Can't register "+configType.getName()+" because cannot find "+e.getMessage()); + } + } + + // HibernateJpaConfiguration has a supertype also covered with @Configuration - so more than just registering + // the hierarchy as accessible, it may contain more config to chase down + Type s = configType.getSuperclass(); + while (s!= null) { + processType(s, visited, depth+1); + s = s.getSuperclass(); + } + + // If the outer type is failing a test, we don't need to recurse... + if (passesTests) { + List nestedTypes = configType.getNestedTypes(); + for (Type t: nestedTypes) { + if (visited.add(t.getName())) { + processType(t, visited, depth+1); + } + } + } else { + System.out.println("INFO: tests failed on "+configType.getName()+" so not going into nested types"); + } + return passesTests; + } + + String fromLtoDotted(String lDescriptor) { + return lDescriptor.substring(1,lDescriptor.length()-1).replace("/", "."); + } + + private Enumeration fetchResources(String resource) { + try { + Enumeration resources = Thread.currentThread().getContextClassLoader().getResources(resource); + return resources; + } catch (IOException e1) { + return Collections.enumeration(Collections.emptyList()); + } + } + + private void log(String msg) { + System.out.println(msg); + } + + private String spaces(int depth) { + return " ".substring(0,depth*2); + } + +} diff --git a/src/main/java/org/springframework/graal/support/SpringFeature.java b/src/main/java/org/springframework/graal/support/SpringFeature.java new file mode 100644 index 000000000..a7a2a1d0c --- /dev/null +++ b/src/main/java/org/springframework/graal/support/SpringFeature.java @@ -0,0 +1,84 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.support; + +import java.nio.Buffer; +import java.util.ArrayList; +import java.util.List; + +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.hosted.ResourcesFeature; +import com.oracle.svm.reflect.hosted.ReflectionFeature; +import com.oracle.svm.reflect.proxy.hosted.DynamicProxyFeature; + +@AutomaticFeature +public class SpringFeature implements Feature { + + private ReflectionHandler reflectionHandler; + + private DynamicProxiesHandler dynamicProxiesHandler; + + private ResourcesHandler resourcesHandler; + + private InitializationHandler buildTimeInitializationHandler; + + public SpringFeature() { + System.out.println( + " ____ _ _____ _ \n"+ + "/ ___| _ __ _ __(_)_ __ __ _ | ___|__ __ _| |_ _ _ _ __ ___ \n"+ + "\\___ \\| '_ \\| '__| | '_ \\ / _` | | |_ / _ \\/ _` | __| | | | '__/ _ \\\n"+ + " ___) | |_) | | | | | | | (_| | | _| __/ (_| | |_| |_| | | | __/\n"+ + "|____/| .__/|_| |_|_| |_|\\__, | |_| \\___|\\__,_|\\__|\\__,_|_| \\___|\n"+ + " |_| |___/ \n"); + reflectionHandler = new ReflectionHandler(); + dynamicProxiesHandler = new DynamicProxiesHandler(); + resourcesHandler = new ResourcesHandler(reflectionHandler); + buildTimeInitializationHandler = new InitializationHandler(); + } + + public boolean isInConfiguration(IsInConfigurationAccess access) { + return true; + } + + public List> getRequiredFeatures() { + List> fs = new ArrayList<>(); + fs.add(DynamicProxyFeature.class); // Ensures DynamicProxyRegistry available + fs.add(ResourcesFeature.class); // Ensures ResourcesRegistry available + fs.add(ReflectionFeature.class); // Ensures RuntimeReflectionSupport available + return fs; + } + + public void duringSetup(DuringSetupAccess access) { + reflectionHandler.register(access); + dynamicProxiesHandler.register(access); + } + + public void beforeAnalysis(BeforeAnalysisAccess access) { + resourcesHandler.register(access); + buildTimeInitializationHandler.register(access); + // TODO who requires this, is it a netty thing? + try { + access.registerAsUnsafeAccessed(Buffer.class.getDeclaredField("address")); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (SecurityException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/org/springframework/graal/support/Utils.java b/src/main/java/org/springframework/graal/support/Utils.java new file mode 100644 index 000000000..6a9414426 --- /dev/null +++ b/src/main/java/org/springframework/graal/support/Utils.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.support; + +import java.io.File; + +import org.springframework.graal.type.ConstantPoolScanner; + +public class Utils { + + public static ConstantPoolScanner scanClass(File f) { + return new ConstantPoolScanner(f); + } + +} diff --git a/src/main/java/org/springframework/graal/type/CompilationHint.java b/src/main/java/org/springframework/graal/type/CompilationHint.java new file mode 100644 index 000000000..cbc9db563 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/CompilationHint.java @@ -0,0 +1,27 @@ +package org.springframework.graal.type; + +public @interface CompilationHint { + + // When attached to an annotation, this indicates which fields in that annotation + // hold type references, e.g. if on ConditionalOnClass, names={"value","name"} + String[] fieldNames(); + + // If difficult to tell the types involved from the thing being annotated, the info can be put here + // (e.g. you have an ImportSelector returning classnames, the possible names should be in here) + String[] name(); + Class[] value(); + + // If true then whatever class is annotated/meta-annotateed with this is useless if + // the types visible through the names() fields are not found. + boolean skipIfTypesMissing(); + + // If true, then whatever types are referenced need to be followed because they may + // be further annotated/meta-annotated with compilation hints + boolean follow(); + + // Do we need to specify what reflection should be accessible? (Fields/Methods/Ctors)? + // Reducing the amount could likely help the image size + + // If true, whatever is (meta-)annotated with this must be accessible via getResource too. + boolean accessibleAsResource(); +} diff --git a/src/main/java/org/springframework/graal/type/ConstantPoolScanner.java b/src/main/java/org/springframework/graal/type/ConstantPoolScanner.java new file mode 100644 index 000000000..20fc7325a --- /dev/null +++ b/src/main/java/org/springframework/graal/type/ConstantPoolScanner.java @@ -0,0 +1,336 @@ +/* + * Copyright 2019 Contributors + * + * 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.graal.type; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * Enables us to check things quickly in the constant pool. Just parses the class up to the end of the constant pool. + * + * Useful reference: https://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html + * + * @author Andy Clement + */ +public class ConstantPoolScanner { + + private static final boolean DEBUG = false; + + private final static byte CONSTANT_Utf8 = 1; + + private final static byte CONSTANT_Integer = 3; + + private final static byte CONSTANT_Float = 4; + + private final static byte CONSTANT_Long = 5; + + private final static byte CONSTANT_Double = 6; + + private final static byte CONSTANT_Class = 7; + + private final static byte CONSTANT_String = 8; + + private final static byte CONSTANT_Fieldref = 9; + + private final static byte CONSTANT_Methodref = 10; + + private final static byte CONSTANT_InterfaceMethodref = 11; + + private final static byte CONSTANT_NameAndType = 12; + + private final static byte CONSTANT_MethodHandle = 15; + + private final static byte CONSTANT_MethodType = 16; + + private final static byte CONSTANT_InvokeDynamic = 18; + + private byte[] classbytes; + + // Used during the parse step + private int ptr; + + // Filled with strings and int[] + private Object[] cpdata; + + private int cpsize; + + private int[] type; + + // Does not need to be a set as there are no dups in the ConstantPool (for a class from a decent compiler...) + private List referencedClasses = new ArrayList(); + + private List referencedMethods = new ArrayList(); + + private String slashedclassname; + + + public static References getReferences(byte[] classbytes) { + ConstantPoolScanner cpScanner = new ConstantPoolScanner(classbytes); + return new References(cpScanner.slashedclassname, cpScanner.referencedClasses, cpScanner.referencedMethods); + } + + public static References getReferences(File f) { + try { + byte[] bytes = Files.readAllBytes(Paths.get(f.toURI())); + ConstantPoolScanner cpScanner = new ConstantPoolScanner(bytes); + return new References(cpScanner.slashedclassname, cpScanner.referencedClasses, cpScanner.referencedMethods); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public ConstantPoolScanner(File f) { + this(readBytes(f)); + } + + public static byte[] readBytes(File f) { + try { + byte[] bytes = Files.readAllBytes(Paths.get(f.toURI())); + return bytes; + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private ConstantPoolScanner(byte[] bytes) { + parseClass(bytes); +// computeReferences(); + } + + // Format of a classfile: + // ClassFile { + // u4 magic; + // u2 minor_version; + // u2 major_version; + // u2 constant_pool_count; + // cp_info constant_pool[constant_pool_count-1]; + // u2 access_flags; + // u2 this_class; + // u2 super_class; + // u2 interfaces_count; + // u2 interfaces[interfaces_count]; + // u2 fields_count; + // field_info fields[fields_count]; + // u2 methods_count; + // method_info methods[methods_count]; + // u2 attributes_count; + // attribute_info attributes[attributes_count]; + // } + private void parseClass(byte[] bytes) { + try { + this.classbytes = bytes; + this.ptr = 0; + int magic = readInt(); // magic 0xCAFEBABE + if (magic != 0xCAFEBABE) { + throw new IllegalStateException("not bytecode, magic was 0x" + Integer.toString(magic, 16)); + } + ptr += 4; // skip minor and major versions + cpsize = readUnsignedShort(); + if (DEBUG) { + System.out.println("Constant Pool Size =" + cpsize); + } + cpdata = new Object[cpsize]; + type = new int[cpsize]; + for (int cpentry = 1; cpentry < cpsize; cpentry++) { + boolean wasDoubleSlotItem = processConstantPoolEntry(cpentry); + if (wasDoubleSlotItem) { + cpentry++; + } + } + ptr += 2; // access flags + int thisclassname = readUnsignedShort(); + int classindex = ((Integer) cpdata[thisclassname]); + slashedclassname = accessUtf8(classindex); + } + catch (Exception e) { + throw new IllegalStateException("Unexpected problem processing bytes for class", e); + } + } + + public String getClassname() { + return slashedclassname; + } + + /** + * Return the UTF8 at the specified index in the constant pool. The data found at the constant pool for that index + * may not have been unpacked yet if this is the first access of the string. If not unpacked the constant pool entry + * is a pair of ints in an array representing the offset and length within the classbytes where the UTF8 string is + * encoded. Once decoded the constant pool entry is flipped from an int array to a String for future fast access. + * + * @param cpIndex constant pool index + * @return UTF8 string at that constant pool index + */ + private String accessUtf8(int cpIndex) { + Object object = cpdata[cpIndex]; + if (object instanceof String) { + return (String) object; + } + int[] ptrAndLen = (int[]) object; + String value; + try { + value = new String(classbytes, ptrAndLen[0], ptrAndLen[1], "UTF8"); + } + catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Bad data found at constant pool position " + cpIndex + " offset=" + + ptrAndLen[0] + " length=" + ptrAndLen[1], e); + } + cpdata[cpIndex] = value; + return value; + } + + /** + * @return an int constructed from the next four bytes to be processed + */ + private final int readInt() { + return ((classbytes[ptr++] & 0xFF) << 24) + ((classbytes[ptr++] & 0xFF) << 16) + + ((classbytes[ptr++] & 0xFF) << 8) + + (classbytes[ptr++] & 0xFF); + } + + /** + * @return an unsigned short constructed from the next two bytes to be processed + */ + private final int readUnsignedShort() { + return ((classbytes[ptr++] & 0xff) << 8) + (classbytes[ptr++] & 0xff); + } + + private boolean processConstantPoolEntry(int index) throws IOException { + byte b = classbytes[ptr++]; + switch (b) { + case CONSTANT_Integer: // CONSTANT_Integer_info { u1 tag; u4 bytes; } + case CONSTANT_Float: // CONSTANT_Float_info { u1 tag; u4 bytes; } + case CONSTANT_Fieldref: // CONSTANT_Fieldref_info { u1 tag; u2 class_index; u2 name_and_type_index; } + case CONSTANT_InterfaceMethodref: // CONSTANT_InterfaceMethodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } + case CONSTANT_InvokeDynamic: // CONSTANT_InvokeDynamic_info { u1 tag; u2 bootstrap_method_attr_index; u2 name_and_type_index; } + ptr += 4; + break; + case CONSTANT_Utf8: + // CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; } + // Cache just the index and length - do not unpack it now + int len = readUnsignedShort(); + cpdata[index] = new int[] { ptr, len }; + ptr += len; + break; + case CONSTANT_Long: // CONSTANT_Long_info { u1 tag; u4 high_bytes; u4 low_bytes; } + case CONSTANT_Double: // CONSTANT_Double_info { u1 tag; u4 high_bytes; u4 low_bytes; } + ptr += 8; + return true; + case CONSTANT_Class: // CONSTANT_Class_info { u1 tag; u2 name_index; } + type[index] = b; + cpdata[index] = readUnsignedShort(); + break; + case CONSTANT_Methodref: + // CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; } + type[index] = b; + cpdata[index] = new int[] { readUnsignedShort(), readUnsignedShort() }; + break; + case CONSTANT_NameAndType: + // The CONSTANT_NameAndType_info structure is used to represent a field or method, without indicating which class or interface type it belongs to: + // CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; } + // type[index] = b; + cpdata[index] = readUnsignedShort(); + ptr += 2; // skip the descriptor for now + break; + case CONSTANT_MethodHandle: + // CONSTANT_MethodHandle_info { u1 tag; u1 reference_kind; u2 reference_index; } + ptr += 3; + break; + case CONSTANT_String: // CONSTANT_String_info { u1 tag; u2 string_index; } + case CONSTANT_MethodType: // CONSTANT_MethodType_info { u1 tag; u2 descriptor_index; } + ptr += 2; + break; + default: + throw new IllegalStateException("Entry: " + index + " " + Byte.toString(b)); + } + return false; + } + + private void computeReferences() { + for (int i = 0; i < cpsize; i++) { + switch (type[i]) { + case CONSTANT_Class: + int classindex = ((Integer) cpdata[i]); + String classname = accessUtf8(classindex); + if (classname == null) { + throw new IllegalStateException(); + } + referencedClasses.add(classname); + break; + case CONSTANT_Methodref: + int[] indexes = (int[]) cpdata[i]; + int classindex2 = indexes[0]; + int nameAndTypeIndex = indexes[1]; + StringBuilder s = new StringBuilder(); + String theClassName = accessUtf8((Integer) cpdata[classindex2]); + if (theClassName.charAt(0) == 'j') { + s.append(theClassName); + s.append("."); + s.append(accessUtf8((Integer) cpdata[nameAndTypeIndex])); + referencedMethods.add(s.toString()); + } + break; + // private final static byte CONSTANT_Utf8 = 1; + // private final static byte CONSTANT_Integer = 3; + // private final static byte CONSTANT_Float = 4; + // private final static byte CONSTANT_Long = 5; + // private final static byte CONSTANT_Double = 6; + // private final static byte CONSTANT_String = 8; + // private final static byte CONSTANT_Fieldref = 9; + // private final static byte CONSTANT_InterfaceMethodref = 11; + // private final static byte CONSTANT_NameAndType = 12; + } + } + } + + + public static class References { + + public final String slashedClassName; + + private final List referencedClasses; + + private final List referencedMethods; + + References(String slashedClassName, List rc, List rm) { + this.slashedClassName = slashedClassName; + this.referencedClasses = rc; + this.referencedMethods = rm; + } + + @Override + public String toString() { + StringBuilder s = new StringBuilder(); + s.append("Class=").append(slashedClassName).append("\n"); + s.append("ReferencedClasses=#").append(referencedClasses.size()).append("\n"); + s.append("ReferencedMethods=#").append(referencedMethods.size()).append("\n"); + return s.toString(); + } + + /** + * @return list of classes of the form org/springframework/boot/configurationprocessor/json/JSONException + */ + public List getReferencedClasses() { + return referencedClasses; + } + } + + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/HintDescriptor.java b/src/main/java/org/springframework/graal/type/HintDescriptor.java new file mode 100644 index 000000000..7b4765e3a --- /dev/null +++ b/src/main/java/org/springframework/graal/type/HintDescriptor.java @@ -0,0 +1,51 @@ +package org.springframework.graal.type; + +import java.util.List; + +/** + * Represents a usage of @CompilationHint. + * + * @author Andy Clement + */ +public class HintDescriptor { + + // This is the annotation 'chain' from the type that got asked about to the thing with @CompilationHint + // This chain may be short (e.g. if an autoconfig has @ConditionalOnClass on it which itself + // is meta annotated with @CompilationHint): chain will be [ConditionalOnClass] + // or it may be long: (e.g. if the autoconfig has an @EnableFoo on it which itself is marked + // with @ConditionalOnClass which in turn has CompilationHint) chain will be [EnableFoo, ConditionalOnClass] + private List annotationChain; + + // If any types hinted at are missing, is this type effectively redundant? + private boolean skipIfTypesMissing; + + // Should any types references be followed because they may also have further + // hints on them (e.g. @Import(Foo) where Foo has @Import(Bar) on it) + private boolean follow; + + private String[] name; + + public HintDescriptor(List annotationChain, boolean skipIfTypesMissing2, boolean follow, String[] name) { + this.annotationChain = annotationChain; + this.skipIfTypesMissing = skipIfTypesMissing2; + this.follow = follow; + this.name = name; + } + + public List getAnnotationChain() { + return annotationChain; + } + + public boolean isSkipIfTypesMissing() { + return skipIfTypesMissing; + } + + public boolean isFollow() { + return follow; + } + + public String[] getName() { + return name; + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/Method.java b/src/main/java/org/springframework/graal/type/Method.java new file mode 100644 index 000000000..8bb046d77 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/Method.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 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. + */ + +package org.springframework.graal.type; + +import org.objectweb.asm.tree.MethodNode; + +public class Method { + + MethodNode mn; + + public Method(MethodNode mn) { + this.mn = mn; + } + + public String toString() { + return mn.name+mn.desc; + } + + public String getName() { + return mn.name; + } + + public String getDesc() { + return mn.desc; + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/MissingTypeException.java b/src/main/java/org/springframework/graal/type/MissingTypeException.java new file mode 100644 index 000000000..e63af7f94 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/MissingTypeException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.type; + +public class MissingTypeException extends RuntimeException { + private static final long serialVersionUID = 1L; + private String typename; + + public MissingTypeException(String slashedTypeName) { + this.typename = slashedTypeName; + } + + @Override + public String getMessage() { + return "Unable to find class file for " + typename; + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/Type.java b/src/main/java/org/springframework/graal/type/Type.java new file mode 100644 index 000000000..36eb3dbe1 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/Type.java @@ -0,0 +1,1074 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.type; + +import java.lang.reflect.Modifier; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.Stack; +import java.util.stream.Collectors; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.signature.SignatureReader; +import org.objectweb.asm.signature.SignatureVisitor; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InnerClassNode; +import org.objectweb.asm.tree.MethodNode; + +/** + * @author Andy Clement + */ +public class Type { + + // Spring Security + public final static String OAuth2ImportSelector = "Lorg/springframework/security/config/annotation/web/configuration/OAuth2ImportSelector;"; + + public final static String SpringWebMvcImportSelector = "Lorg/springframework/security/config/annotation/web/configuration/SpringWebMvcImportSelector;"; + + public final static String ImportSelector ="Lorg/springframework/context/annotation/ImportSelector;"; + + public final static String TransactionManagementConfigurationSelector = "Lorg/springframework/transaction/annotation/TransactionManagementConfigurationSelector;"; + + public final static String SpringDataWebConfigurationSelector = "Lorg/springframework/data/web/config/EnableSpringDataWebSupport$SpringDataWebConfigurationImportSelector;"; + + public final static String SpringDataWebQueryDslSelector = "Lorg/springframework/data/web/config/EnableSpringDataWebSupport$QuerydslActivator;"; + + public final static String AdviceModeImportSelector="Lorg/springframework/context/annotation/AdviceModeImportSelector;"; + + public final static String EnableConfigurationPropertiesImportSelector = "Lorg/springframework/boot/context/properties/EnableConfigurationPropertiesImportSelector;"; + + public final static String CacheConfigurationImportSelector = "Lorg/springframework/boot/autoconfigure/cache/CacheAutoConfiguration$CacheConfigurationImportSelector;"; + + public final static String RabbitConfigurationImportSelector = "Lorg/springframework/amqp/rabbit/annotation/RabbitListenerConfigurationSelector;"; + + public final static String AtBean = "Lorg/springframework/context/annotation/Bean;"; + + public final static String AtImports = "Lorg/springframework/context/annotation/Import;"; + + public final static String AtEnableConfigurationProperties = "Lorg/springframework/boot/context/properties/EnableConfigurationProperties;"; + + public final static String AtConditionalOnClass = "Lorg/springframework/boot/autoconfigure/condition/ConditionalOnClass;"; + + public final static String AtConditional = "Lorg/springframework/context/annotation/Conditional;"; + + public final static String AtConditionalOnMissingBean = "Lorg/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean;"; + + public final static String HypermediaConfigurationImportSelector = "Lorg/springframework/hateoas/config/HypermediaConfigurationImportSelector;"; + + public final static String WebStackImportSelector = "Lorg/springframework/hateoas/config/WebStackImportSelector;"; + + public final static Type MISSING = new Type(null, null); + + public final static Type[] NO_INTERFACES = new Type[0]; + + protected static Set validBoxing = new HashSet(); + + + private TypeSystem typeSystem; + + private ClassNode node; + + private Type[] interfaces; + + public Type(TypeSystem typeSystem, ClassNode node) { + this.typeSystem = typeSystem; + this.node = node; + } + + public static Type forClassNode(TypeSystem typeSystem, ClassNode node) { + return new Type(typeSystem, node); + } + + /** + * @return typename in slashed form (aaa/bbb/ccc/Ddd$Eee) + */ + public String getName() { + return node.name; + } + + public String getDottedName() { + return node.name.replace("/", "."); + } + + public Type getSuperclass() { + if (node.superName == null) { + return null; + } + return typeSystem.resolveSlashed(node.superName); + } + + @Override + public String toString() { + return "Type:"+getName(); + } + + public Type[] getInterfaces() { + if (interfaces == null) { + List itfs = node.interfaces; + if (itfs.size() == 0) { + interfaces = NO_INTERFACES; + } else { + interfaces = new Type[itfs.size()]; + for (int i = 0; i < itfs.size(); i++) { + interfaces[i] = typeSystem.resolveSlashed(itfs.get(i)); + } + } + } + return interfaces; + } + + /** @return List of slashed interface types */ + public List getInterfacesStrings() { + return node.interfaces; + } + + /** @return slashed supertype name */ + public String getSuperclassString() { + return node.superName; + } + + public List getTypesInSignature() { + if (node.signature == null) { + return Collections.emptyList(); + } else { + SignatureReader reader = new SignatureReader(node.signature); + TypeCollector tc = new TypeCollector(); + reader.accept(tc); + return tc.getTypes(); + } + } + + static class TypeCollector extends SignatureVisitor { + + List types = null; + + public TypeCollector() { + super(Opcodes.ASM7); + } + + @Override + public void visitClassType(String name) { + if (types == null) { + types = new ArrayList(); + } + types.add(name); + } + + public List getTypes() { + if (types == null) { + return Collections.emptyList(); + } else { + return types; + } + } + + } + + public boolean implementsInterface(String interfaceName) { + Type[] interfacesToCheck = getInterfaces(); + for (Type interfaceToCheck : interfacesToCheck) { + if (interfaceToCheck.getName().equals(interfaceName)) { + return true; + } + if (interfaceToCheck.implementsInterface(interfaceName)) { + return true; + } + } + Type superclass = getSuperclass(); + while (superclass != null) { + if (superclass.implementsInterface(interfaceName)) { + return true; + } + superclass = superclass.getSuperclass(); + } + return false; + } + + public List getMethodsWithAnnotation(String string) { + return node.methods.stream().filter(m -> hasAnnotation(m, string)).map(m -> wrap(m)) + .collect(Collectors.toList()); + } + + public List getMethodsWithAtBean() { + return getMethodsWithAnnotation(AtBean); + } + + public Method wrap(MethodNode mn) { + return new Method(mn); + } + + private boolean hasAnnotation(MethodNode m, String string) { + List visibleAnnotations = m.visibleAnnotations; + Optional findAny = visibleAnnotations == null ? Optional.empty() + : visibleAnnotations.stream().filter(a -> a.desc.equals(string)).findFirst(); + return findAny.isPresent(); + } + + static { + validBoxing.add("Ljava/lang/Byte;B"); + validBoxing.add("Ljava/lang/Character;C"); + validBoxing.add("Ljava/lang/Double;D"); + validBoxing.add("Ljava/lang/Float;F"); + validBoxing.add("Ljava/lang/Integer;I"); + validBoxing.add("Ljava/lang/Long;J"); + validBoxing.add("Ljava/lang/Short;S"); + validBoxing.add("Ljava/lang/Boolean;Z"); + validBoxing.add("BLjava/lang/Byte;"); + validBoxing.add("CLjava/lang/Character;"); + validBoxing.add("DLjava/lang/Double;"); + validBoxing.add("FLjava/lang/Float;"); + validBoxing.add("ILjava/lang/Integer;"); + validBoxing.add("JLjava/lang/Long;"); + validBoxing.add("SLjava/lang/Short;"); + validBoxing.add("ZLjava/lang/Boolean;"); + } + + public boolean isAssignableFrom(Type other) { + if (other.isPrimitiveType()) { + if (validBoxing.contains(this.getName() + other.getName())) { + return true; + } + } + if (this == other) { + return true; + } + + if (this.getName().equals("Ljava/lang/Object;")) { + return true; + } + +// if (!isTypeVariableReference() +// && other.getSignature().equals("Ljava/lang/Object;")) { +// return false; +// } + +// boolean thisRaw = this.isRawType(); +// if (thisRaw && other.isParameterizedOrGenericType()) { +// return isAssignableFrom(other.getRawType()); +// } +// +// boolean thisGeneric = this.isGenericType(); +// if (thisGeneric && other.isParameterizedOrRawType()) { +// return isAssignableFrom(other.getGenericType()); +// } +// +// if (this.isParameterizedType()) { +// // look at wildcards... +// if (((ReferenceType) this.getRawType()).isAssignableFrom(other)) { +// boolean wildcardsAllTheWay = true; +// ResolvedType[] myParameters = this.getResolvedTypeParameters(); +// for (int i = 0; i < myParameters.length; i++) { +// if (!myParameters[i].isGenericWildcard()) { +// wildcardsAllTheWay = false; +// } else { +// BoundedReferenceType boundedRT = (BoundedReferenceType) myParameters[i]; +// if (boundedRT.isExtends() || boundedRT.isSuper()) { +// wildcardsAllTheWay = false; +// } +// } +// } +// if (wildcardsAllTheWay && !other.isParameterizedType()) { +// return true; +// } +// // we have to match by parameters one at a time +// ResolvedType[] theirParameters = other +// .getResolvedTypeParameters(); +// boolean parametersAssignable = true; +// if (myParameters.length == theirParameters.length) { +// for (int i = 0; i < myParameters.length +// && parametersAssignable; i++) { +// if (myParameters[i] == theirParameters[i]) { +// continue; +// } +// // dont do this: pr253109 +// // if +// // (myParameters[i].isAssignableFrom(theirParameters[i], +// // allowMissing)) { +// // continue; +// // } +// ResolvedType mp = myParameters[i]; +// ResolvedType tp = theirParameters[i]; +// if (mp.isParameterizedType() +// && tp.isParameterizedType()) { +// if (mp.getGenericType().equals(tp.getGenericType())) { +// UnresolvedType[] mtps = mp.getTypeParameters(); +// UnresolvedType[] ttps = tp.getTypeParameters(); +// for (int ii = 0; ii < mtps.length; ii++) { +// if (mtps[ii].isTypeVariableReference() +// && ttps[ii] +// .isTypeVariableReference()) { +// TypeVariable mtv = ((TypeVariableReferenceType) mtps[ii]) +// .getTypeVariable(); +// boolean b = mtv +// .canBeBoundTo((ResolvedType) ttps[ii]); +// if (!b) {// TODO incomplete testing here +// // I think +// parametersAssignable = false; +// break; +// } +// } else { +// parametersAssignable = false; +// break; +// } +// } +// continue; +// } else { +// parametersAssignable = false; +// break; +// } +// } +// if (myParameters[i].isTypeVariableReference() +// && theirParameters[i].isTypeVariableReference()) { +// TypeVariable myTV = ((TypeVariableReferenceType) myParameters[i]) +// .getTypeVariable(); +// // TypeVariable theirTV = +// // ((TypeVariableReferenceType) +// // theirParameters[i]).getTypeVariable(); +// boolean b = myTV.canBeBoundTo(theirParameters[i]); +// if (!b) {// TODO incomplete testing here I think +// parametersAssignable = false; +// break; +// } else { +// continue; +// } +// } +// if (!myParameters[i].isGenericWildcard()) { +// parametersAssignable = false; +// break; +// } else { +// BoundedReferenceType wildcardType = (BoundedReferenceType) myParameters[i]; +// if (!wildcardType.alwaysMatches(theirParameters[i])) { +// parametersAssignable = false; +// break; +// } +// } +// } +// } else { +// parametersAssignable = false; +// } +// if (parametersAssignable) { +// return true; +// } +// } +// } +// +// // eg this=T other=Ljava/lang/Object; +// if (isTypeVariableReference() && !other.isTypeVariableReference()) { +// TypeVariable aVar = ((TypeVariableReference) this) +// .getTypeVariable(); +// return aVar.resolve(world).canBeBoundTo(other); +// } +// +// if (other.isTypeVariableReference()) { +// TypeVariableReferenceType otherType = (TypeVariableReferenceType) other; +// if (this instanceof TypeVariableReference) { +// return ((TypeVariableReference) this) +// .getTypeVariable() +// .resolve(world) +// .canBeBoundTo( +// otherType.getTypeVariable().getFirstBound() +// .resolve(world));// pr171952 +// // return +// // ((TypeVariableReference)this).getTypeVariable()==otherType +// // .getTypeVariable(); +// } else { +// // FIXME asc should this say canBeBoundTo?? +// return this.isAssignableFrom(otherType.getTypeVariable() +// .getFirstBound().resolve(world)); +// } +// } + + Type[] interfaces = other.getInterfaces(); + for (Type intface : interfaces) { + boolean b; +// if (thisRaw && intface.isParameterizedOrGenericType()) { +// b = this.isAssignableFrom(intface.getRawType(), allowMissing); +// } else { + b = this.isAssignableFrom(intface); +// } + if (b) { + return true; + } + } + Type superclass = other.getSuperclass(); + if (superclass != null) { + boolean b; +// if (thisRaw && superclass.isParameterizedOrGenericType()) { +// b = this.isAssignableFrom(superclass.getRawType(), allowMissing); +// } else { + b = this.isAssignableFrom(superclass); +// } + if (b) { + return true; + } + } + return false; + } + + private boolean isPrimitiveType() { + return false; + } + + public boolean isInterface() { + return Modifier.isInterface(node.access); + } + + public boolean hasAnnotationInHierarchy(String lookingFor) { + return hasAnnotationInHierarchy(lookingFor, new ArrayList()); + } + + public boolean hasAnnotationInHierarchy(String lookingFor, List seen) { + if (node.visibleAnnotations != null) { + for (AnnotationNode anno : node.visibleAnnotations) { + if (seen.contains(anno.desc)) + continue; + seen.add(anno.desc); + // System.out.println("Comparing "+anno.desc+" with "+lookingFor); + if (anno.desc.equals(lookingFor)) { + return true; + } + try { + Type resolve = typeSystem.Lresolve(anno.desc); + if (resolve.hasAnnotationInHierarchy(lookingFor, seen)) { + return true; + } + } catch (MissingTypeException mte) { + // not on classpath, that's ok + } + } + } + return false; + } + + public boolean isMetaAnnotated(String slashedTypeDescriptor) { + return isMetaAnnotated(slashedTypeDescriptor, new HashSet<>()); + } + + public boolean isMetaAnnotated(String slashedTypeDescriptor, Set seen) { +// System.out.println("Looking at "+this.getName()+" for "+slashedTypeDescriptor); + for (Type t : this.getAnnotations()) { + String tname = t.getName(); + if (tname.equals(slashedTypeDescriptor)) { + return true; + } + if (!seen.contains(tname)) { + seen.add(tname); + if (t.isMetaAnnotated(slashedTypeDescriptor, seen)) { + return true; + } + } + } + return false; + } + + List annotations = null; + + public static final List NO_ANNOTATIONS = Collections.emptyList(); + + + public List findConditionalOnMissingBeanValue() { + List findAnnotationValue = findAnnotationValue(AtConditionalOnMissingBean, false); + if (findAnnotationValue==null) { + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + if (an.desc.equals(AtConditionalOnMissingBean)) { + System.out.println("??? found nothing on this @COC annotated thing "+this.getName()); + } + } + } + } + return findAnnotationValue; + } + + public List findConditionalOnClassValue() { + List findAnnotationValue = findAnnotationValue(AtConditionalOnClass, false); + if (findAnnotationValue==null) { + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + if (an.desc.equals(AtConditionalOnClass)) { + System.out.println("??? found nothing on this @COC annotated thing "+this.getName()); + } + } + } + } + return findAnnotationValue; + } + + public List findEnableConfigurationPropertiesValue() { + List values = findAnnotationValue(AtEnableConfigurationProperties, false); + return values; + } + + public Map> findImports() { + return findAnnotationValueWithHostAnnotation(AtImports, true, new HashSet<>()); + } + + public List findAnnotationValue(String annotationType, boolean searchMeta) { + return findAnnotationValue(annotationType, searchMeta, new HashSet<>()); + } + + @SuppressWarnings("unchecked") + public Map> findAnnotationValueWithHostAnnotation(String annotationType, boolean searchMeta, Set visited) { + if (!visited.add(this.getName())) { + return Collections.emptyMap(); + } + Map> collectedResults = new LinkedHashMap<>(); + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + if (an.desc.equals(annotationType)) { + List values = an.values; + if (values != null) { + for (int i=0;i importedReferences = ((List)values.get(i+1)) + .stream() + .map(t -> t.getDescriptor()) + .collect(Collectors.toList()); + collectedResults.put(this.getName().replace("/", "."), importedReferences); + } + } + } + } + } + if (searchMeta) { + for (AnnotationNode an: node.visibleAnnotations) { + // For example @EnableSomething might have @Import on it + Type annoType = null; + try { + annoType = typeSystem.Lresolve(an.desc); + } catch (MissingTypeException mte) { + System.out.println("SBG: WARNING: Unable to find "+an.desc+" skipping..."); + continue; + } + collectedResults.putAll(annoType.findAnnotationValueWithHostAnnotation(annotationType, searchMeta, visited)); + } + } + } + return collectedResults; + } + + + @SuppressWarnings("unchecked") + public List findAnnotationValue(String annotationType, boolean searchMeta, Set visited) { + if (!visited.add(this.getName())) { + return Collections.emptyList(); + } + List collectedResults = new ArrayList<>(); + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + if (an.desc.equals(annotationType)) { + List values = an.values; + if (values != null) { + for (int i=0;i)values.get(i+1)) + .stream() + .map(t -> t.getDescriptor()) + .collect(Collectors.toCollection(() -> collectedResults)); + } + } + } + } + } + if (searchMeta) { + for (AnnotationNode an: node.visibleAnnotations) { + // For example @EnableSomething might have @Import on it + Type annoType = typeSystem.Lresolve(an.desc); + collectedResults.addAll(annoType.findAnnotationValue(annotationType, searchMeta, visited)); + } + } + } + return collectedResults; + } + + private List getAnnotations() { + if (annotations == null) { + annotations = new ArrayList<>(); + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + try { + annotations.add(this.typeSystem.Lresolve(an.desc)); + } catch (MissingTypeException mte) { + // that's ok you weren't relying on it anyway! + } + } + } +// if (node.invisibleAnnotations != null) { +// for (AnnotationNode an: node.invisibleAnnotations) { +// try { +// annotations.add(this.typeSystem.Lresolve(an.desc)); +// } catch (MissingTypeException mte) { +// // that's ok you weren't relying on it anyway! +// } +// } +// } + if (annotations.size() == 0) { + annotations = NO_ANNOTATIONS; + } + } + return annotations; + } + + public Type findAnnotation(Type searchType) { + List annos = getAnnotations(); + for (Type anno : annos) { + if (anno.equals(searchType)) { + return anno; + } + } + return null; + } + + /** + * @return true if meta annotated with org.springframework.stereotype.Indexed + */ + public Entry isIndexedOrEntity() { + Type indexedType = isMetaAnnotated2("Lorg/springframework/stereotype/Indexed;"); + if (indexedType != null) { + return new AbstractMap.SimpleImmutableEntry(this.node.name.replace("/", "."),indexedType.getName().replace("/", ".")); + } else { + indexedType = isMetaAnnotated2("Ljavax/persistence/Entity;"); + if (indexedType != null) { + return new AbstractMap.SimpleImmutableEntry(this.node.name.replace("/", "."),"javax.persistence.Entity"); + } + Type t = isIndexedInHierarchy(); + if ( t != null) { + // This should catch repositories where the Repository interface is marked @Indexed + //app.main.model.FooRepository=org.springframework.data.repository.Repository") + return new AbstractMap.SimpleImmutableEntry(this.node.name.replace("/","."), t.node.name.replace("/",".")); + } + return null; + } + } + + private Type isIndexedInHierarchy() { + if (isAnnotated("Lorg/springframework/stereotype/Indexed;")) { + return this; + } + Type[] is = getInterfaces(); + for (Type i: is) { + Type t = i.isIndexedInHierarchy(); + if (t != null) { + return t; + } + } + Type sc = getSuperclass(); + if (sc!=null) { + return sc.isIndexedInHierarchy(); + } + return null; + } +// app.main.model.FooRepository=org.springframework.data.repository.Repository +// public List findConditionalOnClassValue() { +// if (node.visibleAnnotations != null) { +// for (AnnotationNode an : node.visibleAnnotations) { +// if (an.desc.equals("Lorg/springframework/boot/autoconfigure/condition/ConditionalOnClass;")) { +// List values = an.values; +// for (int i=0;i)values.get(i+1)) +// .stream() +// .map(t -> t.getDescriptor()) +// .collect(Collectors.toList()); +// } +// } +//// for (Object o: values) { +//// System.out.println("<> "+o+" "+(o==null?"":o.ge + + private Type isMetaAnnotated2(String Ldescriptor) { + return isMetaAnnotated2(Ldescriptor, new HashSet<>()); + } + + private boolean isAnnotated(String Ldescriptor) { + if (node.visibleAnnotations != null) { + for (AnnotationNode an: node.visibleAnnotations) { + if (an.desc.equals(Ldescriptor)) { + return true; + } + } + } + return false; + } + + private Type isMetaAnnotated2(String Ldescriptor, Set seen) { + if (node.visibleAnnotations != null) { + for (AnnotationNode an: node.visibleAnnotations) { + if (seen.add(an.desc)) { + if (an.desc.equals(Ldescriptor)) { + return this;//typeSystem.Lresolve(an.desc); + } else { + Type annoType = typeSystem.Lresolve(an.desc); + Type meta = annoType.isMetaAnnotated2(Ldescriptor, seen); + if (meta != null) { + return meta; + } + } + } + } + } + return null; + } + + public List getNestedTypes() { + List result = null; + List innerClasses = node.innerClasses; + for (InnerClassNode inner: innerClasses) { + if (inner.outerName==null || !inner.outerName.equals(getName())) { +// System.out.println("SKIPPPING "+inner.name+" because outer is "+inner.outerName+" and we are looking at "+getName()); + continue; + } + if (inner.name.equals(getName())) { + continue; + } + Type t = typeSystem.resolve(inner.name); // aaa/bbb/ccc/Ddd$Eee + if (result == null) { + result = new ArrayList<>(); + } + result.add(t); + } + return result==null?Collections.emptyList():result; + } + + public String getDescriptor() { + return "L"+node.name.replace(".", "/")+";"; + } + + /** + * Find @CompilationHints directly on this type or used as a meta-annotation on annotations on this type. + */ + public Map> getHints() { + Map> hints = new LinkedHashMap<>(); + CompilationHint hint = proposedAnnotations.get(getDescriptor()); + if (hint !=null) { + List s = new ArrayList<>(); + s.add(this); + hints.put(new HintDescriptor(s, hint.skipIfTypesMissing, hint.follow, hint.name), null); + } + if (node.visibleAnnotations != null) { + for (AnnotationNode an: node.visibleAnnotations) { + Type annotationType = typeSystem.Lresolve(an.desc, true); + if (annotationType == null) { + System.out.println("Couldn't resolve "+an.desc); + } else { + Stack s = new Stack<>(); + s.push(this); + annotationType.collectHints(an, hints, new HashSet<>(), s); + } + } + } + if (implementsImportSelector() && hints.size()==0) { + throw new IllegalStateException("No @CompilationHint found for import selector: "+getDottedName()); + } + + return hints.size()==0? Collections.emptyMap():hints; + } + + // TODO repeatable annotations... + + private void collectHints(AnnotationNode an, Map> hints, Set visited, Stack annotationChain) { + if (!visited.add(an)) { + return; + } + try { + annotationChain.push(this); + // Am I a compilation hint? + CompilationHint hint = proposedAnnotations.get(an.desc); + if (hint !=null) { + hints.put(new HintDescriptor(new ArrayList<>(annotationChain), hint.skipIfTypesMissing, hint.follow, hint.name), collectTypes(an)); + } + // check for meta annotation + if (node.visibleAnnotations != null) { + for (AnnotationNode an2: node.visibleAnnotations) { + Type annotationType = typeSystem.Lresolve(an2.desc, true); + if (annotationType == null) { + System.out.println("Couldn't resolve "+an2.desc); + } else { + annotationType.collectHints(an2, hints, visited, annotationChain); + } + } + } + } finally { + annotationChain.pop(); + } + } + + private CompilationHint findCompilationHintHelper(HashSet visited) { + if (!visited.add(this)) { + return null; + } + if (node.visibleAnnotations != null) { + for (AnnotationNode an : node.visibleAnnotations) { + CompilationHint compilationHint = proposedAnnotations.get(an.desc); + if (compilationHint != null) { + return compilationHint; + } + Type resolvedAnnotation = typeSystem.Lresolve(an.desc); + compilationHint = resolvedAnnotation.findCompilationHintHelper(visited); + if (compilationHint != null) { + return compilationHint; + } + } + } + return null; + } + + private List collectTypes(AnnotationNode an) { + List values = an.values; + if (values != null) { + for (int i=0;i importedReferences = ((List)values.get(i+1)) + .stream() + .map(t -> t.getDescriptor()) + .collect(Collectors.toList()); + return importedReferences; + } + } + } + return Collections.emptyList(); + } + + private static Map proposedAnnotations = new HashMap<>(); + + static { + // @ConditionalOnClass has @CompilationHint(skipIfTypesMissing=true, follow=false) + proposedAnnotations.put(AtConditionalOnClass, new CompilationHint(true,false)); + + // @ConditionalOnMissingBean has @CompilationHint(skipIfTypesMissing=true, follow=false) + proposedAnnotations.put(AtConditionalOnMissingBean, new CompilationHint(true, false)); + + // TODO can be {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar} + // @Imports has @CompilationHint(skipIfTypesMissing=false?, follow=true) + proposedAnnotations.put(AtImports, new CompilationHint(false, true)); + + // @Conditional has @CompilationHint(skipIfTypesMissing=false, follow=false) + proposedAnnotations.put(AtConditional, new CompilationHint(false, false)); + + // TODO do configuration properties chain? + // @EnableConfigurationProperties has @CompilationHint(skipIfTypesMissing=false, follow=false) + proposedAnnotations.put(AtEnableConfigurationProperties, new CompilationHint(false, false)); + + // @EnableConfigurationPropertiesImportSelector has + // @CompilationHint(skipIfTypesMissing=false, follow=false, name={ + // ConfigurationPropertiesBeanRegistrar.class.getName(), + // ConfigurationPropertiesBindingPostProcessorRegistrar.class.getName() }) + // proposedAnnotations.put(AtEnableConfigurationProperties, new CompilationHint(false, false)); + + // CacheConfigurationImportSelector has + // @CompilationHint(skipIfTypesMissing=true, follow=false, name={ + // "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration", + // "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"}) + proposedAnnotations.put(CacheConfigurationImportSelector, + new CompilationHint(false,false, new String[] { + "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration", + "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"} + )); + + proposedAnnotations.put(RabbitConfigurationImportSelector, + new CompilationHint(true,true, new String[] { + "org.springframework.amqp.rabbit.annotation.RabbitBootstrapConfiguration"} + )); + + // TransactionManagementConfigurationSelector has + // @CompilationHint(skipIfTypesMissing=true, follow=true, name={...}) + proposedAnnotations.put(TransactionManagementConfigurationSelector, + new CompilationHint(true, true, new String[] { + "org.springframework.context.annotation.AutoProxyRegistrar", + "org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration", + "org.springframework.transaction.aspectj.AspectJJtaTransactionManagementConfiguration", + "org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration"} + )); + + // EnableSpringDataWebSupport. TODO: there are others in spring.factories. + proposedAnnotations.put(SpringDataWebConfigurationSelector, + new CompilationHint(true, true, new String[] { + "org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration", + "org.springframework.data.web.config.SpringDataWebConfiguration"} + )); + + // EnableSpringDataWebSupport. TODO: there are others in spring.factories. + proposedAnnotations.put(SpringDataWebQueryDslSelector, + new CompilationHint(true, true, new String[] { + "org.springframework.data.web.config.QuerydslWebConfiguration"} + )); + + // EnableConfigurationPropertiesImportSelector has + // @CompilationHint(skipIfTypesMissing=true, follow=false, name={ + // "org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector$ConfigurationPropertiesBeanRegistrar", + // "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar"}) + proposedAnnotations.put(EnableConfigurationPropertiesImportSelector, + new CompilationHint(false,false, new String[] { + "org.springframework.boot.context.properties.EnableConfigurationPropertiesImportSelector$ConfigurationPropertiesBeanRegistrar", + "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar"} + )); + + + // Not quite right... this is a superclass of a selector we've already added... + proposedAnnotations.put(AdviceModeImportSelector, + new CompilationHint(true, true, new String[0] + )); + + + // Spring Security! + proposedAnnotations.put(SpringWebMvcImportSelector, + new CompilationHint(false, true, new String[] { + "org.springframework.web.servlet.DispatcherServlet", + "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" + })); + proposedAnnotations.put(OAuth2ImportSelector, + new CompilationHint(false, true, new String[] { + "org.springframework.security.oauth2.client.registration.ClientRegistration", + "org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration" + })); + + proposedAnnotations.put(HypermediaConfigurationImportSelector, + new CompilationHint(false, true, new String[] { + "org.springframework.hateoas.config.HypermediaConfigurationImportSelector" + })); + + proposedAnnotations.put(WebStackImportSelector, + new CompilationHint(false, true, new String[] { + "org.springframework.hateoas.config.WebStackImportSelector" + })); + } + + private boolean implementsImportSelector() { + return implementsInterface(fromLdescriptorToSlashed(ImportSelector)); + } + + private String fromLdescriptorToSlashed(String Ldescriptor) { + return Ldescriptor.substring(1,Ldescriptor.length()-1); + } + + private CompilationHint findCompilationHint(Type annotationType) { + String descriptor = "L"+annotationType.getName().replace(".", "/")+";"; + CompilationHint hint = proposedAnnotations.get(descriptor); + if (hint !=null) { + return hint; + } else { + // check for meta annotation + return annotationType.findCompilationHintHelper(new HashSet<>()); + } + } + + // TODO what about AliasFor usage in spring annotations themselves? does that trip this code up when we start looking at particular fields? + + static class CompilationHint { + boolean follow; + boolean skipIfTypesMissing; + private String[] name; + + public CompilationHint(boolean skipIfTypesMissing, boolean follow) { + this(skipIfTypesMissing,follow,new String[] {}); + } + + // TODO what about whether you need to reflect on ctors/methods/fields? + public CompilationHint(boolean skipIfTypesMissing, boolean follow, String[] name) { + this.skipIfTypesMissing = skipIfTypesMissing; + this.follow = follow; + this.name = name; + } + } + + public void collectMissingAnnotationTypesHelper(Set missingAnnotationTypes, HashSet visited) { + if (!visited.add(this)) { + return; + } + if (node.visibleAnnotations != null) { + for (AnnotationNode an: node.visibleAnnotations) { + Type annotationType = typeSystem.Lresolve(an.desc, true); + if (annotationType == null) { + missingAnnotationTypes.add(an.desc.substring(0,an.desc.length()-1).replace("/", ".")); + } else { + annotationType.collectMissingAnnotationTypesHelper(missingAnnotationTypes, visited); + } + } + } + } + +// @SuppressWarnings("unchecked") +// public void findCompilationHints(String annotationType, Map> hintCollector, Set visited) { +// if (!visited.add(this.getName())) { +// return Collections.emptyMap(); +// } +// Map> collectedResults = new LinkedHashMap<>(); +// if (node.visibleAnnotations != null) { +// for (AnnotationNode an : node.visibleAnnotations) { +// if (an.desc.equals(annotationType)) { +// List values = an.values; +// if (values != null) { +// for (int i=0;i importedReferences = ((List)values.get(i+1)) +// .stream() +// .map(t -> t.getDescriptor()) +// .collect(Collectors.toList()); +// collectedResults.put(this.getName().replace("/", "."), importedReferences); +// } +// } +// } +// } +// } +// if (searchMeta) { +// for (AnnotationNode an: node.visibleAnnotations) { +// // For example @EnableSomething might have @Import on it +// Type annoType = null; +// try { +// annoType = typeSystem.Lresolve(an.desc); +// } catch (MissingTypeException mte) { +// System.out.println("SBG: WARNING: Unable to find "+an.desc+" skipping..."); +// continue; +// } +// collectedResults.putAll(annoType.findCompilationHints(annotationType, visited)); +// } +// } +// } +// return collectedResults; +// } + + + // Assume @ConditionalOnClass has @CompilationHint(skipIfTypesMissing=true) and both value() and name() in + // the annotation would have @CompilationTypeList + + + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/graal/type/TypeSystem.java b/src/main/java/org/springframework/graal/type/TypeSystem.java new file mode 100644 index 000000000..a4a042672 --- /dev/null +++ b/src/main/java/org/springframework/graal/type/TypeSystem.java @@ -0,0 +1,539 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.graal.type; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; + +/** + * Simple type system with some rudimentary caching. + */ +public class TypeSystem { + + public static String SPRING_AT_CONFIGURATION = "Lorg/springframework/context/annotation/Configuration;"; + + // Map of all types on the classpath that have some kind of annotations on them + Map annotatedTypes; + + // Classpath from which this type system will resolve types + private List classpath; + + // Cache of resolved types TODO time out entries? + private Map typeCache = new HashMap<>(); + + // Map of which zip files contain which packages TODO split package support + private Map packageCache = new HashMap<>(); + + // Map of which application files contain particular packages + private Map> appPackages = new HashMap<>(); + + + public static TypeSystem get(List classpath) { + return new TypeSystem(classpath); + } + + public TypeSystem(List classpath) { + this.classpath = classpath; + index(); + } + + public List getClasspath() { + return classpath; + } + + public Type resolveDotted(String dottedTypeName) { + String slashedTypeName = toSlashedName(dottedTypeName); + return resolveSlashed(slashedTypeName); + } + + public boolean canResolveSlashed(String slashedTypeName) { + try { + return resolveSlashed(slashedTypeName) != null; + } catch (RuntimeException re) { + if (re.getMessage().startsWith("Unable to find class file for")) { + return false; + } + throw re; + } + } + + public Type resolveSlashed(String slashedTypeName) { + return resolveSlashed(slashedTypeName, false); + } + + public Type resolveSlashed(String slashedTypeName, boolean allowNotFound) { + Type type = typeCache.get(slashedTypeName); + if (type == Type.MISSING) { + if (allowNotFound) { + return null; + } else { + throw new MissingTypeException(slashedTypeName); + } + } + if (type != null) { + return type; + } + byte[] bytes = find(slashedTypeName); + if (bytes == null) { + // System class? + InputStream resourceAsStream = Thread.currentThread().getContextClassLoader() + .getResourceAsStream(slashedTypeName + ".class"); + if (resourceAsStream == null) { + // cache a missingtype so we don't go looking again! + typeCache.put(slashedTypeName, Type.MISSING); + if (allowNotFound) { + return null; + } else { + throw new MissingTypeException(slashedTypeName); + } + } + try { + bytes = loadFromStream(resourceAsStream); + } catch (RuntimeException e) { + throw new RuntimeException("Problems loading class from resource stream: " + slashedTypeName, e); + } + } + ClassNode node = new ClassNode(); + ClassReader reader = new ClassReader(bytes); + reader.accept(node, ClassReader.SKIP_DEBUG); + type = Type.forClassNode(this, node); + typeCache.put(slashedTypeName, type); + return type; + } + + private String toSlashedName(String dottedTypeName) { + return dottedTypeName.replace(".", "/"); + } + + public boolean canResolve(String classname) { + if (classname.contains(".")) { + throw new RuntimeException("Dont pass dotted names to resolve() :" + classname); + } + return canResolveSlashed(classname); + } + + public Type resolve(String classname, boolean allowNotFound) { + if (classname.contains(".")) { + throw new RuntimeException("Dont pass dotted names to resolve() :" + classname); + } + return resolveSlashed(classname, allowNotFound); + } + + public Type resolve(String classname) { + return resolve(classname, false); + } + + public Type Lresolve(String desc) { + return resolve(desc.substring(1, desc.length() - 1)); + } + + public Type Lresolve(String desc, boolean silent) { + try { + return resolve(desc.substring(1, desc.length() - 1)); + } catch (MissingTypeException mte) { + if (silent) + return null; + else + throw mte; + } + } + + public Set resolveCompleteFindMissingTypes(Type type) { + return resolveComplete(type.getDescriptor()); + } + + public Set resolveCompleteFindMissingAnnotationTypes(Type type) { + Set missingAnnotationTypes = new LinkedHashSet<>(); + type.collectMissingAnnotationTypesHelper(missingAnnotationTypes, new HashSet<>()); + return missingAnnotationTypes; + } + + /** + * Verifies the type plus all its super types, interfaces and any type references in generic specifications exist. + * @return List of missing types, empty if all good! + */ + public Set resolveComplete(String desc) { + Set missingTypes = new LinkedHashSet<>(); + resolveComplete(desc.substring(1, desc.length()-1), missingTypes, new HashSet<>()); + return missingTypes; + } + + private void resolveComplete(String slashedDescriptor, Set missingTypes, Set visited) { + if (visited.add(slashedDescriptor)) { + Type baseType = resolve(slashedDescriptor, true); + if (baseType == null) { + missingTypes.add(slashedDescriptor); + } else { + // Check generics + List typesInSignature = baseType.getTypesInSignature(); + for (String t: typesInSignature) { + System.out.println("Found this "+t+" in signature of "+baseType.getName()); + } + String superclassString = baseType.getSuperclassString(); + if (superclassString != null) { + resolveComplete(superclassString, missingTypes, visited); + } + List interfaces = baseType.getInterfacesStrings(); + if (interfaces != null) { + for (String interfce: interfaces) { + resolveComplete(interfce, missingTypes, visited); + } + } + } + } + } + + public void index() { + for (String s : classpath) { + File f = new File(s); + if (f.isDirectory()) { + indexDir(f); + } else { + indexJar(f); + } + } + } + + public void indexDir(File dir) { + Path root = Paths.get(dir.toURI()); + try { + Files.walk(root).filter(f -> f.toString().endsWith(".class")).map(f -> { + String name = f.toString().substring(root.toString().length() + 1); + int lastSlash = name.lastIndexOf("/"); + if (lastSlash != -1 && name.endsWith(".class")) { + return name.substring(0, lastSlash); + } + return null; + }).forEach(n -> { + if (n != null) { + List dirs = appPackages.get(n); + if (dirs == null) { + dirs = new ArrayList<>(); + appPackages.put(n, dirs); + } + dirs.add(dir); + } + }); + } catch (IOException ioe) { + throw new IllegalStateException("Unable to walk " + dir, ioe); + } + } + + public void indexJar(File jar) { + // Walk the jar, index entries and cache package > this jar + try { + try (ZipFile zf = new ZipFile(jar)) { + Enumeration entries = zf.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.endsWith(".class")) { + int lastSlash = name.lastIndexOf("/"); + if (lastSlash != -1 && name.endsWith(".class")) { + packageCache.put(name.substring(0, lastSlash), jar); + } + } + } + } + } catch (IOException ioe) { + throw new RuntimeException("Problem during scan of " + jar, ioe); + } + } + + public byte[] find(String slashedTypeName) { + String search = slashedTypeName + ".class"; + try { + int index = slashedTypeName.lastIndexOf("/"); + String packageName = index==-1?"":slashedTypeName.substring(0, index); + + if (appPackages.containsKey(packageName)) { + List list = appPackages.get(packageName); + for (File f : list) { + File toTry = new File(f, search); + if (toTry.exists()) { + return loadFromStream(new FileInputStream(toTry)); + } + } + } else { + File jarfile = packageCache.get(packageName); + if (jarfile != null) { + try (ZipFile zf = new ZipFile(jarfile)) { + Enumeration entries = zf.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.equals(search)) { + return loadFromStream(zf.getInputStream(entry)); + } + } + } + } + } + + return null; + } catch (IOException ioe) { + throw new RuntimeException("Problem finding " + slashedTypeName, ioe); + } + } + + public static byte[] loadFromStream(InputStream stream) { + try { + BufferedInputStream bis = new BufferedInputStream(stream); + int size = 2048; + byte[] theData = new byte[size]; + int dataReadSoFar = 0; + byte[] buffer = new byte[size / 2]; + int read = 0; + while ((read = bis.read(buffer)) != -1) { + if ((read + dataReadSoFar) > theData.length) { + // need to make more room + byte[] newTheData = new byte[theData.length * 2]; + // System.out.println("doubled to " + newTheData.length); + System.arraycopy(theData, 0, newTheData, 0, dataReadSoFar); + theData = newTheData; + } + System.arraycopy(buffer, 0, theData, dataReadSoFar, read); + dataReadSoFar += read; + } + bis.close(); + // Resize to actual data read + byte[] returnData = new byte[dataReadSoFar]; + System.arraycopy(theData, 0, returnData, 0, dataReadSoFar); + return returnData; + } catch (IOException e) { + throw new RuntimeException("Unexpectedly unable to load bytedata from input stream", e); + } finally { + try { + stream.close(); + } catch (IOException ioe) { + } + } + } + + public String toString() { + return "TypeSystem for cp(" + classpath + ") jarPackages=#" + packageCache.size() + " appPackages=" + + appPackages; + } + + public void scan() { + // Scan the classpath for things of interest, do this only once! + for (String classpathEntry : classpath) { + File f = new File(classpathEntry); + if (f.isDirectory()) { + scanFiles(f, f); + } else { + scanArchive(f); + } + } + } + + private void scanArchive(File f) { + try (ZipFile zf = new ZipFile(f)) { + Enumeration entries = zf.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (entry.getName().endsWith(".class")) { + ClassReader reader = new ClassReader(zf.getInputStream(entry)); + ClassNode node = new ClassNode(); + reader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + AnnotationInfo ai = new AnnotationInfo(this, node); + if (ai.hasData()) { + System.out.println("From " + entry.toString() + " got " + ai.toAnnotationString()); + annotatedTypes.put(node.name, ai); + } + } + // TODO resources? + } + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } + } + + private void scanFiles(File file, File base) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + for (File f : files) { + scanFiles(f, base); + } + } else if (file.getName().endsWith(".class")) { + try { + byte[] bytes = Files.readAllBytes(Paths.get(file.toURI())); + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + reader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + AnnotationInfo ai = new AnnotationInfo(this, node); + if (ai.hasData()) { + System.out.println("From " + file.getName() + " got " + ai.toAnnotationString()); + annotatedTypes.put(node.name, ai); + } + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } + } +// else // resource? + } + + public static class AnnotationInfo { + + private String name; + private TypeSystem typeSystem; + private List annotations; + + // if this is the annotationinfo for an annotation, this will cache meta + // annotations + private List metaAnnotationsList = null; + + // need file? + + public AnnotationInfo(TypeSystem typeSystem, ClassNode node) { + this.typeSystem = typeSystem; + this.name = node.name; + annotations = node.visibleAnnotations; + } + + public boolean hasData() { + return annotations != null && annotations.size() != 0; + } + + public String toAnnotationString() { + StringBuilder sb = new StringBuilder(); + if (annotations != null) { + for (AnnotationNode an : annotations) { + sb.append(an.desc); + sb.append("("); + List values = an.values; + if (values != null) { + for (int j = 0; j < values.size(); j += 2) { + sb.append(values.get(j)); + sb.append("="); + sb.append(values.get(j + 1)); + } + } + sb.append(")"); + } + } + return sb.toString(); + } + + public boolean hasDescriptor(String annotationDescriptor) { + for (AnnotationNode an : annotations) { + if (an.desc.equals(annotationDescriptor)) { + return true; + } + } + return false; + } + + // TODO filter out java/lang/annotation annotations? Surely we don't need all of + // them + + List getMetaAnnotations() { + if (metaAnnotationsList == null) { + metaAnnotationsList = new ArrayList<>(); + collectMetaAnnotations(); + if (metaAnnotationsList.size() == 0) { + metaAnnotationsList = Collections.emptyList(); + } + } + return metaAnnotationsList; + } + + public boolean hasDescriptorMeta(String annotationDescriptor) { + System.out.println("Checking " + name + " for " + annotationDescriptor); + for (AnnotationNode an : annotations) { + if (an.desc.equals(annotationDescriptor)) { + return true; + } + } + for (AnnotationNode an : getMetaAnnotations()) { + if (an.desc.equals(annotationDescriptor)) { + return true; + } + } + return false; + } + + private void collectMetaAnnotations() { + for (AnnotationNode an : annotations) { + // Go through our annotations and grab their meta annotations + AnnotationInfo ai = typeSystem.annotatedTypes.get(an.desc.substring(1, an.desc.length() - 1)); + if (ai != null && ai.hasData()) { + metaAnnotationsList.addAll(ai.getAnnotations()); + metaAnnotationsList.addAll(ai.getMetaAnnotations()); + if (name.endsWith("DemoApplication")) { + System.out.println("111"); + for (AnnotationNode ann : metaAnnotationsList) { + System.out.println(ann.desc); + } + System.out.println("222"); + } + } + } + } + + private Collection getAnnotations() { + return annotations; + } + } + + private void ensureScanned() { + if (annotatedTypes == null) { + annotatedTypes = new HashMap<>(); + long t = System.currentTimeMillis(); + scan(); + System.out.println("SBG: scan time: " + (System.currentTimeMillis() - t) + "ms"); + } + } + + public List findTypesAnnotated(String annotationDescriptor, boolean metaAnnotated) { + ensureScanned(); + if (metaAnnotated) { + return annotatedTypes.values().stream().filter(ai -> ai.hasDescriptorMeta(annotationDescriptor)) + .map(ai -> ai.name).collect(Collectors.toList()); + } else { + return annotatedTypes.values().stream().filter(ai -> ai.hasDescriptor(annotationDescriptor)) + .map(ai -> ai.name).collect(Collectors.toList()); + } + } + + public List findTypesAnnotationAtConfiguration(boolean metaAnnotated) { + return findTypesAnnotated(SPRING_AT_CONFIGURATION,metaAnnotated); + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/internal/svm/MessageInterpolatorIsAround.java b/src/main/java/org/springframework/internal/svm/MessageInterpolatorIsAround.java new file mode 100644 index 000000000..d1290c9b8 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/MessageInterpolatorIsAround.java @@ -0,0 +1,18 @@ +package org.springframework.internal.svm; + +import java.util.function.BooleanSupplier; + +public class MessageInterpolatorIsAround implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + try { + Class.forName("javax.validation.MessageInterpolator"); + Class.forName("org.springframework.boot.validation.MessageInterpolatorFactory"); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + +} diff --git a/src/main/java/org/springframework/internal/svm/OnlyPresent.java b/src/main/java/org/springframework/internal/svm/OnlyPresent.java new file mode 100644 index 000000000..431325d22 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/OnlyPresent.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016-2017 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. + */ + +package org.springframework.internal.svm; + +import java.util.function.Predicate; + +class OnlyPresent implements Predicate { + + @Override + public boolean test(String type) { + try { + return Class.forName(type, false, getClass().getClassLoader()) != null; + } + catch (ClassNotFoundException ex) { + return false; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/internal/svm/Target_DefaultMethodInvokingMethodInterceptor.java b/src/main/java/org/springframework/internal/svm/Target_DefaultMethodInvokingMethodInterceptor.java new file mode 100644 index 000000000..40a96912c --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_DefaultMethodInvokingMethodInterceptor.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Contributors + * + * 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.internal.svm; + +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.ProxyMethodInvocation; + +/** + * @author Andy Clement + */ +@TargetClass(className="org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor", onlyWith = OnlyPresent.class) +public final class Target_DefaultMethodInvokingMethodInterceptor { + + @Substitute + public Object invoke(MethodInvocation invocation) throws Throwable { + Method method = invocation.getMethod(); + if (!method.isDefault()) { + return invocation.proceed(); + } + Object[] arguments = invocation.getArguments(); + Object proxy = ((ProxyMethodInvocation)invocation).getProxy(); + try { + return method.invoke(proxy,arguments); + } catch (UndeclaredThrowableException ute) { + System.out.println("UNDECLARED THROWABLE: "+ute.getUndeclaredThrowable()); + throw ute; + } + } + +} diff --git a/src/main/java/org/springframework/internal/svm/Target_org_hibernate_jpa_boot_internal_PersistenceUnitInfoDescriptor.java b/src/main/java/org/springframework/internal/svm/Target_org_hibernate_jpa_boot_internal_PersistenceUnitInfoDescriptor.java new file mode 100644 index 000000000..aaca794d3 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_hibernate_jpa_boot_internal_PersistenceUnitInfoDescriptor.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Contributors + * + * 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.internal.svm; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Workaround for + * Caused by: com.oracle.svm.core.jdk.UnsupportedFeatureError: Unsupported method java.lang.ClassLoader.registerAsParallelCapable() is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class + at com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:102) + at java.lang.ClassLoader.registerAsParallelCapable(Target_java_lang_ClassLoader.java:1204) + at org.springframework.core.DecoratingClassLoader.(DecoratingClassLoader.java:38) + at com.oracle.svm.core.hub.ClassInitializationInfo.invokeClassInitializer(ClassInitializationInfo.java:347) + at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:267) + at java.lang.Class.ensureInitialized(DynamicHub.java:437) + at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:232) + at java.lang.Class.ensureInitialized(DynamicHub.java:437) + at com.oracle.svm.core.hub.ClassInitializationInfo.initialize(ClassInitializationInfo.java:232) + at java.lang.Class.ensureInitialized(DynamicHub.java:437) + at org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo.getNewTempClassLoader(SpringPersistenceUnitInfo.java:93) + at org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor.getTempClassLoader(PersistenceUnitInfoDescriptor.java:78) + at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.populate(EntityManagerFactoryBuilderImpl.java:833) + at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.(EntityManagerFactoryBuilderImpl.java:219) + at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.(EntityManagerFactoryBuilderImpl.java:167) + at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:51) + at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) + at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390) + + * @author Andy Clement + */ +@TargetClass(className="org.hibernate.jpa.boot.internal.PersistenceUnitInfoDescriptor", onlyWith = OnlyPresent.class) +public final class Target_org_hibernate_jpa_boot_internal_PersistenceUnitInfoDescriptor { + + @Substitute + public ClassLoader getTempClassLoader() { + return null; + } +} diff --git a/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_SpringBootVersion.java b/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_SpringBootVersion.java new file mode 100644 index 000000000..fbde1ba68 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_SpringBootVersion.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019 Contributors + * + * 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.internal.svm; + +import org.springframework.beans.BeansException; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * + * @author Andy Clement + */ +@TargetClass(className="org.springframework.boot.SpringBootVersion", onlyWith = OnlyPresent.class) +public final class Target_org_springframework_boot_SpringBootVersion { + + @Substitute + public static String getVersion() throws BeansException { + return null; + // Without this, line 66 in SpringBootVersion 2.2.0 snapshots (>m2) fails with NPE: + + // URL codeSourceLocation = SpringBootVersion.class.getProtectionDomain() + // .getCodeSource().getLocation(); // this is line 66 + + //java.lang.NullPointerException + // at org.springframework.boot.SpringBootVersion.determineSpringBootVersion(SpringBootVersion.java:66) + // at org.springframework.boot.SpringBootVersion.getVersion(SpringBootVersion.java:56) + // at org.springframework.boot.SpringBootBanner.printBanner(SpringBootBanner.java:51) + // at org.springframework.boot.SpringApplicationBannerPrinter.print(SpringApplicationBannerPrinter.java:71) + // at org.springframework.boot.SpringApplication.printBanner(SpringApplication.java:582) + // at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) + // at com.example.func.BuncApplication.run(BuncApplication.java:55) + // at com.example.func.BuncApplication.main(BuncApplication.java:34) + } +} \ No newline at end of file diff --git a/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_validation_MessageInterpolatorFactory.java b/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_validation_MessageInterpolatorFactory.java new file mode 100644 index 000000000..9cd39b20c --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_springframework_boot_validation_MessageInterpolatorFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Contributors + * + * 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.internal.svm; + +import javax.validation.MessageInterpolator; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.util.ClassUtils; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Substitution for MessageInterpolatorFactory. The code pattern in there misbehaves under graal so let's just fallback straight away... needs review + * + * @author Andy Clement + */ +@TargetClass(className="org.springframework.boot.validation.MessageInterpolatorFactory",onlyWith=MessageInterpolatorIsAround.class) +public final class Target_org_springframework_boot_validation_MessageInterpolatorFactory { + + @Substitute + public MessageInterpolator getObject() throws BeansException { + Class interpolatorClass = ClassUtils.resolveClassName("org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator", null); + Object interpolator = BeanUtils.instantiateClass(interpolatorClass); + return (MessageInterpolator) interpolator; + } +} diff --git a/src/main/java/org/springframework/internal/svm/Target_org_springframework_jdbc_EmbeddedDatabaseConnection.java b/src/main/java/org/springframework/internal/svm/Target_org_springframework_jdbc_EmbeddedDatabaseConnection.java new file mode 100644 index 000000000..1698fad2a --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_springframework_jdbc_EmbeddedDatabaseConnection.java @@ -0,0 +1,37 @@ +///* +// * Copyright 2019 Contributors +// * +// * 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.internal.svm; +// +//import org.springframework.beans.BeansException; +//import org.springframework.boot.jdbc.EmbeddedDatabaseConnection; +// +//import com.oracle.svm.core.annotate.Substitute; +//import com.oracle.svm.core.annotate.TargetClass; +// +///** +// * Substitution for EmbeddedDatabaseConnection. +// * +// * @author Andy Clement +// */ +//@TargetClass(org.springframework.boot.jdbc.EmbeddedDatabaseConnection.class) +//public final class Target_org_springframework_jdbc_EmbeddedDatabaseConnection { +// +// // working around Graal #1196 +// @Substitute +// public EmbeddedDatabaseConnection[] values() throws BeansException { +// return new EmbeddedDatabaseConnection[0]; +// } +//} \ No newline at end of file diff --git a/src/main/java/org/springframework/internal/svm/Target_org_springframework_orm_jpa_persistenceunit_defaultpersistenceunitmanager.java b/src/main/java/org/springframework/internal/svm/Target_org_springframework_orm_jpa_persistenceunit_defaultpersistenceunitmanager.java new file mode 100644 index 000000000..66fd90e17 --- /dev/null +++ b/src/main/java/org/springframework/internal/svm/Target_org_springframework_orm_jpa_persistenceunit_defaultpersistenceunitmanager.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Contributors + * + * 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.internal.svm; + +import java.net.URL; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Workaround for + * + * Caused by: javax.persistence.PersistenceException: Unable to resolve persistence unit + * root URL at + * org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager.determineDefaultPersistenceUnitRootUrl(DefaultPersistenceUnitManager.java:640) + * at + * org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager.preparePersistenceUnitInfos(DefaultPersistenceUnitManager.java:462) + * at + * org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager.afterPropertiesSet(DefaultPersistenceUnitManager.java:443) + * at + * org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:328) + * at + * org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1841) + * at + * org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778) + * ... 16 more Caused by: java.io.FileNotFoundException: class path resource [] cannot be + * resolved to URL because it does not exist at + * org.springframework.core.io.ClassPathResource.getURL(ClassPathResource.java:195) at + * org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager.determineDefaultPersistenceUnitRootUrl(DefaultPersistenceUnitManager.java:636) + * @author Andy Clement + */ +@TargetClass(className = "org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager", onlyWith = OnlyPresent.class) +public final class Target_org_springframework_orm_jpa_persistenceunit_defaultpersistenceunitmanager { + + @Substitute + public URL determineDefaultPersistenceUnitRootUrl() { + return null; + } +} diff --git a/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java b/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java new file mode 100644 index 000000000..2c5bed6e3 --- /dev/null +++ b/src/main/java/org/springframework/jdbc/core/ConnectionCallback.java @@ -0,0 +1,70 @@ +/* + * Copyright 2002-2018 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. + */ + +package org.springframework.jdbc.core; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.springframework.dao.DataAccessException; +import org.springframework.lang.Nullable; + +/** + * Generic callback interface for code that operates on a JDBC Connection. + * Allows to execute any number of operations on a single Connection, + * using any type and number of Statements. + * + *

This is particularly useful for delegating to existing data access code + * that expects a Connection to work on and throws SQLException. For newly + * written code, it is strongly recommended to use JdbcTemplate's more specific + * operations, for example a {@code query} or {@code update} variant. + * + * @author Juergen Hoeller + * @since 1.1.3 + * @param the result type + * @see JdbcTemplate#execute(ConnectionCallback) + * @see JdbcTemplate#query + * @see JdbcTemplate#update + */ +@FunctionalInterface +public interface ConnectionCallback { + + /** + * Gets called by {@code JdbcTemplate.execute} with an active JDBC + * Connection. Does not need to care about activating or closing the + * Connection, or handling transactions. + *

If called without a thread-bound JDBC transaction (initiated by + * DataSourceTransactionManager), the code will simply get executed on the + * JDBC connection with its transactional semantics. If JdbcTemplate is + * configured to use a JTA-aware DataSource, the JDBC Connection and thus + * the callback code will be transactional if a JTA transaction is active. + *

Allows for returning a result object created within the callback, i.e. + * a domain object or a collection of domain objects. Note that there's special + * support for single step actions: see {@code JdbcTemplate.queryForObject} + * etc. A thrown RuntimeException is treated as application exception: + * it gets propagated to the caller of the template. + * @param con active JDBC Connection + * @return a result object, or {@code null} if none + * @throws SQLException if thrown by a JDBC method, to be auto-converted + * to a DataAccessException by a SQLExceptionTranslator + * @throws DataAccessException in case of custom exceptions + * @see JdbcTemplate#queryForObject(String, Class) + * @see JdbcTemplate#queryForRowSet(String) + */ + @Nullable + T doInConnection(Connection con) throws SQLException, DataAccessException; + +} diff --git a/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java b/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java new file mode 100644 index 000000000..3b0239d24 --- /dev/null +++ b/src/main/java/org/springframework/jdbc/datasource/embedded/EmbeddedDatabaseType.java @@ -0,0 +1,37 @@ +/* + * Copyright 2002-2018 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. + */ + +package org.springframework.jdbc.datasource.embedded; + +/** + * A supported embedded database type. + * + * @author Keith Donald + * @author Oliver Gierke + * @since 3.0 + */ +public enum EmbeddedDatabaseType { + + /** The Hypersonic Embedded Java SQL Database. */ + HSQL, + + /** The H2 Embedded Java SQL Database Engine. */ + H2, + + /** The Apache Derby Embedded SQL Database. */ + DERBY + +} diff --git a/src/main/resources/initialization.json b/src/main/resources/initialization.json new file mode 100644 index 000000000..72823ce4d --- /dev/null +++ b/src/main/resources/initialization.json @@ -0,0 +1,179 @@ +{ +"runtimeInitialization": +[ +{"package": "io.netty.handler.codec.http2"}, +{"class": "reactor.netty.tcp.TcpClientSecure"}, +{"class": "reactor.netty.http.client.HttpClientSecure"}, +{"class": "sun.reflect.misc.Trampoline"}, +{"class": "reactor.netty.http.server.HttpServer"}, +{"class": "ch.qos.logback.classic.spi.PackagingDataCalculator"} +], +"buildTimeInitialization": +[ + // vv vanilla-grpc + {"class": "io.netty.handler.codec.http2.CharSequenceMap"}, + {"class": "io.netty.handler.codec.http2.Http2Headers$PseudoHeaderName"}, + // ^^ vanilla-grpc + // These whilst working on the tx sample upgrading to graal 19.2 and boot 2.2.0.m5 + //{"package": "org.springframework.aop"}, + {"class": "org.springframework.aop.TargetSource"}, + {"class":"org.springframework.aop.framework.Advised"}, + {"class": "org.springframework.aop.Advisor"}, + {"class": "org.springframework.aop.Advisor$1"}, + {"class": "org.aopalliance.aop.Advice"}, + {"class": "org.springframework.boot.CommandLineRunner"}, + {"class": "app.main.Finder"}, + {"class": "org.springframework.core.DecoratingProxy"}, + {"class": "org.springframework.jdbc.datasource.ConnectionProxy"}, + // + {"class": "org.slf4j.helpers.NOPLogger"}, + {"class": "io.netty.handler.codec.http2.ReadOnlyHttp2Headers"}, +{"class": "org.springframework.boot.validation.MessageInterpolatorFactory"}, +{"class": "com.google.protobuf.Extension"}, +{"class": "com.google.protobuf.ExtensionLite"}, +{"class": "com.google.protobuf.ExtensionRegistry"}, +{"class": "org.springframework.transaction.annotation.Isolation"}, +{"class": "org.springframework.transaction.annotation.Propagation"}, +{"class": "org.springframework.http.HttpStatus"}, +{"class": "org.h2.Driver"}, +{"package": "reactor.netty.resources"}, +{"package": "reactor.netty"}, +{"package": "reactor.netty.channel"}, +{"package": "reactor.netty.http"}, +{"package": "reactor.netty.http.client"}, +{"package": "reactor.netty.http.server"}, +{"package": "reactor.netty.http.websocket"}, +{"package": "reactor.netty.resources"}, +{"package": "reactor.netty.tcp"}, +{"package": "reactor.netty.udp"}, +{"class": "reactor.netty.resources.ConnectionProvider"}, +{"package": "reactor.util"}, +{"class": "reactor.util.Loggers$Slf4JLogger"}, +{"package": "org.apache.logging.log4j"}, +{"class": "org.jboss.logging.LoggerProviders"}, +{"class": "org.jboss.logging.Log4j2LoggerProvider"}, +{"class": "org.hibernate.validator.internal.util.logging.Log_$logger"}, +{"class": "org.jboss.logging.Log4j2Logger"}, +{"package": "org.apache.logging.slf4j"}, +{"package": "org.jboss.logging"}, +{"class": "org.springframework.util.unit.DataSize"}, +{"class": "org.hibernate.validator.internal.engine.ConfigurationImpl"}, +{"class": "org.springframework.core.annotation.AnnotationFilter"}, +{"class": "org.springframework.core.annotation.AnnotationFilter$1"}, +{"class": "org.springframework.core.annotation.PackagesAnnotationFilter"}, +{"class": "org.springframework.util.StringUtils"}, +{"class": "org.springframework.util.Assert"}, +{"class": "reactor.netty.ConnectionObserver"}, +{"class": "org.slf4j.helpers.Util"}, +{"class": "org.slf4j.helpers.NOPLoggerFactory"}, +{"class": "org.slf4j.helpers.SubstituteLoggerFactory"}, +{"class": "org.slf4j.LoggerFactory"}, +{"class": "org.slf4j.impl.StaticLoggerBinder"}, +{"class": "ch.qos.logback.classic.util.ContextSelectorStaticBinder"}, +{"class": "ch.qos.logback.classic.LoggerContext"}, +{"class": "ch.qos.logback.classic.selector.DefaultContextSelector"}, +{"class": "ch.qos.logback.classic.Logger"}, +{"class": "ch.qos.logback.classic.selector.DefaultContextSelector"}, +{"class": "ch.qos.logback.classic.spi.LoggerContextVO"}, +{"class": "ch.qos.logback.classic.spi.TurboFilterList"}, +{"class": "ch.qos.logback.core.BasicStatusManager"}, +{"class": "ch.qos.logback.core.spi.LogbackLock"}, +{"class": "ch.qos.logback.core.status.InfoStatus"}, +{"class": "ch.qos.logback.core.util.COWArrayList"}, +{"class": "org.hibernate.validator.internal.metadata.provider.ProgrammaticMetaDataProvider"}, +{"package": "org.hibernate.validator.internal.metadata.provider"}, +{"class": "org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder"}, +{"package": "org.hibernate.validator.internal.metadata.aggregated"}, +{"class": "org.hibernate.validator.internal.metadata.raw.ConstrainedElement$ConstrainedElementKind"}, +{"package": "org.hibernate.validator.internal.metadata.raw"}, +{"class": "org.hibernate.validator.internal.engine.ValidatorImpl"}, +{"package": "org.hibernate.validator.internal.engine"}, +{"class": "ch.qos.logback.classic.layout.TTLLLayout"}, +{"class": "ch.qos.logback.core.util.CachingDateFormatter"}, +{"class": "ch.qos.logback.classic.pattern.ThrowableProxyConverter"}, +{"class": "org.slf4j.helpers.FormattingTuple"}, +{"class": "ch.qos.logback.classic.util.LoggerNameUtil"}, +{"class": "ch.qos.logback.core.spi.FilterAttachableImpl"}, +{"class": "org.slf4j.helpers.MessageFormatter"}, +{"package": "ch.qos.logback.classic.spi"}, +{"class": "org.hibernate.validator.internal.util.Contracts"}, +{"class": "org.hibernate.validator.internal.engine.resolver.TraversableResolver"}, +{"class": "org.hibernate.validator.internal.util.privilegedactions.LoadClass"}, +{"class": "org.hibernate.validator.internal.engine.resolver.TraversableResolvers"}, +{"class": "org.hibernate.validator.internal.util.logging.Log"}, +{"class": "org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator"}, +{"class": "org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator"}, +{"class": "org.hibernate.validator.internal.util.CollectionHelper"}, +{"class": "org.hibernate.validator.internal.xml.config.ResourceLoaderHelper"}, +{"class": "org.hibernate.validator.internal.engine.ValidatorFactoryImpl"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager"}, +{"class": "javax.validation.Validation"}, +{"class": "javax.validation.ConstraintValidator"}, +{"package": "javax.validation"}, +{"class": "org.hibernate.validator.constraints.CompositionType"}, +{"package": "org.hibernate.validator.constraints"}, +{"class": "org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator"}, +{"package": "org.hibernate.validator.internal.engine.groups"}, +{"class": "javax.validation.ValidationException"}, +{"package": "org.hibernate.validator.internal.engine.valueextraction"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.IntArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.LongArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.FloatArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ShortArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.DoubleArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.CharArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ListValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.BooleanArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ObjectArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.MapValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.MapKeyExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.IterableValueExtractor"}, +{"class": "org.hibernate.validator.internal.util.TypeHelper"}, +{"class": "org.hibernate.validator.internal.util.ReflectionHelper"}, +{"class": "org.hibernate.validator.internal.util.ExecutableHelper"}, +{"package": "org.hibernate.validator.internal.util"}, +{"class": "org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping"}, +{"package": "org.hibernate.validator.internal.cfg.context"}, +{"class": "org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl"}, +{"package": "org.hibernate.validator.internal.metadata.core"}, +{"class": "org.hibernate.validator.internal.metadata.core.ConstraintHelper"}, +{"package": "org.hibernate.validator.internal.metadata.aggregated.rule"}, +{"package": "org.hibernate.validator.internal.engine.constraintvalidation"}, +{"class": "org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager"}, +{"class": "org.hibernate.validator.internal.metadata.aggregated.rule.OverridingMethodMustNotAlterParameterConstraints"}, +{"class": "org.hibernate.validator.internal.engine.constraintvalidation.ClassBasedValidatorDescriptor"}, +{"class": "org.hibernate.validator.internal.engine.scripting.DefaultScriptEvaluatorFactory"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.ByteArrayValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.OptionalValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.OptionalIntValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.OptionalDoubleValueExtractor"}, +{"class": "org.hibernate.validator.internal.engine.valueextraction.OptionalLongValueExtractor"}, +//{"class": "org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator"}, +{"class": "org.hibernate.validator.internal.util.logging.Messages"}, +{"class": "org.hibernate.validator.internal.util.logging.Messages_$bundle"}, +{"class": "org.hibernate.validator.internal.xml.config.ValidationXmlParser"}, +{"class": "org.hibernate.validator.resourceloading.PlatformResourceBundleLocator$AggregateResourceBundle"}, +{"class": "org.hibernate.validator.resourceloading.PlatformResourceBundleLocator$AggregateResourceBundleControl"}, +{"class": "org.hibernate.validator.internal.util.privilegedactions.GetMethod"}, +{"class": "org.hibernate.validator.internal.xml.config.ValidationBootstrapParameters"}, +{"class": "org.hibernate.validator.resourceloading.PlatformResourceBundleLocator"}, +{"package": "org.hibernate.validator.internal.util.logging.LoggerFactory"}, +{"package": "ch.qos.logback.core"}, +{"package": "ch.qos.logback.classic"}, +{"package": "ch.qos.logback.classic.util"}, +{"class": "ch.qos.logback.classic.util.LogbackMDCAdapter"}, +{"class": "ch.qos.logback.classic.spi.LoggingEvent"}, +{"class": "ch.qos.logback.core.helpers.CyclicBuffer"}, +{"class": "ch.qos.logback.core.encoder.LayoutWrappingEncoder"}, +{"class": "ch.qos.logback.core.joran.spi.ConsoleTarget$1"}, +{"class": "ch.qos.logback.core.ConsoleAppender"}, +{"class": "ch.qos.logback.core.spi.AppenderAttachableImpl"}, +{"class": "ch.qos.logback.classic.BasicConfigurator"}, +{"class": "org.slf4j.MDC"}, +{"class": "com.rabbitmq.client.SocketChannelConfigurator"} +// See https://medium.com/graalvm/instant-netty-startup-using-graalvm-native-image-generation-ed6f14ff7692 +] +} + + diff --git a/src/main/resources/proxies.json b/src/main/resources/proxies.json new file mode 100644 index 000000000..8a9f71042 --- /dev/null +++ b/src/main/resources/proxies.json @@ -0,0 +1,26 @@ +[ +["org.springframework.boot.context.properties.ConfigurationProperties","org.springframework.core.annotation.SynthesizedAnnotation"], +["org.springframework.stereotype.Component"], +["org.springframework.beans.factory.annotation.Qualifier"], +["org.springframework.boot.context.properties.ConfigurationProperties"], +["org.springframework.context.annotation.Lazy"], +["org.springframework.web.bind.annotation.RequestMapping"], +["org.springframework.web.bind.annotation.ResponseStatus"], +["org.springframework.web.bind.annotation.RequestBody"], +["org.springframework.web.bind.annotation.ResponseBody"], +["javax.validation.Validator","org.springframework.aop.SpringProxy","org.springframework.aop.framework.Advised","org.springframework.core.DecoratingProxy"], +["org.hibernate.jpa.HibernateEntityManagerFactory","org.springframework.orm.jpa.EntityManagerFactoryInfo"], +["org.hibernate.jpa.HibernateEntityManagerFactory","org.springframework.orm.jpa.EntityManagerFactoryInfo","javax.persistence.EntityManagerFactory"], +["javax.persistence.Id"], +["javax.persistence.GeneratedValue"], +["javax.persistence.Transient"], +["org.springframework.data.jpa.repository.support.CrudMethodMetadata", "org.springframework.aop.SpringProxy", "org.springframework.aop.framework.Advised", "org.springframework.core.DecoratingProxy"], +["org.hibernate.jpa.HibernateEntityManager","org.springframework.orm.jpa.EntityManagerProxy"], +["org.springframework.web.bind.annotation.RequestParam"], +["org.springframework.boot.CommandLineRunner","app.main.Finder","org.springframework.aop.SpringProxy", + "org.springframework.aop.framework.Advised","org.springframework.core.DecoratingProxy"], +["app.main.model.FooRepository","org.springframework.aop.SpringProxy","org.springframework.aop.framework.Advised", + "org.springframework.core.DecoratingProxy"], + ["org.springframework.amqp.rabbit.annotation.RabbitListener"], + ["org.springframework.amqp.rabbit.connection.ChannelProxy"] +] diff --git a/src/main/resources/reflect.json b/src/main/resources/reflect.json new file mode 100644 index 000000000..c80e85bce --- /dev/null +++ b/src/main/resources/reflect.json @@ -0,0 +1,1689 @@ +[ + // vv vanilla-grpc + { + "name": "com.google.protobuf.ExtensionRegistry", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // ^^ vanilla-grpc + // These two are from CodecConfigurer.properties + { + "name": "org.springframework.http.codec.support.DefaultClientCodecConfigurer", + "methods": [{"name": "","parameterTypes": []}] + }, + { + "name": "org.springframework.http.codec.support.DefaultServerCodecConfigurer", + "methods": [{"name": "","parameterTypes": []}] + }, + { + "name": "org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.annotations.common.Version", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.AutoFlushEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.OnResourceCondition", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.ClearEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.DeleteEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.DirtyCheckEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.EvictEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.hibernate.event.spi.FlushEntityEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.FlushEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.InitializeCollectionEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.LoadEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.LockEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.MergeEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PersistEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostCollectionRecreateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostCollectionRemoveEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostCollectionUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostDeleteEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostInsertEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostLoadEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PostUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreCollectionRecreateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreCollectionRemoveEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreCollectionUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreDeleteEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreInsertEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreLoadEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.PreUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.RefreshEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.ReplicateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.ResolveNaturalIdEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.event.spi.SaveOrUpdateEventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.hibernate.event.spi.EventType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.dialect.H2Dialect", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.jboss.logging.BasicLogger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.sun.xml.internal.stream.events.XMLEventFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.annotations.common.util.impl.Log_$logger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.annotations.common.util.impl.Log", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.sun.xml.internal.stream.events.XMLEventFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "com.google.gson.GsonBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.Advised", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // these 3 from AopConfigUtils + { + "name": "org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.aspectj.AspectJAwareAdvisorAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // supertypes of InfrastructureAdvistorAutoProxyCreator - to combat error: + // Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'order' of + // bean class [org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator]: Bean property + // 'order' is not writable or has an invalid setter method. Does the parameter type + // of the setter match the return type of the getter? + { + "name": "org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.ProxyProcessorSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.aop.framework.ProxyConfig", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // Should these two be pulled in by annotations on entityManagerFactory method in JpaBaseConfiguration + { + "name": "javax.persistence.EntityManagerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.jpa.HibernateEntityManagerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.hibernate.validator.HibernateValidatorConfiguration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Configuration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.SpringBootVersion", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.validation.spi.ConfigurationState", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.validation.Configuration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.http.client.reactive.ReactorResourceFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.function.client.WebClient$Builder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.client.RestTemplateBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext", + "methods": [ + { + "name": "", + "parameterTypes": [ + ] + } + ] + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesBinder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.SearchStrategy", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.support.PropertySourcesPlaceholderConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ScopedProxyMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.jmx.export.naming.MetadataNamingStrategy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.CommonAnnotationBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.util.logging.LogManager", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredClasses": true + }, + { + "name": "org.springframework.boot.logging.java.JavaLoggingSystem", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredClasses": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication$Type", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication$Type", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigureOrder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // these 5 are from BaseDefaultCodecs + { + "name": "com.fasterxml.jackson.databind.ObjectMapper", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.fasterxml.jackson.core.JsonGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.fasterxml.jackson.dataformat.smile.SmileFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Message", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.xml.bind.Binder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // this one after adding those 5 + { + "name": "com.sun.xml.internal.stream.XMLInputFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.AnnotationConfigApplicationContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.ConditionalOnResource", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.GetMapping", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.RequestMapping", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.PostMapping", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.ResponseBody", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.RequestBody", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.stereotype.Controller", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.RestController", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Role", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.validation.Validator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.validation.Validator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ImportSelector", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationImportSelector$AutoConfigurationGroup", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.sun.jmx.mbeanserver.JmxMBeanServer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.event.DefaultEventListenerFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Lazy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.ClassLoader", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationProperties", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.TypeExcludeFilter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigureAfter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.jmx.support.MBeanRegistrationSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.BeanFactoryAware", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.cache.CacheCondition", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.CommandLineRunner", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.SpringBootConfiguration", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.SpringBootApplication", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.sun.jmx.mbeanserver.SunJmxMBeanServer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.xml.XmlBeanDefinitionReader", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.config.DelegatingWebFluxConfiguration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.config.EnableWebFlux", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.EnableAutoConfiguration", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + },{ + "name": "org.springframework.beans.factory.config.PropertiesFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + },{ + "name": "org.springframework.core.io.support.PropertiesLoaderSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackage", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.event.EventListenerMethodProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ComponentScan", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.reflect.ParameterizedType", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessorRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.task.TaskExecutorBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.EnableConfigurationProperties", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ImportBeanDefinitionRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.SpringApplication", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ComponentScan$Filter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration$ConfigAvailableCondition", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // vanilla-thymeleaf related, starts here... + { + "name": "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.http.converter.FormHttpMessageConverter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, // NPE: org.springframework.boot.autoconfigure.http.HttpMessageConverters.extractPartConverters(HttpMessageConverters.java:162) + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.bind.annotation.RequestParam", + "allDeclaredConstructors": true, + "allDeclaredFields": true, // Method [public abstract java.lang.String org.springframework.web.bind.annotation.RequestParam.name()] is unsupported for synthesized annotation type [interface org.springframework.web.bind.annotation.RequestParam] + "allDeclaredMethods": true + }, + { + "name": "org.thymeleaf.spring5.view.reactive.ThymeleafReactiveView", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.thymeleaf.spring5.expression.Mvc$NonSpring41MvcUriComponentsBuilderDelegate", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.thymeleaf.standard.expression.AdditionExpression", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // ends here... + // vanilla-tx vvv + { + "name": "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.jdbc.support.SQLErrorCodes", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.sql.DatabaseMetaData", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.transaction.TransactionDefinition", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.transaction.annotation.Propagation", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.transaction.annotation.Transactional", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.AdviceMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.AutoProxyRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // vanilla-tx ^^^ + { + "name": "org.springframework.boot.autoconfigure.jmx.ParentAwareNamingStrategy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration$JCacheAvailableCondition", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.task.TaskSchedulerBuilder", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.validation.beanvalidation.MethodValidationPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.HandlerResult", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Configuration", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.annotation.Autowired", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.AnnotationScopeMetadataResolver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$BasePackages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.http.HttpMessageConverters", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.socket.nio.NioServerSocketChannel", + "methods": [ + { + "name": "", + "parameterTypes": [ + ] + } + ] + }, + { + "name": "io.netty.channel.socket.nio.NioSocketChannel", + "methods": [ + { + "name": "", + "parameterTypes": [ + ] + } + ] + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.http.codec.ClientCodecConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.http.codec.ServerCodecConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.web.reactive.result.method.AbstractHandlerMethodMapping$PreFlightAmbiguousMatchHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.util.logging.Messages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.messageinterpolation.ParameterMessageInterpolator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.util.logging.Log", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.condition.BeanTypeRegistry", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.h2.Driver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.entity.SingleTableEntityPersister", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.cache.spi.access.CollectionDataAccess", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.mapping.PersistentClass", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.spi.PersisterCreationContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.cache.spi.access.NaturalIdDataAccess", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.cache.spi.access.EntityDataAccess", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.entity.AbstractEntityPersister", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.internal.PersisterClassResolverInitiator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.persister.spi.PersisterClassResolver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.transaction.support.TransactionTemplate", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // These from DefaultIdentifierGeneratorFactory + { + "name": "org.hibernate.id.UUIDGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.GUIDGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.UUIDHexGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.Assigned", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.IdentityGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.SelectGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.enhanced.SequenceStyleGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.SequenceHiLoGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.IncrementGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.ForeignGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.SequenceIdentityGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.id.enhanced.TableGenerator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "app.main.model.Foo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher$Registrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.jdbc.DataSourceProperties", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.config.EnableJpaRepositories", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.jpa.HibernateEntityManager", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // vvv vanilla-jpa + { + "name": "java.lang.Throwable", + "allDeclaredFields": true, + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.SessionImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.query.QueryLookupStrategy$Key", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.support.SimpleJpaRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { // this one shouldn't be needed (it's a superclass of the one below, which is what the bean definition actually is made of) + "name": "org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { // this one shouldn't be needed (it's a superclass of the one below, which is what the bean definition actually is made of) + "name": "org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.core.support.PropertiesBasedNamedQueries", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesScanRegistrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationPropertiesScan", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackages$Registrar", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackage", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurationPackages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.persistence.Entity", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.persistence.GeneratedValue", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "javax.persistence.Id", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurePackages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.AutoConfigurePackages$BasePackages", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.query.QueryByExampleExecutor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.PagingAndSortingRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.CrudRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.repository.Repository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.JpaRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "app.main.model.FooRepository", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.support.EntityManagerBeanDefinitionRegistrarPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.orm.jpa.SharedEntityManagerCreator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.util.JpaMetamodelCacheCleanup", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.data.jpa.repository.support.JpaEvaluationContextExtension", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // ^^^ vanilla-jpa + { + "name": "org.springframework.orm.jpa.JpaVendorAdapter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.engine.resolver.JPATraversableResolver", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.engine.resolver.TraversableResolvers", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.resourceloading.PlatformResourceBundleLocator", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.xml.config.ValidationBootstrapParameters", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.validator.internal.engine.ConfigurationImpl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.annotation.Repeatable", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer$SharedMetadataReaderFactoryBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.management.ManagementFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.management.RuntimeMXBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.DefaultChannelPipeline$HeadContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.DefaultChannelPipeline$TailContext", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // vvv vanilla-grpc + { "name": "io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelInboundHandlerAdapter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelHandlerAdapter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelInboundHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.bootstrap.ServerBootstrap$1", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { "name": "io.netty.channel.ChannelInitializer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // ^^^ vanilla-grpc + + // Because of EntityTuplizerFactory + { + "name": "org.hibernate.tuple.Tuplizer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.tuple.entity.EntityTuplizer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.tuple.entity.AbstractEntityTuplizer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.tuple.entity.PojoEntityTuplizer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // { + // "name": "org.hibernate.tuple.entity.DynamicMapEntityTuplizer", + // "allDeclaredConstructors": true, + // "allDeclaredMethods": true + // }, + // { + // "name": "org.hibernate.tuple.entity.EntityMetamodel", + // "allDeclaredConstructors": true, + // "allDeclaredMethods": true + // }, + // { + // "name": "org.hibernate.mapping.PersistentClass", + // "allDeclaredConstructors": true, + // "allDeclaredMethods": true + // }, + + + { + "name": "javax.management.MBeanServer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.core.annotation.Order", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.support.GenericApplicationContext", + "methods": [ + { + "name": "", + "parameterTypes": [ + ] + } + ] + }, + { + "name": "org.springframework.context.event.GenericApplicationListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.jmx.export.annotation.AnnotationMBeanExporter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.config.BeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.annotation.ElementType", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.stereotype.Component", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.beans.factory.annotation.Qualifier", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ConfigurationClassPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.EnvironmentAware", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // jboss logging referenced in hibernate is accessing log4j: + { + "name": "org.apache.logging.log4j.message.ReusableMessageFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.apache.logging.log4j.message.DefaultFlowMessageFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.h2.mvstore.db.MVTableEngine", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.sql.Statement", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.sql.Statement[]", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "com.zaxxer.hikari.HikariDataSource", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.zaxxer.hikari.HikariConfig", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.EntityManagerMessageLogger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.EntityManagerMessageLogger_$logger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.CoreMessageLogger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.internal.CoreMessageLogger_$logger", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + // These 2 from HibernatJpaConfiguration: + { + "name": "org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.hibernate.service.jta.platform.internal.NoJtaPlatform", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + + { + "name": "org.hibernate.Session", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "name": "java.util.EventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.Class", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.util.EventListener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.Class", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.Object", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.Bean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.handler.codec.http.HttpServerCodec", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.CombinedChannelDuplexHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.netty.channel.ChannelDuplexHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "reactor.netty.channel.ChannelOperationsHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "reactor.netty.http.server.HttpTrafficHandler", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "java.lang.annotation.RetentionPolicy", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.yaml.snakeyaml.Yaml", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // Simple rabbit app + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties$Cache", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties$Listener", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties$Ssl", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.RabbitProperties$Template", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.core.Queue", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name":"org.springframework.amqp.rabbit.annotation.RabbitListener", + "allDeclaredConstructors":true, + "allDeclaredMethods":true + }, + { + "name":"org.springframework.amqp.rabbit.connection.ChannelProxy", + "allDeclaredConstructors":true, + "allDeclaredMethods":true + }, + { + "name":"com.rabbitmq.client.Channel", + "allDeclaredConstructors":true, + "allDeclaredMethods":true + }, + { + "name":"com.rabbitmq.client.ShutdownNotifier", + "allDeclaredConstructors":true, + "allDeclaredMethods":true + }, + { + "name": "org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.context.annotation.ConfigurationClassParser$DefaultDeferredImportSelectorGroup", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.boot.autoconfigure.amqp.DirectRabbitListenerContainerFactoryConfigurer", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.rabbit.connection.CachingConnectionFactory", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.core.AnonymousQueue", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.amqp.core.AmqpAdmin", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.reactivestreams.Publisher", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + // plus spring integration + { + "name": "org.springframework.integration.config.IntegrationConfigurationBeanFactoryPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "org.springframework.integration.config.DefaultConfiguringBeanFactoryPostProcessor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] diff --git a/src/main/resources/resources.json b/src/main/resources/resources.json new file mode 100644 index 000000000..fd33f88c4 --- /dev/null +++ b/src/main/resources/resources.json @@ -0,0 +1,73 @@ +{ + "resources": [ + {"pattern": "META-INF/spring.components"}, + {"pattern": "META-INF/spring.factories"}, + {"pattern": "org/springframework/boot/logging/java/logging.properties"}, + {"pattern": "META-INF/services/javax.validation.spi.ValidationProvider"}, // make pattern? + {"pattern": "hibernate.properties"}, + {"pattern": "org/hibernate/.*.xsd"}, + {"pattern": "org/hibernate/.*.dtd"}, + {"pattern": "application.yml"}, + {"pattern": "application.properties"}, + {"pattern": "logging.properties"}, + {"pattern": "org/springframework/http/codec/CodecConfigurer.properties"}, + + {"pattern": "org/reactivestreams/Publisher.class"}, + {"pattern": "org/springframework/amqp/rabbit/connection/ChannelProxy.class"}, + + // vvv vanilla-jpa + {"pattern": "org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.class"}, + {"pattern": "META-INF/jpa-named-queries.properties"}, + // ^^^ vanilla-jpa + + // vvv vanilla-tx + {"pattern": "org/springframework/boot/CommandLineRunner.class"}, + {"pattern": "app/main/EnableTx.class"}, + {"pattern": "org/springframework/context/annotation/AutoProxyRegistrar.class"}, + {"pattern": "app/main/Finder.class"}, + {"pattern": "org/springframework/jdbc/support/sql-error-codes.xml"}, + {"pattern": "schema.sql"}, + // ^^^ vanilla-tx + + // vvv vanilla-thymeleaf + {"pattern": "static/index.html"}, + {"pattern": "templates/greeting.html"}, + // ^^^ vanilla-thymeleaf + + // vvv vanilla-rabbit + {"pattern": "rabbitmq-amqp-client.properties"}, + // ^^^ vanilla-rabbit + {"pattern": "META-INF/spring.integration.default.properties"}, + + // Does this catch the logging//*.properties? + {"pattern": "org/springframework/boot/logging/.*.properties"}, + {"pattern": "org/springframework/boot/logging/.*.xml"}, + + {"pattern": "org/hibernate/validator/internal/engine/ConfigurationImpl.class"}, + {"pattern": "org/hibernate/validator/internal/util/Contracts.class"}, + {"pattern": "org/hibernate/validator/internal/util/logging/Log.class"}, + {"pattern": "org/hibernate/validator/internal/engine/resolver/TraversableResolvers.class"}, + + {"pattern": "org/springframework/boot/context/properties/ConfigurationPropertiesScanRegistrar.class"}, + {"pattern": "org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.Hikari.class"}, + {"pattern": "org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.Registrar.class"}, + {"pattern": "org/springframework/boot/autoconfigure/orm/jpa/DataSourceInitializedPublisher.class"}, + {"pattern": "org/springframework/boot/autoconfigure/jdbc/DataSourceProperties.class"}, + {"pattern": "org/springframework/boot/autoconfigure/jdbc/DataSourceInitializerPostProcessor.class"}, + + {"pattern": "org/springframework/context/ApplicationListener.class"}, + {"pattern": "org/springframework/beans/factory/InitializingBean.class"}, + {"pattern": "org/springframework/context/annotation/Role.class"}, + {"pattern": "javax/validation/Validator.class"}, + {"pattern": "org/springframework/context/annotation/Import.class"}, + {"pattern": "org/springframework/context/annotation/ImportAware.class"}, + {"pattern": "org/springframework/context/EnvironmentAware.class"}, + {"pattern": "org/springframework/context/annotation/Configuration.class"}, + {"pattern": "org/springframework/beans/factory/BeanFactoryAware.class"}, + {"pattern": "org/springframework/beans/factory/Aware.class"}, + {"pattern": "org/springframework/beans/factory/BeanClassLoaderAware.class"}, + {"pattern": "org/springframework/context/ApplicationContextAware.class"}, + {"pattern": "org/springframework/context/annotation/ImportBeanDefinitionRegistrar.class"} + ] +} + diff --git a/src/test/java/org/springframework/support/graal/TypeSystemTest.java b/src/test/java/org/springframework/support/graal/TypeSystemTest.java new file mode 100644 index 000000000..7430c718c --- /dev/null +++ b/src/test/java/org/springframework/support/graal/TypeSystemTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 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. + */ +package org.springframework.support.graal; + +import java.io.File; + +import org.junit.Test; + +public class TypeSystemTest { + + @Test + public void test() throws Exception { + File file = new File("./target/classes"); + System.out.println(file.getCanonicalPath()); + } + +}