diff --git a/.gitignore b/.gitignore index 49e68a6..816f85a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ node_modules site .DS_Store .env +.idea **/*.pdf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d262945 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM node:18-alpine +WORKDIR /app + +COPY ["package.json", "./"] +COPY ["gulpfile.js", "./"] + +RUN npm install gulp -g +RUN npm install + +COPY ["app", "./app"] + +RUN gulp build + +CMD ["gulp", "serve"] diff --git a/README.md b/README.md index 9a49788..fc01462 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Dev.java +# Contributing to Dev.java -[Dev.java](https://dev.java) is the official website for the Java platform and language maintained by the Java Platform Group at Oracle. +[Dev.java](https://dev.java) is the official website for the Java platform and language maintained by the Java Platform Group at Oracle. We accept contributions from community members through this repository. [Click here](https://dev.java/authors) for examples of contributed content. -We [recently announced](https://inside.java) that we are accepting contributions to Dev.java. This repository contains most of the content for the website, a lightweight JavaScript toolchain to build the site into static HTML, and guidelines for contributing to it. +This repository contains the contribution guidelines as well as a lightweight JavaScript toolchain to build the site into static HTML. Here are the sections of this document: @@ -11,6 +11,7 @@ Here are the sections of this document: * [Content Lifecycle](#content-lifecycle) * [Content Proposal](#content-proposal) * [GitHub Workflow](#github-workflow) + * [Content License](#content-license) * [Building the Site](#building-the-site) * [Working with Content](#working-with-content) @@ -61,7 +62,7 @@ All content must start with a Content Proposal. This will be in the form of a Gi ### GitHub Workflow -Once your proposal has been moved to the `accepted` stage, you can begin working on your content. Here are the steps to do this: +Once your proposal has been moved to the `approved` stage, you can begin working on your content. Here are the steps to do this: 1. Fork this repo 1. Create a branch off of `main` for each piece of content @@ -70,19 +71,34 @@ Once your proposal has been moved to the `accepted` stage, you can begin working 1. Submit a pull request back to this repo +### Content License + +Contributors must sign the [Oracle Contributor Agreement](https://oca.opensource.oracle.com/) which will be verified once there is a pull request created. Contributed content is made available under the [UPL license](https://oss.oracle.com/licenses/upl/). You can find examples [here](https://dev.java/authors). + ## Building the Site -There is some basic JavaScript infrastructure to help build the static site. This should be as simple as: +There is some basic JavaScript infrastructure to help build the static site. There are two options: + +### Option 1: Build locally 1. install node and npm. easiest way is [nvm](https://github.com/nvm-sh/nvm) and `nvm use` in this directory. 1. `npm install` 1. `npm install gulp -g` 1. `gulp` -A browser should launch viewing https://localhost:3000. +A browser should launch viewing [https://localhost:3000](https://localhost:3000) + +### Option 2: Use Docker + +1. `docker build --tag devjava .` +1. `docker run --publish 3000:3000 --init -it --rm devjava` + +You should then be able to open a browser and visit [https://localhost:3000](https://localhost:3000) + +(For a more dynamic development experience avoiding a Docker build after every change, you can mount the local /app folder to the container by adding option `-v $PWD/app:/app/app` to your `docker run` command. Note: $PWD may not work in Windows.) ## Working with Content -See [working with content guide](/docs/working-with-content.md) \ No newline at end of file +See [working with content guide](/docs/working-with-content.md) diff --git a/app/assets/images/eclipse/breakpoint.png b/app/assets/images/eclipse/breakpoint.png new file mode 100644 index 0000000..fecc252 Binary files /dev/null and b/app/assets/images/eclipse/breakpoint.png differ diff --git a/app/assets/images/eclipse/compilation_error.png b/app/assets/images/eclipse/compilation_error.png new file mode 100644 index 0000000..e6ebe05 Binary files /dev/null and b/app/assets/images/eclipse/compilation_error.png differ diff --git a/app/assets/images/eclipse/console_output.png b/app/assets/images/eclipse/console_output.png new file mode 100644 index 0000000..8b8677c Binary files /dev/null and b/app/assets/images/eclipse/console_output.png differ diff --git a/app/assets/images/eclipse/content_assist_main.png b/app/assets/images/eclipse/content_assist_main.png new file mode 100644 index 0000000..d7658b9 Binary files /dev/null and b/app/assets/images/eclipse/content_assist_main.png differ diff --git a/app/assets/images/eclipse/content_assist_suggest_class.png b/app/assets/images/eclipse/content_assist_suggest_class.png new file mode 100644 index 0000000..93f3768 Binary files /dev/null and b/app/assets/images/eclipse/content_assist_suggest_class.png differ diff --git a/app/assets/images/eclipse/content_assist_suggest_method.png b/app/assets/images/eclipse/content_assist_suggest_method.png new file mode 100644 index 0000000..0e115ff Binary files /dev/null and b/app/assets/images/eclipse/content_assist_suggest_method.png differ diff --git a/app/assets/images/eclipse/content_assist_sysout.png b/app/assets/images/eclipse/content_assist_sysout.png new file mode 100644 index 0000000..ee7706f Binary files /dev/null and b/app/assets/images/eclipse/content_assist_sysout.png differ diff --git a/app/assets/images/eclipse/context_generate_getters_setters.png b/app/assets/images/eclipse/context_generate_getters_setters.png new file mode 100644 index 0000000..b921406 Binary files /dev/null and b/app/assets/images/eclipse/context_generate_getters_setters.png differ diff --git a/app/assets/images/eclipse/context_generate_hashcode_equals.png b/app/assets/images/eclipse/context_generate_hashcode_equals.png new file mode 100644 index 0000000..a2dd637 Binary files /dev/null and b/app/assets/images/eclipse/context_generate_hashcode_equals.png differ diff --git a/app/assets/images/eclipse/context_rename.png b/app/assets/images/eclipse/context_rename.png new file mode 100644 index 0000000..adce4b6 Binary files /dev/null and b/app/assets/images/eclipse/context_rename.png differ diff --git a/app/assets/images/eclipse/context_tostring.png b/app/assets/images/eclipse/context_tostring.png new file mode 100644 index 0000000..6373314 Binary files /dev/null and b/app/assets/images/eclipse/context_tostring.png differ diff --git a/app/assets/images/eclipse/create_class.png b/app/assets/images/eclipse/create_class.png new file mode 100644 index 0000000..7f25f1c Binary files /dev/null and b/app/assets/images/eclipse/create_class.png differ diff --git a/app/assets/images/eclipse/create_java_project.gif b/app/assets/images/eclipse/create_java_project.gif new file mode 100644 index 0000000..c69edd0 Binary files /dev/null and b/app/assets/images/eclipse/create_java_project.gif differ diff --git a/app/assets/images/eclipse/debug_button.png b/app/assets/images/eclipse/debug_button.png new file mode 100644 index 0000000..0e16e2e Binary files /dev/null and b/app/assets/images/eclipse/debug_button.png differ diff --git a/app/assets/images/eclipse/debug_button_in_toolbar.png b/app/assets/images/eclipse/debug_button_in_toolbar.png new file mode 100644 index 0000000..27f3f2d Binary files /dev/null and b/app/assets/images/eclipse/debug_button_in_toolbar.png differ diff --git a/app/assets/images/eclipse/debug_perspective.png b/app/assets/images/eclipse/debug_perspective.png new file mode 100644 index 0000000..d682536 Binary files /dev/null and b/app/assets/images/eclipse/debug_perspective.png differ diff --git a/app/assets/images/eclipse/debug_perspective_switch.png b/app/assets/images/eclipse/debug_perspective_switch.png new file mode 100644 index 0000000..407e6aa Binary files /dev/null and b/app/assets/images/eclipse/debug_perspective_switch.png differ diff --git a/app/assets/images/eclipse/debug_resume.png b/app/assets/images/eclipse/debug_resume.png new file mode 100644 index 0000000..74b903e Binary files /dev/null and b/app/assets/images/eclipse/debug_resume.png differ diff --git a/app/assets/images/eclipse/debug_step_into.png b/app/assets/images/eclipse/debug_step_into.png new file mode 100644 index 0000000..e3ae83c Binary files /dev/null and b/app/assets/images/eclipse/debug_step_into.png differ diff --git a/app/assets/images/eclipse/debug_step_over.png b/app/assets/images/eclipse/debug_step_over.png new file mode 100644 index 0000000..b928ddc Binary files /dev/null and b/app/assets/images/eclipse/debug_step_over.png differ diff --git a/app/assets/images/eclipse/debug_toolbar_buttons.png b/app/assets/images/eclipse/debug_toolbar_buttons.png new file mode 100644 index 0000000..3441f4e Binary files /dev/null and b/app/assets/images/eclipse/debug_toolbar_buttons.png differ diff --git a/app/assets/images/eclipse/file_create_project.png b/app/assets/images/eclipse/file_create_project.png new file mode 100644 index 0000000..a478d33 Binary files /dev/null and b/app/assets/images/eclipse/file_create_project.png differ diff --git a/app/assets/images/eclipse/getter_setter_modal.png b/app/assets/images/eclipse/getter_setter_modal.png new file mode 100644 index 0000000..f1a3a70 Binary files /dev/null and b/app/assets/images/eclipse/getter_setter_modal.png differ diff --git a/app/assets/images/eclipse/hashcode_equals_modal.png b/app/assets/images/eclipse/hashcode_equals_modal.png new file mode 100644 index 0000000..e482359 Binary files /dev/null and b/app/assets/images/eclipse/hashcode_equals_modal.png differ diff --git a/app/assets/images/eclipse/install.png b/app/assets/images/eclipse/install.png new file mode 100644 index 0000000..994474d Binary files /dev/null and b/app/assets/images/eclipse/install.png differ diff --git a/app/assets/images/eclipse/java_class_creation.png b/app/assets/images/eclipse/java_class_creation.png new file mode 100644 index 0000000..06d3368 Binary files /dev/null and b/app/assets/images/eclipse/java_class_creation.png differ diff --git a/app/assets/images/eclipse/open_problems_view.png b/app/assets/images/eclipse/open_problems_view.png new file mode 100644 index 0000000..5c32f95 Binary files /dev/null and b/app/assets/images/eclipse/open_problems_view.png differ diff --git a/app/assets/images/eclipse/problems_view.png b/app/assets/images/eclipse/problems_view.png new file mode 100644 index 0000000..7955656 Binary files /dev/null and b/app/assets/images/eclipse/problems_view.png differ diff --git a/app/assets/images/eclipse/problems_view_warning.png b/app/assets/images/eclipse/problems_view_warning.png new file mode 100644 index 0000000..90a125e Binary files /dev/null and b/app/assets/images/eclipse/problems_view_warning.png differ diff --git a/app/assets/images/eclipse/rename_box.png b/app/assets/images/eclipse/rename_box.png new file mode 100644 index 0000000..8915382 Binary files /dev/null and b/app/assets/images/eclipse/rename_box.png differ diff --git a/app/assets/images/eclipse/rename_different_text.png b/app/assets/images/eclipse/rename_different_text.png new file mode 100644 index 0000000..eb1c19c Binary files /dev/null and b/app/assets/images/eclipse/rename_different_text.png differ diff --git a/app/assets/images/eclipse/run_as_editor.png b/app/assets/images/eclipse/run_as_editor.png new file mode 100644 index 0000000..6c97d36 Binary files /dev/null and b/app/assets/images/eclipse/run_as_editor.png differ diff --git a/app/assets/images/eclipse/run_as_package_explorer.png b/app/assets/images/eclipse/run_as_package_explorer.png new file mode 100644 index 0000000..0fa6d1c Binary files /dev/null and b/app/assets/images/eclipse/run_as_package_explorer.png differ diff --git a/app/assets/images/eclipse/run_button.png b/app/assets/images/eclipse/run_button.png new file mode 100644 index 0000000..33e46e4 Binary files /dev/null and b/app/assets/images/eclipse/run_button.png differ diff --git a/app/assets/images/eclipse/run_buttons_toolbar.png b/app/assets/images/eclipse/run_buttons_toolbar.png new file mode 100644 index 0000000..7a256f4 Binary files /dev/null and b/app/assets/images/eclipse/run_buttons_toolbar.png differ diff --git a/app/assets/images/eclipse/tostring_options.png b/app/assets/images/eclipse/tostring_options.png new file mode 100644 index 0000000..e5ea8e6 Binary files /dev/null and b/app/assets/images/eclipse/tostring_options.png differ diff --git a/app/assets/images/eclipse/warning.png b/app/assets/images/eclipse/warning.png new file mode 100644 index 0000000..3e7b23b Binary files /dev/null and b/app/assets/images/eclipse/warning.png differ diff --git a/app/assets/images/eclipse/welcome.png b/app/assets/images/eclipse/welcome.png new file mode 100644 index 0000000..c82aa6c Binary files /dev/null and b/app/assets/images/eclipse/welcome.png differ diff --git a/app/assets/images/eclipse/workspace_selection.png b/app/assets/images/eclipse/workspace_selection.png new file mode 100644 index 0000000..306118b Binary files /dev/null and b/app/assets/images/eclipse/workspace_selection.png differ diff --git a/app/assets/images/intellij-idea/alt-enter.png b/app/assets/images/intellij-idea/alt-enter.png new file mode 100644 index 0000000..9f06c58 Binary files /dev/null and b/app/assets/images/intellij-idea/alt-enter.png differ diff --git a/app/assets/images/intellij-idea/create-test.png b/app/assets/images/intellij-idea/create-test.png new file mode 100644 index 0000000..dc545c6 Binary files /dev/null and b/app/assets/images/intellij-idea/create-test.png differ diff --git a/app/assets/images/intellij-idea/debug.gif b/app/assets/images/intellij-idea/debug.gif new file mode 100644 index 0000000..72588a4 Binary files /dev/null and b/app/assets/images/intellij-idea/debug.gif differ diff --git a/app/assets/images/intellij-idea/download-jdk-popup.png b/app/assets/images/intellij-idea/download-jdk-popup.png new file mode 100644 index 0000000..43bb3d0 Binary files /dev/null and b/app/assets/images/intellij-idea/download-jdk-popup.png differ diff --git a/app/assets/images/intellij-idea/edit-configurations.png b/app/assets/images/intellij-idea/edit-configurations.png new file mode 100644 index 0000000..65b694f Binary files /dev/null and b/app/assets/images/intellij-idea/edit-configurations.png differ diff --git a/app/assets/images/intellij-idea/find-in-files.png b/app/assets/images/intellij-idea/find-in-files.png new file mode 100644 index 0000000..f52b675 Binary files /dev/null and b/app/assets/images/intellij-idea/find-in-files.png differ diff --git a/app/assets/images/intellij-idea/generate-code.png b/app/assets/images/intellij-idea/generate-code.png new file mode 100644 index 0000000..50857cd Binary files /dev/null and b/app/assets/images/intellij-idea/generate-code.png differ diff --git a/app/assets/images/intellij-idea/hello-world.gif b/app/assets/images/intellij-idea/hello-world.gif new file mode 100644 index 0000000..ec3453c Binary files /dev/null and b/app/assets/images/intellij-idea/hello-world.gif differ diff --git a/app/assets/images/intellij-idea/junit5-dependencies.png b/app/assets/images/intellij-idea/junit5-dependencies.png new file mode 100644 index 0000000..f69bd3f Binary files /dev/null and b/app/assets/images/intellij-idea/junit5-dependencies.png differ diff --git a/app/assets/images/intellij-idea/main-method.png b/app/assets/images/intellij-idea/main-method.png new file mode 100644 index 0000000..6db76fb Binary files /dev/null and b/app/assets/images/intellij-idea/main-method.png differ diff --git a/app/assets/images/intellij-idea/new-java-class.png b/app/assets/images/intellij-idea/new-java-class.png new file mode 100644 index 0000000..86ce5b1 Binary files /dev/null and b/app/assets/images/intellij-idea/new-java-class.png differ diff --git a/app/assets/images/intellij-idea/new-project-menu.png b/app/assets/images/intellij-idea/new-project-menu.png new file mode 100644 index 0000000..e2816f3 Binary files /dev/null and b/app/assets/images/intellij-idea/new-project-menu.png differ diff --git a/app/assets/images/intellij-idea/new-project.png b/app/assets/images/intellij-idea/new-project.png new file mode 100644 index 0000000..1eea291 Binary files /dev/null and b/app/assets/images/intellij-idea/new-project.png differ diff --git a/app/assets/images/intellij-idea/project.png b/app/assets/images/intellij-idea/project.png new file mode 100644 index 0000000..166caa2 Binary files /dev/null and b/app/assets/images/intellij-idea/project.png differ diff --git a/app/assets/images/intellij-idea/refactor.gif b/app/assets/images/intellij-idea/refactor.gif new file mode 100644 index 0000000..b9b910a Binary files /dev/null and b/app/assets/images/intellij-idea/refactor.gif differ diff --git a/app/assets/images/intellij-idea/run-config.png b/app/assets/images/intellij-idea/run-config.png new file mode 100644 index 0000000..ac281b1 Binary files /dev/null and b/app/assets/images/intellij-idea/run-config.png differ diff --git a/app/assets/images/intellij-idea/run.png b/app/assets/images/intellij-idea/run.png new file mode 100644 index 0000000..fa5a38f Binary files /dev/null and b/app/assets/images/intellij-idea/run.png differ diff --git a/app/assets/images/intellij-idea/search-everywhere.png b/app/assets/images/intellij-idea/search-everywhere.png new file mode 100644 index 0000000..7d1385c Binary files /dev/null and b/app/assets/images/intellij-idea/search-everywhere.png differ diff --git a/app/assets/images/intellij-idea/welcome-screen.png b/app/assets/images/intellij-idea/welcome-screen.png new file mode 100644 index 0000000..56ca568 Binary files /dev/null and b/app/assets/images/intellij-idea/welcome-screen.png differ diff --git a/app/assets/images/javafx/animation/interpolator/discrete-example.gif b/app/assets/images/javafx/animation/interpolator/discrete-example.gif new file mode 100644 index 0000000..b755231 Binary files /dev/null and b/app/assets/images/javafx/animation/interpolator/discrete-example.gif differ diff --git a/app/assets/images/javafx/animation/interpolator/ease-both-example.gif b/app/assets/images/javafx/animation/interpolator/ease-both-example.gif new file mode 100644 index 0000000..58f46ff Binary files /dev/null and b/app/assets/images/javafx/animation/interpolator/ease-both-example.gif differ diff --git a/app/assets/images/javafx/animation/interpolator/ease-in-example.gif b/app/assets/images/javafx/animation/interpolator/ease-in-example.gif new file mode 100644 index 0000000..bd392bd Binary files /dev/null and b/app/assets/images/javafx/animation/interpolator/ease-in-example.gif differ diff --git a/app/assets/images/javafx/animation/interpolator/ease-out-example.gif b/app/assets/images/javafx/animation/interpolator/ease-out-example.gif new file mode 100644 index 0000000..e283435 Binary files /dev/null and b/app/assets/images/javafx/animation/interpolator/ease-out-example.gif differ diff --git a/app/assets/images/javafx/animation/interpolator/linear-example.gif b/app/assets/images/javafx/animation/interpolator/linear-example.gif new file mode 100644 index 0000000..8175bb3 Binary files /dev/null and b/app/assets/images/javafx/animation/interpolator/linear-example.gif differ diff --git a/app/assets/images/javafx/animation/timeline-example.gif b/app/assets/images/javafx/animation/timeline-example.gif new file mode 100644 index 0000000..1af1aeb Binary files /dev/null and b/app/assets/images/javafx/animation/timeline-example.gif differ diff --git a/app/assets/images/javafx/animation/transition/fade-example.gif b/app/assets/images/javafx/animation/transition/fade-example.gif new file mode 100644 index 0000000..af6bfcf Binary files /dev/null and b/app/assets/images/javafx/animation/transition/fade-example.gif differ diff --git a/app/assets/images/javafx/animation/transition/fill-example.gif b/app/assets/images/javafx/animation/transition/fill-example.gif new file mode 100644 index 0000000..df955c2 Binary files /dev/null and b/app/assets/images/javafx/animation/transition/fill-example.gif differ diff --git a/app/assets/images/javafx/animation/transition/parallel-example.gif b/app/assets/images/javafx/animation/transition/parallel-example.gif new file mode 100644 index 0000000..2cc0907 Binary files /dev/null and b/app/assets/images/javafx/animation/transition/parallel-example.gif differ diff --git a/app/assets/images/javafx/animation/transition/path-example.gif b/app/assets/images/javafx/animation/transition/path-example.gif new file mode 100644 index 0000000..b025af3 Binary files /dev/null and b/app/assets/images/javafx/animation/transition/path-example.gif differ diff --git a/app/assets/images/javafx/animation/transition/pause-example.gif b/app/assets/images/javafx/animation/transition/pause-example.gif new file mode 100644 index 0000000..bafc066 Binary files /dev/null and b/app/assets/images/javafx/animation/transition/pause-example.gif differ diff --git a/app/assets/images/javafx/animation/transition/rotate-example.gif b/app/assets/images/javafx/animation/transition/rotate-example.gif new file mode 100644 index 0000000..b584b57 Binary files /dev/null and b/app/assets/images/javafx/animation/transition/rotate-example.gif differ diff --git a/app/assets/images/javafx/animation/transition/scale-example.gif b/app/assets/images/javafx/animation/transition/scale-example.gif new file mode 100644 index 0000000..6194da2 Binary files /dev/null and b/app/assets/images/javafx/animation/transition/scale-example.gif differ diff --git a/app/assets/images/javafx/animation/transition/stroke-example.gif b/app/assets/images/javafx/animation/transition/stroke-example.gif new file mode 100644 index 0000000..5eff105 Binary files /dev/null and b/app/assets/images/javafx/animation/transition/stroke-example.gif differ diff --git a/app/assets/images/javafx/animation/transition/translate-example.gif b/app/assets/images/javafx/animation/transition/translate-example.gif new file mode 100644 index 0000000..c5a2303 Binary files /dev/null and b/app/assets/images/javafx/animation/transition/translate-example.gif differ diff --git a/app/assets/images/javafx/enahanced-myshapes-application.png b/app/assets/images/javafx/enahanced-myshapes-application.png new file mode 100644 index 0000000..8271223 Binary files /dev/null and b/app/assets/images/javafx/enahanced-myshapes-application.png differ diff --git a/app/assets/images/javafx/javafx-coordinates.png b/app/assets/images/javafx/javafx-coordinates.png new file mode 100644 index 0000000..cf9449b Binary files /dev/null and b/app/assets/images/javafx/javafx-coordinates.png differ diff --git a/app/assets/images/javafx/myshapes-application.png b/app/assets/images/javafx/myshapes-application.png new file mode 100644 index 0000000..7abddf2 Binary files /dev/null and b/app/assets/images/javafx/myshapes-application.png differ diff --git a/app/assets/images/javafx/myshapes-fxml-css.png b/app/assets/images/javafx/myshapes-fxml-css.png new file mode 100644 index 0000000..4ce7cb7 Binary files /dev/null and b/app/assets/images/javafx/myshapes-fxml-css.png differ diff --git a/app/assets/images/javafx/myshapes-properties-change.png b/app/assets/images/javafx/myshapes-properties-change.png new file mode 100644 index 0000000..7e8953d Binary files /dev/null and b/app/assets/images/javafx/myshapes-properties-change.png differ diff --git a/app/assets/images/javafx/myshapes-properties-fluent.png b/app/assets/images/javafx/myshapes-properties-fluent.png new file mode 100644 index 0000000..1ee43b9 Binary files /dev/null and b/app/assets/images/javafx/myshapes-properties-fluent.png differ diff --git a/app/assets/images/javafx/myshapes-properties-invalidation.png b/app/assets/images/javafx/myshapes-properties-invalidation.png new file mode 100644 index 0000000..aeec43e Binary files /dev/null and b/app/assets/images/javafx/myshapes-properties-invalidation.png differ diff --git a/app/assets/images/javafx/myshapes-properties.png b/app/assets/images/javafx/myshapes-properties.png new file mode 100644 index 0000000..a6d16da Binary files /dev/null and b/app/assets/images/javafx/myshapes-properties.png differ diff --git a/app/assets/images/javafx/myshapes-scene-graph.png b/app/assets/images/javafx/myshapes-scene-graph.png new file mode 100644 index 0000000..bb7dc27 Binary files /dev/null and b/app/assets/images/javafx/myshapes-scene-graph.png differ diff --git a/app/assets/images/javafx/person-ui-app.png b/app/assets/images/javafx/person-ui-app.png new file mode 100644 index 0000000..1d03acc Binary files /dev/null and b/app/assets/images/javafx/person-ui-app.png differ diff --git a/app/assets/images/javafx/person-ui-file-struct.png b/app/assets/images/javafx/person-ui-file-struct.png new file mode 100644 index 0000000..bfbb2ea Binary files /dev/null and b/app/assets/images/javafx/person-ui-file-struct.png differ diff --git a/app/assets/images/javafx/person-ui-scene-graph.png b/app/assets/images/javafx/person-ui-scene-graph.png new file mode 100644 index 0000000..c219817 Binary files /dev/null and b/app/assets/images/javafx/person-ui-scene-graph.png differ diff --git a/app/assets/images/javafx/rotate-transition-myshapes-application.png b/app/assets/images/javafx/rotate-transition-myshapes-application.png new file mode 100644 index 0000000..dacfda5 Binary files /dev/null and b/app/assets/images/javafx/rotate-transition-myshapes-application.png differ diff --git a/app/assets/images/javafx/scene-builder.png b/app/assets/images/javafx/scene-builder.png new file mode 100644 index 0000000..2c5dea8 Binary files /dev/null and b/app/assets/images/javafx/scene-builder.png differ diff --git a/app/data/authors.yaml b/app/data/authors.yaml index dfbf31d..96c500d 100644 --- a/app/data/authors.yaml +++ b/app/data/authors.yaml @@ -1,36 +1,3 @@ -- name: Chad Arimura - email: chad.arimura@oracle.com - photo_url: https://chadarimura.com/chad.jpg - github: carimura - twitter: chadarimura - website: https://chadarimura.com - description: | - Chad is a cool guy. - and here's more text. - thanks. - -- name: Nicolai Parlog - email: nicolai.parlog@oracle.com - photo_url: https://nipafx.dev/static/8a28e9a07c4004e9d9f136b410e484e8/6cc9c/nicolai.webp - github: nipafx - twitter: nipafx - website: https://nipafx.dev - description: | - Nicolai is a cool guy. - and here's more text. - thanks. - -- name: José Paumard - email: jose.paumard@oracle.com - photo_url: https://pluralsight.imgix.net/author/lg/jose-paumard-v1.jpg - github: josepaumard - twitter: josepaumard - website: https://youtube.com/hashtag/jepcafe - description: | - José is a cool guy. - and here's more text. - thanks. - - name: Venkat Subramaniam email: venkats@agiledeveloper.com photo_url: https://itkonekt.com/media/2019/12/Venkat.png @@ -38,4 +5,113 @@ twitter: venkat_s website: https://agiledeveloper.com/ description: | - Venkat Placeholder. \ No newline at end of file + Dr. Venkat Subramaniam is an award-winning author, founder of Agile Developer, Inc., and an instructional professor at the University of Houston. He has mentored tens of thousands of software developers in the US, Canada, Europe, and Asia, and is a regularly-invited speaker at several international conferences. Venkat helps his clients effectively apply and succeed with agile practices on their software projects.

+ + Venkat is a (co)author of multiple books, including the 2007 Jolt Productivity award winning book Practices of an Agile Developer. His latest book is Programming Kotlin: Create Elegant, Expressive, and Performant JVM and Android Applications. Venkat is a well-recognized person in the software communities. He was once a recipient of the MicroSoft MVP award. He has received JavaOne RockStar award three years in a row and was inducted into the Java Champions program in 2013 for his efforts in motivating and inspiring software developers around the world. + +- name: Jeanne Boyarsky + email: nyjeanne@gmail.com + photo_url: https://www.selikoff.net/wp-content/uploads/2019/05/jeanne-headshot.jpg + github: boyarsky + twitter: jeanneboyarsky + website: https://jeanneboyarsky.com + description: | + Jeanne is a Java developer and Java champion in NYC. She works as a developer for a bank in New York City and serves as a + moderator on CodeRanch which is the best java discussion forum on the web. She has moderated on topics including + JDBC, Testing, IDEs, Process and Performance. She is one of the leaders of javasig.com and mentors a high school + robotics team in her free time. + +- name: Cay Horstmann + email: cay@horstmann.com + photo_url: https://horstmann.com/cay-pfh.jpg + github: cayhorstmann + twitter: cayhorstmann + website: https://horstmann.com + description: | + I grew up in Northern Germany and attended the Christian-Albrechts-Universität in Kiel, a harbor town at the Baltic sea. + I received a M.S. in computer science from Syracuse University, and a Ph.D. in mathematics from the University of + Michigan in Ann Arbor. I taught computer science at San Jose State University for almost thirty years and held + visiting appointments at universities in Germany, Switzerland, Vietnam, and Macau. I was a “serial entrepreneur” + before that was a thing, including a stint as VP and CTO of a dot com startup that went from three people in a + tiny office to a public company. In my copious spare time I write books and develop online courses for beginning + and professional programmers. + +- name: Dr Heinz M. Kabutz + email: heinz@javaspecialists.eu + photo_url: https://javaspecialists.eu/pics/gb/heinz2-300x300.jpg + github: kabutz + twitter: heinzkabutz + website: https://www.javaspecialists.eu + description: | + Heinz is the author of The Java Specialists' Newsletter, + a monthly newsletter that has been in publication since the end of 2000. In it, he explores + many useful tips and tricks that have enthralled tens of thousands of enthusiastic fans for + over two decades. He was one of the first Java Champions. + +- name: Paul Anderson + photo_url: https://asgteach.com/wp-content/uploads/2016/02/PaulAnderson-300x291.png + twitter: Paul_ASGTeach + website: https://asgteach.com/ + description: | + Paul is a Java Champion and a member of the Oracle ACE Program. He is an experienced speaker, + making the technical aspects of software engineering fun and understandable. + He has conducted Technical Sessions and Tutorials at JavaOne, Devoxx, and NetBeans Day conferences + in San Francisco, Europe, and Latin America. Paul is also the author of LiveLesson videos on JavaFX + Programming and Java Reflection for Pearson Education. He has contributed articles to InformIT + and is the co-author of eight textbooks on software programming. + +- name: Gail C. Anderson + photo_url: https://asgteach.com/wp-content/uploads/2016/02/GailAnderson.png + twitter: gail_asgteach + website: https://asgteach.com/ + description: | + Gail is a Java Champion and a member of the Oracle ACE Program. She is Director of Research + and founding member of the Anderson Software Group, a leading provider of training courses + in Java, JavaFX, Python, Go, Modern C++, and other programming languages. + Gail enjoys researching and writing about leading-edge Java technologies. Her current passion + includes JavaFX with GraalVM for cross-platform mobile applications. She is the co-author of + nine textbooks on software programming. Most recently, she is a contributing author to + The Definitive Guide to Modern Java Clients with JavaFX 17. Gail has presented at various + Java conferences and JUGS including Devoxx, DevNexus, JCrete, and Oracle Code/JavaOne worldwide. + +- name: Daniel Schmid + github: danthe1st + twitter: dan_the_1st + photo_url: https://danthe1st.github.io/img/Daniel.jpg + website: https://danthe1st.github.io/ + description: | + Daniel is a Java Developer from Austria who is also managing the Discord Java Community. + +- name: Marit van Dijk + email: mlvandijk@gmail.com + photo_url: https://maritvandijk.com/wp-content/uploads/2022/08/cropped-marit-van-dijk-1.jpeg + github: mlvandijk + twitter: MaritvanDijk77 + website: https://maritvandijk.com + description: | + Marit is a Software Developer, a Java Champion and works as a Developer Advocate at JetBrains. + She speaks at international conferences (including JavaZone, Devoxx, Voxxed Days, JSpring, JFall, JFokus and GOTO), local JUGs, podcasts and webinars, + and has contributed to the book “97 Things Every Java Programmer Should Know” (O’Reilly Media). + +- name: Connor Schweighöfer + email: squidxtv@gmail.com + photo_url: https://squidxtv.me/img/Connor.png + github: SquidXTV + twitter: SquidXTV + website: https://squidxtv.me/ + description: | + Connor is a Java Developer and high school student from Germany with four years of programming experience. + He is a top helper and community lead in Together Java, one of the largest Java communities on Discord, with over 30,000 users. + Connor has worked on various Java projects, including Discord bots, Minecraft plugins and JavaFX applications. + +- name: Nataliia Dziubenko + github: smthelusive + twitter: worth_exploring + photo_url: https://nataliiadziubenko.com/assets/images/nataliia.png + website: https://nataliiadziubenko.com/ + description: | + Nataliia is a Senior Software Engineer at Xebia. She began her career in 2015 and has since worked with diverse + technologies and domains, primarily focusing on the JVM ecosystem. Nataliia is passionate and curious about everything + around JVM. She enjoys digging into the low-level concepts and diving into how things work under the hood. She also + loves sharing knowledge, so she speaks at conferences such as JFall, JSpring, Voxxed Days, and Java User Groups, + and writes a blog about JVM. \ No newline at end of file diff --git a/app/data/javadoc.json b/app/data/javadoc.json index fd1122d..a22629c 100644 --- a/app/data/javadoc.json +++ b/app/data/javadoc.json @@ -1,8 +1,10 @@ { - "current_release": "19", + "current_release": "23", "java-documentation": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/", + "java-documentation-summary": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/api/java.naming/module-summary.html", "javadoc_root": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/api/", + "jdk-release-note": "https://www.oracle.com/java/technologies/javase/jdk-relnotes-index.html", "security-standard-names": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/security/standard-names.html", "jre-jdk-cryptoroadmap": "https://www.java.com/en/jre-jdk-cryptoroadmap.html", @@ -13,7 +15,7 @@ "jpackage": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jpackage.html", "java-api-docs": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/api/index.html", "specification": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/api/index.html", - "release-notes": "https://www.oracle.com/java/technologies/javase/@@CURRENT_RELEASE@@-relnotes.html", + "release-notes": "https://www.oracle.com/java/technologies/javase/@@CURRENT_RELEASE@@u-relnotes.html", "jvm-guide": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/vm/java-virtual-machine-technology-overview.html", "gc-tuning": { "link": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/gctuning/introduction-garbage-collection-tuning.html", @@ -52,6 +54,7 @@ "jmod": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jmod.html", "jdeps": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jdeps.html", "jdeprscan": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jdeprscan.html", + "jwebserver": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jwebserver.html", "jfr": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jfr.html", "jconsole": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jconsole.html", "jps": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jps.html", @@ -77,9 +80,13 @@ "rfc-5246": "https://www.ietf.org/rfc/rfc5246.html", "rfc-8446": "https://www.ietf.org/rfc/rfc8446.html", "rfc-8017": "https://www.ietf.org/rfc/rfc8017.html", + "rfc-8032": "https://tools.ietf.org/html/rfc8032", + "trec-x509": "https://www.itu.int/rec/T-REC-X.509/en", + "dss": "https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf", + "rfc-1422": "https://www.ietf.org/rfc/rfc1422.html", "ANSI-20X9.62": "https://standards.globalspec.com/std/1955141/ANSI%20X9.62", - "ide-eclipse": "https://www.eclipse.org/", + "ide-eclipse": "https://www.eclipseide.org/", "ide-netbeans": "https://netbeans.apache.org/", "ide-intellij": "https://www.jetbrains.com/idea/", @@ -121,6 +128,8 @@ "Object.wait(long)": "java.base/java/lang/Object.html#wait(long)", "Object.wait(long,int)": "java.base/java/lang/Object.html#wait(long,int)", + "ClassLoader": "java.base/java/lang/ClassLoader.html", + "NullPointerException": "java.base/java/lang/NullPointerException.html", "Override": "java.base/java/lang/Override.html", @@ -138,26 +147,17 @@ "Cloneable": "java.base/java/lang/Cloneable.html", "CloneNotSupportedException": "java.base/java/lang/CloneNotSupportedException.html", - "Class": "java.base/java/lang/Class.html", - "Class.getFields()": "java.base/java/lang/Class.html#getFields()", - "Class.getInterfaces()": "java.base/java/lang/Class.html#getInterfaces()", - "Class.getMethods()": "java.base/java/lang/Class.html#getMethods()", - "Class.getSimpleName()": "java.base/java/lang/Class.html#getSimpleName()", - "Class.getSuperclass()": "java.base/java/lang/Class.html#getSuperclass()", - "Class.isAnnotation()": "java.base/java/lang/Class.html#isAnnotation()", - "Class.isEnum()": "java.base/java/lang/Class.html#isEnum()", - "Class.isInterface()": "java.base/java/lang/Class.html#isInterface()", - "Record": "java.base/java/lang/Record.html", "System": "java.base/java/lang/System.html", "System.out": "java.base/java/lang/System.html#out", "System.arraycopy(java.lang.Object,int,java.lang.Object,int,int)": "java.base/java/lang/System.html#arraycopy(java.lang.Object,int,java.lang.Object,int,int)", + "System.console()": "java.base/java/lang/System.html#console()", "Long": "java.base/java/lang/Long.html", "Long.compareTo(Long)": "java.base/java/lang/Long.html#compareTo(java.lang.Long)", - "Long.compareUnsigned(int,int)": "java.base/java/lang/Long.html#compareUnsigned(int,int)", - "Long.divideUnsigned(int,int)": "java.base/java/lang/Long.html#divideUnsigned(int,int)", + "Long.compareUnsigned(long,long)": "java.base/java/lang/Long.html#compareUnsigned(long,long)", + "Long.divideUnsigned(long,long)": "java.base/java/lang/Long.html#divideUnsigned(long,long)", "Arrays": "java.base/java/util/Arrays.html", "Arrays.asList()": "java.base/java/util/Arrays.html#asList(T...)", @@ -237,10 +237,6 @@ "Math.toRadians(double)": "java.base/java/lang/Math.html#toRadians(double)", "Math": "java.base/java/lang/Math.html", - "Collections.sort(List)": "java.base/java/util/Collections.html#sort(java.util.List)", - "Collections.sort(List,comparator)": "java.base/java/util/Collections.html#sort(java.util.List,java.util.Comparator)", - "Collections.emptyList()": "java.base/java/util/Collections.html#emptyList()", - "File": "java.base/java/io/File.html", "File(String)": "java.base/java/io/File.html#%3Cinit%3E(java.lang.String)", "File(String,String)" : "java.base/java/io/File.html#%3Cinit%3E(java.lang.String,java.lang.String)", @@ -275,6 +271,7 @@ "File.toURI()" : "java.base/java/io/File.html#toURI()", "File.isHidden()" : "java.base/java/io/File.html#isHidden()", "File.list()" : "java.base/java/io/File.html#list()", + "File.listFiles()" : "java.base/java/io/File.html#listFiles()", "File.mkdir()" : "java.base/java/io/File.html#mkdir()", "File.mkdirs()" : "java.base/java/io/File.html#mkdirs()", "File.listRoots()" : "java.base/java/io/File.html#listRoots()", @@ -314,6 +311,8 @@ "Files.readAllLines(Path)": "java.base/java/nio/file/Files.html#readAllLines(java.nio.file.Path)", "Files.readAllLines(Path,Charset)": "java.base/java/nio/file/Files.html#readAllLines(java.nio.file.Path,java.nio.charset.Charset)", "Files.write(byte)": "java.base/java/nio/file/Files.html#write(java.nio.file.Path,byte%5B%5D,java.nio.file.OpenOption...)", + "Files.writeString()": "java.base/java/nio/file/Files.html#writeString(java.nio.file.Path,java.lang.CharSequence,java.nio.file.OpenOption...)", + "Files.write()": "java.base/java/nio/file/Files.html#write(java.nio.file.Path,byte%5B%5D,java.nio.file.OpenOption...)", "Files.write(Iterable)": "java.base/java/nio/file/Files.html#write(java.nio.file.Path,java.lang.Iterable,java.nio.charset.Charset,java.nio.file.OpenOption...)", "Files.newBufferedReader(CharSet)": "java.base/java/nio/file/Files.html#newBufferedReader(java.nio.file.Path,java.nio.charset.Charset)", "Files.newBufferedWriter()": "java.base/java/nio/file/Files.html#newBufferedWriter(java.nio.file.Path,java.nio.charset.Charset,java.nio.file.OpenOption...)", @@ -335,9 +334,16 @@ "Files.readSymbolicLink(Path)": "java.base/java/nio/file/Files.html#readSymbolicLink(java.nio.file.Path)", "Files.probeContentType(Path)": "java.base/java/nio/file/Files.html#probeContentType(java.nio.file.Path)", "Files.lines(Path)": "java.base/java/nio/file/Files.html#lines(java.nio.file.Path)", + "Files.list(Path)": "java.base/java/nio/file/Files.html#list(java.nio.file.Path)", + "Files.find(Path)": "java.base/java/nio/file/Files.html#find(java.nio.file.Path,int,java.util.function.BiPredicate,java.nio.file.FileVisitOption...)", + "Files.walk(Path)": "java.base/java/nio/file/Files.html#walk(java.nio.file.Path,java.nio.file.FileVisitOption...)", + "Files.walk(Path,depth)": "java.base/java/nio/file/Files.html#walk(java.nio.file.Path,int,java.nio.file.FileVisitOption...)", + "Files.walkFileTree(Path)": "java.base/java/nio/file/Files.html#walkFileTree(java.nio.file.Path,java.nio.file.FileVisitor)", "Files.copy(InputStream,Path,CopyOption)": "java.base/java/nio/file/Files.html#copy(java.io.InputStream,java.nio.file.Path,java.nio.file.CopyOption...)", "Files.copy(Path,OutputStream)": "java.base/java/nio/file/Files.html#copy(java.nio.file.Path,java.io.OutputStream)", "Files.copy(Path,Path,CopyOption)": "java.base/java/nio/file/Files.html#copy(java.nio.file.Path,java.nio.file.Path,java.nio.file.CopyOption...)", + "Files.readString(Path)": "java.base/java/nio/file/Files.html#readString(java.nio.file.Path)", + "Files.readAllBytes(Path)": "java.base/java/nio/file/Files.html#readAllBytes(java.nio.file.Path)", "Files.setAttribute(Path,String,Object,LinkOption)": "java.base/java/nio/file/Files.html#setAttribute(java.nio.file.Path,java.lang.String,java.lang.Object,java.nio.file.LinkOption...)", "Files.createTempFile(String,String,FileAttribute)": "java.base/java/nio/file/Files.html#createTempFile(java.lang.String,java.lang.String,java.nio.file.attribute.FileAttribute...)", "Files.createTempFile(Path,String,String,FileAttribute)": "java.base/java/nio/file/Files.html#createTempFile(java.nio.file.Path,java.lang.String,java.lang.String,java.nio.file.attribute.FileAttribute...)", @@ -407,9 +413,7 @@ "FileTime": "java.base/java/nio/file/attribute/FileTime.html", - "Iterable": "java.base/java/lang/Iterable.html", - "Iterable.forEach()": "java.base/java/lang/Iterable.html#forEach(java.util.function.Consumer)", - "Iterable.iterator()": "java.base/java/lang/Iterable.html#iterator()", + "Console": "java.base/java/io/Console.html", "Path": "java.base/java/nio/file/Path.html", "Path.toFile()": "java.base/java/nio/file/Path.html#toFile()", @@ -451,6 +455,8 @@ "Float.parseFloat(String)": "java.base/java/lang/Float.html#parseFloat(java.lang.String)", "Double": "java.base/java/lang/Double.html", + "Double.toString(double)": "java.base/java/lang/Double.html#Double.html#toString(double)", + "Double.parseDouble(String)": "java.base/java/lang/Double.html#parseDouble(java.lang.String)", "Double.compareTo(Double)": "java.base/java/lang/Double.html#compareTo(java.lang.Double)", "Character": "java.base/java/lang/Character.html", @@ -504,7 +510,9 @@ "String.regionMatches(int,String,int,int)": "java.base/java/lang/String.html#regionMatches(int,java.lang.String,int,int)", "String.equals(Object)": "java.base/java/lang/String.html#equals(java.lang.Object)", "String.regionMatches(boolean,int,String,int,int)": "java.base/java/lang/String.html#regionMatches(boolean,int,java.lang.String,int,int)", - "String.join(CharSequence,CharSequence)": "java.base/java/lang/String.html#join(java.lang.CharSequence,java.lang.CharSequence)", + "String.join(CharSequence,CharSequence)": "java.base/java/lang/String.html#join(java.lang.CharSequence,java.lang.CharSequence...)", + "String.join(CharSequence,Iterable)": "java.base/java/lang/String.html#join(java.lang.CharSequence,java.lang.Iterable)", + "String.valueOf(Object)": "java.base/java/lang/String.html#valueOf(java.lang.Object)", "StringIndexOutOfBoundsException": "java.base/java/lang/StringIndexOutOfBoundsException.html", @@ -513,8 +521,8 @@ "StringBuilder": "java.base/java/lang/StringBuilder.html", "StringBuilder()": "java.base/java/lang/StringBuilder.html#%3Cinit%3E()", - "StringBuilder(CharSequence)": "java/lang/StringBuilder.html#%3Cinit%3E(java.lang.CharSequence)", - "StringBuilder(String)": "java/lang/StringBuilder.html#%3Cinit%3E(java.lang.String)", + "StringBuilder(CharSequence)": "java.base/java/lang/StringBuilder.html#%3Cinit%3E(java.lang.CharSequence)", + "StringBuilder(String)": "java.base/java/lang/StringBuilder.html#%3Cinit%3E(java.lang.String)", "StringBuilder(int)": "java.base/java/lang/StringBuilder.html#%3Cinit%3E(int)", "StringBuilder.length()": "java.base/java/lang/StringBuilder.html#length()", "StringBuilder.capacity()": "java.base/java/lang/StringBuilder.html#capacity()", @@ -555,6 +563,7 @@ "Externalizable": "java.base/java/io/Externalizable.html", "Writer": "java.base/java/io/Writer.html", + "FileWriter": "java.base/java/io/FileWriter.html", "FileReader": "java.base/java/io/FileReader.html", "FilterWriter": "java.base/java/io/FilterWriter.html", "CharArrayReader": "java.base/java/io/CharArrayReader.html", @@ -566,6 +575,7 @@ "LineNumberReader": "java.base/java/io/LineNumberReader.html", "BufferedWriter": "java.base/java/io/BufferedWriter.html", + "BufferedWriter.write(int)": "java.base/java/io/BufferedWriter.html#write(int)", "BufferedWriter.close()": "java.base/java/io/BufferedWriter.html#close()", "FileNotFoundException": "java.base/java/io/FileNotFoundException.html", @@ -601,10 +611,14 @@ "BufferedOutputStream": "java.base/java/io/BufferedOutputStream.html", "PrintWriter": "java.base/java/io/PrintWriter.html", + "PrintWriter.printf()": "java.base/java/io/PrintWriter.html#printf(java.lang.String,java.lang.Object...)", "PrintWriter.println()": "java.base/java/io/PrintWriter.html#println()", - "GZIPInputStream": "java.base/java/io/GZIPInputStream.html", - "GZIPOutputStream": "java.base/java/io/GZIPOutputStream.html", + "ZipInputStream": "java.base/java/util/zip/ZipInputStream.html", + "ZipOutputStream": "java.base/java/util/zip/ZipOutputStream.html", + + "GZIPInputStream": "java.base/java/util/zip/GZIPInputStream.html", + "GZIPOutputStream": "java.base/java/util/zip/GZIPOutputStream.html", "DataOutputStream": "java.base/java/io/DataOutputStream.html", "DataOutputStream.writeBoolean(boolean)": "java.base/java/io/DataOutputStream#writeBoolean(boolean)", @@ -641,6 +655,8 @@ "InputStreamReader": "java.base/java/io/InputStreamReader.html", "OutputStreamWriter": "java.base/java/io/OutputStreamWriter.html", + "Scanner": "java.base/java/util/Scanner.html", + "Closeable": "java.base/java/io/Closeable.html", "Closeable.close()": "java.base/java/io/Closeable.html#close()", @@ -670,6 +686,8 @@ "SeekableByteChannel.write(ByteBuffer)": "java.base/java/nio/channels/SeekableByteChannel.html#write(java.nio.ByteBuffer)", "SeekableByteChannel.truncate(long)": "java.base/java/nio/channels/SeekableByteChannel.html#truncate(long)", + "ImageIO.read(URL)": "java.desktop/javax/imageio/ImageIO.html#read(java.net.URL)", + "URI": "java.base/java/net/URI.html", "LinkedList": "java.base/java/util/LinkedList.html", @@ -917,7 +935,8 @@ "TimeZone.toZoneId()": "java.base/java/util/TimeZone.html#toZoneId()", "DateTimeParseException": "java.base/java/time/format/DateTimeParseException.html", - "DateTimeException": "java.base/java/time/format/DateTimeException.html", + + "DateTimeException": "java.base/java/time/DateTimeException.html", "DecimalFormat": "java.base/java/text/DecimalFormat.html", @@ -949,6 +968,7 @@ "NegativeArraySizeException": "java.base/java/lang/NegativeArraySizeException.html", "EmptyStackException": "java.base/java/util/EmptyStackException.html", "RuntimeException": "java.base/java/lang/RuntimeException.html", + "ReflectiveOperationException": "java.base/java/lang/ReflectiveOperationException.html", "ArrayStoreException": "java.base/java/lang/ArrayStoreException.html", "ClassCastException": "java.base/java/lang/ClassCastException.html", @@ -1042,9 +1062,6 @@ "RetentionPolicy.RUNTIME": "java.base/java/lang/annotation/RetentionPolicy.html#RUNTIME", "RetentionPolicy.SOURCE": "java.base/java/lang/annotation/RetentionPolicy.html#SOURCE", "Target": "java.base/java/lang/annotation/Target.html", - "AnnotatedElement": "java.base/java/lang/reflect/AnnotatedElement.html", - "AnnotatedElement.getAnnotation(Class)": "java.base/java/lang/reflect/AnnotatedElement.html#getAnnotation(java.lang.Class)", - "AnnotatedElement.getAnnotationsByType(Class)": "java.base/java/lang/reflect/AnnotatedElement.html#getAnnotationsByType(java.lang.Class)", "IllegalArgumentException": "java.base/java/lang/IllegalArgumentException.html", "IllegalStateException": "java.base/java/lang/IllegalStateException.html", @@ -1054,9 +1071,20 @@ "ArrayDeque": "java.base/java/util/ArrayDeque.html", + "Iterator": "java.base/java/util/Iterator.html", + "Iterator.hasNext()": "java.base/java/util/Iterator.html#hasNext()", + "Iterator.next()": "java.base/java/util/Iterator.html#next()", + "Iterator.remove()": "java.base/java/util/Iterator.html#remove()", + "Iterator.forEachRemaining(Consumer)": "java.base/java/util/Iterator.html#forEachRemaining(java.util.function.Consumer)", + + "Iterable": "java.base/java/lang/Iterable.html", + "Iterable.forEach()": "java.base/java/lang/Iterable.html#forEach(java.util.function.Consumer)", + "Iterable.iterator()": "java.base/java/lang/Iterable.html#iterator()", + "Collection": "java.base/java/util/Collection.html", "Collection.removeIf(Predicate)": "java.base/java/util/Collection.html#removeIf(java.util.function.Predicate)", "Collection.add(E)": "java.base/java/util/Collection.html#add(E)", + "Collection.remove(Object)": "java.base/java/util/Collection.html#remove(java.lang.Object)", "Collection.addAll(Collection)": "java.base/java/util/Collection.html#addAll(java.util.Collection)", "Collection.clear()": "java.base/java/util/Collection.html#clear()", "Collection.contains(Object)": "java.base/java/util/Collection.html#contains(java.lang.Object)", @@ -1078,8 +1106,11 @@ "Collections.min(Collection)": "java.base/java/util/Collections.html#min(java.util.Collection)", "Collections.reverse(List)": "java.base/java/util/Collections.html#reverse(java.util.List)", "Collections.rotate(List,int)": "java.base/java/util/Collections.html#rotate(java.util.List,int)", - "Collections.shuffle(List)": "java.base/java/util/Collections.html#shuffle(java.util.List)", "Collections.swap(List,int,int)": "java.base/java/util/Collections.html#swap(java.util.List,int,int)", + "Collections.sort(List)": "java.base/java/util/Collections.html#sort(java.util.List)", + "Collections.shuffle(List)": "java.base/java/util/Collections.html#shuffle(java.util.List)", + "Collections.sort(List,comparator)": "java.base/java/util/Collections.html#sort(java.util.List,java.util.Comparator)", + "Collections.emptyList()": "java.base/java/util/Collections.html#emptyList()", "Deque": "java.base/java/util/Deque.html", "Deque.addFirst(E)": "java.base/java/util/Deque.html#addFirst(E)", @@ -1097,6 +1128,7 @@ "Deque.push(E)": "java.base/java/util/Deque.html#push(E)", "Deque.removeFirst()": "java.base/java/util/Deque.html#removeFirst()", + "Enum": "java.base/java/lang/Enum.html", "Enumeration": "java.base/java/util/Enumeration.html", "HashSet": "java.base/java/util/HashSet.html", @@ -1106,11 +1138,6 @@ "IdentityHashMap": "java.base/java/util/IdentityHashMap.html", - "Iterator": "java.base/java/util/Iterator.html", - "Iterator.hasNext()": "java.base/java/util/Iterator.html#hasNext()", - "Iterator.next()": "java.base/java/util/Iterator.html#next()", - "Iterator.remove()": "java.base/java/util/Iterator.html#remove()", - "LinkedHashMap": "java.base/java/util/LinkedHashMap.html", "List": "java.base/java/util/List.html", @@ -1191,6 +1218,9 @@ "NavigableMap.subMap(K,boolean,K,boolean)": "java.base/java/util/NavigableMap.html#subMap(K,boolean,K,boolean)", "NavigableMap.tailMap(K)": "java.base/java/util/NavigableMap.html#tailMap(K)", + "ConcurrentMap": "java.base/java/util/concurrent/ConcurrentMap.html", + "ConcurrentMap.putIfAbsent()": "java.base/java/util/concurrent/ConcurrentMap.html#putIfAbsent()", + "NavigableSet": "java.base/java/util/NavigableSet.html", "NavigableSet.ceiling(E)": "java.base/java/util/NavigableSet.html#ceiling(E)", "NavigableSet.descendingIterator()": "java.base/java/util/NavigableSet.html#descendingIterator()", @@ -1254,6 +1284,7 @@ "NumberFormatException": "java.base/java/lang/NumberFormatException.html", "OutOfMemoryError": "java.base/java/lang/OutOfMemoryError.html", + "Character.UnicodeBlock": "java.base/java/lang/Character.UnicodeBlock.html", "Character.toString(int)": "java.base/java/lang/Character.html#toString(int)", "Charset": "java.base/java/nio/charset/Charset.html", @@ -1390,6 +1421,7 @@ "Stream.toArray()": "java.base/java/util/stream/Stream.html#toArray()", "Stream.toList()": "java.base/java/util/stream/Stream.html#toList()", + "HttpClient": "java.net.http/java/net/http/HttpClient.html", "HttpClient.send(HttpRequest,HttpResponse.BodyHandler)": "java.net.http/java/net/http/HttpClient.html#send(java.net.http.HttpRequest,java.net.http.HttpResponse.BodyHandler)", "HttpResponse.BodyHandlers.ofLines()": "java.net.http/java/net/http/HttpResponse.BodyHandlers.html#ofLines()", @@ -1405,5 +1437,167 @@ "MessageDigest": "java.base/java/security/MessageDigest.html", "Security.getProviders()": "java.base/java/security/Security.html#getProviders()", "Security.addProvider(java.security.Provider)": "java.base/java/security/Security.html#addProvider(java.security.Provider)", - "Security.insertProviderAt": "java.base/java/security/Security.html#insertProviderAt(java.security.Provider,int)" + "Security.insertProviderAt": "java.base/java/security/Security.html#insertProviderAt(java.security.Provider,int)", + "Certificate": "java.base/java/security/cert/Certificate.html", + "X509Certificate": "java.base/java/security/cert/X509Certificate.html", + "TrustAnchor": "java.base/java/security/cert/TrustAnchor.html", + "CertPathValidator": "java.base/java/security/cert/CertPathValidator.html", + "Security": "java.base/java/security/Security.html", + "FlightRecorderMXBean": "jdk.management.jfr/jdk/management/jfr/FlightRecorderMXBean.html", + "MBeanServerConnection": "java.management/javax/management/MBeanServerConnection.html", + "SimpleFileServer": "jdk.httpserver/com/sun/net/httpserver/SimpleFileServer", + + "Lookup": "java.base/java/lang/invoke/MethodHandles.Lookup.html", + "MethodType": "java.base/java/lang/invoke/MethodType.html", + "MethodHandles": "java.base/java/lang/invoke/MethodHandles.html", + "MethodHandles.Lookup.findVirtual(Class,String,MethodType)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#findVirtual(java.lang.Class,java.lang.String,java.lang.invoke.MethodType)", + "MethodHandles.Lookup.findStatic(Class,String,MethodType)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#findStatic(java.lang.Class,java.lang.String,java.lang.invoke.MethodType)", + "MethodHandles.Lookup.findConstructor(Class,MethodType)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#findConstructor(java.lang.Class,java.lang.invoke.MethodType)", + "MethodHandles.Lookup.findGetter(Class,String,Class)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#findGetter(java.lang.Class,java.lang.String,java.lang.Class)", + "MethodHandles.Lookup.findSetter(Class,String,Class)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#findSetter(java.lang.Class,java.lang.String,java.lang.Class)", + "MethodHandles.Lookup.findStaticGetter(Class,String,Class)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#findStaticGetter(java.lang.Class,java.lang.String,java.lang.Class)", + "MethodHandles.Lookup.findStaticSetter(Class,String,Class)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#findStaticSetter(java.lang.Class,java.lang.String,java.lang.Class)", + "MethodHandles.Lookup.findVarHandle(Class,String,Class)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#findVarHandle(java.lang.Class,java.lang.String,java.lang.Class)", + "MethodHandles.Lookup.findStaticVarHandle(Class,String,Class)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#findStaticVarHandle(java.lang.Class,java.lang.String,java.lang.Class)", + "MethodHandles.Lookup.unreflect(Method)": "java.base/java/lang/invoke/MethodHandles.Lookup.html#unreflect(java.lang.reflect.Method)", + "MethodHandles.publicLookup()": "java.base/java/lang/invoke/MethodHandles.html#publicLookup()", + "MethodHandles.tryFinally(MethodHandle,MethodHandle)": "java.base/java/lang/invoke/MethodHandles.html#tryFinally(java.lang.invoke.MethodHandle,java.lang.invoke.MethodHandle)", + "MethodHandles.permuteArguments(MethodHandle,MethodType,int...)": "java.base/java/lang/invoke/MethodHandles.html#permuteArguments(java.lang.invoke.MethodHandle,java.lang.invoke.MethodType,int...)", + "MethodHandles.insertArguments(MethodHandle,int,Object...)": "java.base/java/lang/invoke/MethodHandles.html#insertArguments(java.lang.invoke.MethodHandle,int,java.lang.Object...)", + "MethodHandles.filterArguments(MethodHandle,int,MethodHandle...)": "java.base/java/lang/invoke/MethodHandles.html#filterArguments(java.lang.invoke.MethodHandle,int,java.lang.invoke.MethodHandle...)", + "MethodHandles.foldArguments(MethodHandle,int,MethodHandle)": "java.base/java/lang/invoke/MethodHandles.html#foldArguments(java.lang.invoke.MethodHandle,int,java.lang.invoke.MethodHandle)", + "MethodHandles.reflectAs(Class,MethodHandle)": "java.base/java/lang/invoke/MethodHandles.html#reflectAs(java.lang.Class,java.lang.invoke.MethodHandle)", + "MethodHandles.arrayConstructor(Class)": "java.base/java/lang/invoke/MethodHandles.html#arrayConstructor(java.lang.Class)", + "MethodHandles.arrayElementSetter(Class)": "java.base/java/lang/invoke/MethodHandles.html#arrayElementSetter(java.lang.Class)", + "MethodHandles.arrayElementGetter(Class)": "java.base/java/lang/invoke/MethodHandles.html#arrayElementGetter(java.lang.Class)", + "MethodHandles.catchException(MethodHandle,Class,MethodHandle)": "java.base/java/lang/invoke/MethodHandles.html#catchException(java.lang.invoke.MethodHandle,java.lang.Class,java.lang.invoke.MethodHandle)", + "MethodHandles.arrayLength(Class)": "java.base/java/lang/invoke/MethodHandles.html#arrayLength(java.lang.Class)", + "MethodHandle.invoke(Object...)": "java.base/java/lang/invoke/MethodHandle.html#invoke(java.lang.Object...)", + "MethodHandle.invokeExact(Object...)": "java.base/java/lang/invoke/MethodHandle.html#invokeExact(java.lang.Object...)", + "MethodHandle.invokeWithArguments(Object...)": "java.base/java/lang/invoke/MethodHandle.html#invokeWithArguments(java.lang.Object...)", + "MethodHandle.asType(MethodType)": "java.base/java/lang/invoke/MethodHandle.html#asType(java.lang.invoke.MethodType)", + "VarHandle": "java.base/java/lang/invoke/VarHandle.html", + "WrongMethodTypeException": "java.base/java/lang/invoke/WrongMethodTypeException.html", + + "ReflectPermission": "java.base/java/lang/reflect/ReflectPermission.html", + + "java.lang.reflect": "java.base/java/lang/reflect/package-summary.html", + + "Class": "java.base/java/lang/Class.html", + "Class.getFields()": "java.base/java/lang/Class.html#getFields()", + "Class.getInterfaces()": "java.base/java/lang/Class.html#getInterfaces()", + "Class.getMethods()": "java.base/java/lang/Class.html#getMethods()", + "Class.getSimpleName()": "java.base/java/lang/Class.html#getSimpleName()", + "Class.getSuperclass()": "java.base/java/lang/Class.html#getSuperclass()", + "Class.isAnnotation()": "java.base/java/lang/Class.html#isAnnotation()", + "Class.isEnum()": "java.base/java/lang/Class.html#isEnum()", + "Class.isInterface()": "java.base/java/lang/Class.html#isInterface()", + "Class.toString()": "java.base/java/lang/Class.html#toString()", + "Class.forName(String)": "java.base/java/lang/Class.html#forName(java.lang.String)", + "Class.getName()": "java.base/java/lang/Class.html#getName()", + "Class.getCanonicalName()": "java.base/java/lang/Class.html#getCanonicalName()", + "Class.getTypeName()": "java.base/java/lang/Class.html#getTypeName()", + "Class.forPrimitiveName()": "java.base/java/lang/Class.html#forPrimitiveName()", + "Class.getClasses()": "java.base/java/lang/Class.html#getClasses()", + "Class.getEnclosingClass()": "java.base/java/lang/Class.html#getEnclosingClass()", + "Class.getDeclaredClasses()": "java.base/java/lang/Class.html#getDeclaredClasses()", + "Class.getDeclaringClass()": "java.base/java/lang/Class.html#getDeclaringClass()", + "Class.getModifiers()": "java.base/java/lang/Class.html#getModifiers()", + "Class.getDeclaredField(String)": "java.base/java/lang/Class.html#getDeclaredField(java.lang.String)", + "Class.getField(String)": "java.base/java/lang/Class.html#getField(java.lang.String)", + "Class.getDeclaredFields()": "java.base/java/lang/Class.html#getDeclaredFields()", + "Class.getDeclaredMethod(String,Class...)": "java.base/java/lang/Class.html#getDeclaredMethod(java.lang.String,java.lang.Class...)", + "Class.getMethod(String,Class...)": "java.base/java/lang/Class.html#getMethod(java.lang.String,java.lang.Class...)", + "Class.getDeclaredMethods()": "java.base/java/lang/Class.html#getDeclaredMethods()", + "Class.getDeclaredConstructor(Class...)": "java.base/java/lang/Class.html#getDeclaredConstructor(java.lang.Class...)", + "Class.getConstructor(Class...)": "java.base/java/lang/Class.html#getConstructor(java.lang.Class...)", + "Class.getDeclaredConstructors()": "java.base/java/lang/Class.html#getDeclaredConstructors()", + "Class.getConstructors()": "java.base/java/lang/Class.html#getConstructors()", + "Class.newInstance()": "java.base/java/lang/Class.html#newInstance()", + "Class.isRecord()": "java.base/java/lang/Class.html#isRecord()", + "Class.getEnumConstants()": "java.base/java/lang/Class.html#getEnumConstants()", + "Class.isArray()": "java.base/java/lang/Class.html#isArray()", + "Class.getComponentType()": "java.base/java/lang/Class.html#getComponentType()", + + "Void": "java.base/java/lang/Void.html", + "Void.TYPE": "java.base/java/lang/Void.html#TYPE", + + "Type": "java.base/java/lang/reflect/Type.html", + "Type.getTypeName()": "java.base/java/lang/reflect/Type.html#getTypeName()", + + "Member": "java.base/java/lang/reflect/Member.html", + "Member.accessFlags()": "java.base/java/lang/reflect/Member.html#accessFlags()", + "Member.getDeclaringClass()": "java.base/java/lang/reflect/Member.html#getDeclaringClass()", + + "Field": "java.base/java/lang/reflect/Field.html", + "Field.getFields()": "java.base/java/lang/reflect/Field.html#getFields()", + "Field.getType()": "java.base/java/lang/reflect/Field.html#getType()", + "Field.getGenericType()": "java.base/java/lang/reflect/Field.html#getGenericType()", + "Field.getModifiers()": "java.base/java/lang/reflect/Field.html#getModifiers()", + "Field.accessFlags()": "java.base/java/lang/reflect/Field.html#accessFlags()", + "Field.get(Object)": "java.base/java/lang/reflect/Field.html#get(java.lang.Object)", + "Field.set(Object,Object)": "java.base/java/lang/reflect/Field.html#set(java.lang.Object,java.lang.Object)", + "Field.setInt(Object,int)": "java.base/java/lang/reflect/Field.html#setInt(java.lang.Object,int)", + "Field.getInt(Object)": "java.base/java/lang/reflect/Field.html#getInt(java.lang.Object)", + "Field.setAccessible()": "java.base/java/lang/reflect/Field.html#setAccessible()", + "Field.isEnumConstant()": "java.base/java/lang/reflect/Field.html#isEnumConstant()", + + "Method": "java.base/java/lang/reflect/Method.html", + "Method.invoke(Object,Object...)": "java.base/java/lang/reflect/Method.html#invoke(java.lang.Object,java.lang.Object...)", + "Method.invoke()": "java.base/java/lang/reflect/Method.html#invoke()", + "Method.getReturnType()": "java.base/java/lang/reflect/Method.html#getReturnType()", + "Method.getGenericReturnType()": "java.base/java/lang/reflect/Method.html#getGenericReturnType()", + "Method.getGenericExceptionTypes()": "java.base/java/lang/reflect/Method.html#getGenericExceptionTypes()", + "Method.getModifiers()": "java.base/java/lang/reflect/Method.html#getModifiers()", + "Method.setAccessible(boolean)": "java.base/java/lang/reflect/Method.html#setAccessible(boolean)", + + "Constructor": "java.base/java/lang/reflect/Constructor.html", + "Constructor.newInstance()": "java.base/java/lang/reflect/Constructor.html#newInstance()", + "Constructor.getParameterTypes()": "java.base/java/lang/reflect/Constructor.html#getParameterTypes()", + "Constructor.getGenericParameterTypes()": "java.base/java/lang/reflect/Constructor.html#getGenericParameterTypes()", + + "RecordComponent": "java.base/java/lang/reflect/RecordComponent.html", + "RecordComponent.getDeclaringRecord()": "java.base/java/lang/reflect/RecordComponent.html#getDeclaringRecord()", + "RecordComponent.getAccessor()": "java.base/java/lang/reflect/RecordComponent.html#getAccessor()", + "RecordComponent.getName()": "java.base/java/lang/reflect/RecordComponent.html#getName()", + "RecordComponent.getType()": "java.base/java/lang/reflect/RecordComponent.html#getType()", + "RecordComponent.getGenericType()": "java.base/java/lang/reflect/RecordComponent.html#getGenericType()", + + "Array": "java.base/java/lang/reflect/Array.html", + "Array.newInstance()": "java.base/java/lang/reflect/Array.html#newInstance()", + "Array.newInstance(Class,int)": "java.base/java/lang/reflect/Array.html#newInstance(java.lang.Class,int)", + "Array.getLength(Object)": "java.base/java/lang/reflect/Array.html#getLength(java.lang.Object)", + "Array.set(Object,int,Object)": "java.base/java/lang/reflect/Array.html#set(java.lang.Object,int,java.lang.Object)", + "Array.get(Object,int)": "java.base/java/lang/reflect/Array.html#set(java.lang.Object,int)", + "Array.setDouble(Object,int,double)": "java.base/java/lang/reflect/Array.html#setDouble(java.lang.Object,int,double)", + "Array.getDouble(Object,int)": "java.base/java/lang/reflect/Array.html#getDouble(java.lang.Object,int)", + + "AnnotatedElement": "java.base/java/lang/reflect/AnnotatedElement.html", + "AnnotatedElement.getDeclaredAnnotations()": "java.base/java/lang/reflect/AnnotatedElement.html#getDeclaredAnnotations()", + "AnnotatedElement.getAnnotations()": "java.base/java/lang/reflect/AnnotatedElement.html#getAnnotations()", + "AnnotatedElement.isAnnotationPresent(Class)": "java.base/java/lang/reflect/AnnotatedElement.html#isAnnotationPresent(java.lang.Class)", + "AnnotatedElement.getAnnotation(Class)": "java.base/java/lang/reflect/AnnotatedElement.html#getAnnotation(java.lang.Class)", + "AnnotatedElement.getDeclaredAnnotation(Class)": "java.base/java/lang/reflect/AnnotatedElement.html#getDeclaredAnnotation(java.lang.Class)", + "AnnotatedElement.getAnnotationsByType(Class)": "java.base/java/lang/reflect/AnnotatedElement.html#getAnnotationsByType(java.lang.Class)", + + "AnnotatedType": "java.base/java/lang/reflect/AnnotatedType.html", + + "Proxy": "java.base/java/lang/reflect/Proxy.html", + + "Executable": "java.base/java/lang/reflect/Executable.html", + "Executable.getParameters()": "java.base/java/lang/reflect/Executable.html#getParameters()", + + "AccessFlag": "java.base/java/lang/reflect/AccessFlag.html", + + "Modifier": "java.base/java/lang/reflect/Modifier.html", + + "ClassNotFoundException": "java.base/java/lang/ClassNotFoundException.html", + "InstantiationException": "java.base/java/lang/InstantiationException.html", + "InvocationTargetException": "java.base/java/lang/reflect/InvocationTargetException.html", + "InvocationTargetException.getTargetException()": "java.base/java/lang/reflect/InvocationTargetException.html#getTargetException()", + + "jdk.jshell": "jdk.jshell/module-summary.html", + + "jdeps-man": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jdeps.html#options-to-filter-classes-to-be-analyzed", + "jlink-man": "https://docs.oracle.com/en/java/javase/@@CURRENT_RELEASE@@/docs/specs/man/jlink.html" } \ No newline at end of file diff --git a/app/data/javafxdoc.json b/app/data/javafxdoc.json new file mode 100644 index 0000000..dfc5f19 --- /dev/null +++ b/app/data/javafxdoc.json @@ -0,0 +1,80 @@ +{ + "current_release": "23", + "release_uuid": "343fae14109c42b09c0437fc90a10d4b", + + "javafxdoc_root": "https://download.java.net/java/GA/javafx@@CURRENT_RELEASE@@/@@RELEASE_UUID@@/docs/api/", + + "Application": "javafx.graphics/javafx/application/Application.html", + + "Scene": "javafx.graphics/javafx/scene/Scene.html", + + "Stage": "javafx.graphics/javafx/stage/Stage.html", + + "Group": "javafx.graphics/javafx/scene/Group.html", + + "AnchorPane": "javafx.graphics/javafx/scene/layout/AnchorPane.html", + "BorderPane": "javafx.graphics/javafx/scene/layout/BorderPane.html", + + "FlowPane": "javafx.graphics/javafx/scene/layout/FlowPane.html", + "GridPane": "javafx.graphics/javafx/scene/layout/GridPane.html", + "HBox": "javafx.graphics/javafx/scene/layout/HBox.html", + "StackPane": "javafx.graphics/javafx/scene/layout/StackPane.html", + "VBox": "javafx.graphics/javafx/scene/layout/VBox.html", + + "ImageView": "javafx.graphics/javafx/scene/image/ImageView.html", + + "Color": "javafx.graphics/javafx/scene/paint/Color.html", + "LinearGradient": "javafx.graphics/javafx/scene/paint/LinearGradient.html", + + "Ellipse": "javafx.graphics/javafx/scene/shape/Ellipse.html", + "Line": "javafx.graphics/javafx/scene/shape/Line.html", + "Path": "javafx.graphics/javafx/scene/shape/Path.html", + "Rectangle": "javafx.graphics/javafx/scene/shape/Rectangle.html", + "Shape": "javafx.graphics/javafx/scene/shape/Shape.html", + "Text": "javafx.graphics/javafx/scene/text/Text.html", + + + "Button": "javafx.controls/javafx/scene/control/Button.html", + "ButtonBar": "javafx.controls/javafx/scene/control/ButtonBar.html", + "ListView": "javafx.controls/javafx/scene/control/ListView.html", + "SplitPane": "javafx.controls/javafx/scene/control/SplitPane.html", + "TextField": "javafx.controls/javafx/scene/control/TextField.html", + "TextArea": "javafx.controls/javafx/scene/control/TextArea.html", + + "Animation.Status": "javafx.graphics/javafx/animation/Animation.Status.html", + "RotateTransition": "javafx.graphics/javafx/animation/RotateTransition.html", + "ParallelTransition": "javafx.graphics/javafx/animation/ParallelTransition.html", + "PauseTransition": "javafx.graphics/javafx/animation/PauseTransition.html", + "SequentialTransition": "javafx.graphics/javafx/animation/SequentialTransition.html", + "Transition": "javafx.graphics/javafx/animation/Transition.html", + + "When": "javafx.base/javafx/beans/binding/When.html", + "ObservableList": "javafx.base/javafx/collections/ObservableList.html", + "SortedList": "javafx.base/javafx/collections/transformation/SortedList.html", + + "FXMLLoader": "javafx.fxml/javafx/fxml/FXMLLoader.html", + "Initializable": "javafx.fxml/javafx/fxml/Initializable.html", + "AnimationPackageSummary": "javafx.graphics/javafx/animation/package-summary.html", + "WritableValue": "javafx.base/javafx/beans/value/WritableValue.html", + "Animation": "javafx.graphics/javafx/animation/Animation.html", + "Duration": "javafx.base/javafx/util/Duration.html", + "Node": "javafx.graphics/javafx/scene/Node.html", + "FadeTransition": "javafx.graphics/javafx/animation/FadeTransition.html", + "FillTransition": "javafx.graphics/javafx/animation/FillTransition.html", + "PathTransition": "javafx.graphics/javafx/animation/PathTransition.html", + "ScaleTransition": "javafx.graphics/javafx/animation/ScaleTransition.html", + "StrokeTransition": "javafx.graphics/javafx/animation/StrokeTransition.html", + "TranslateTransition": "javafx.graphics/javafx/animation/TranslateTransition.html", + "Timeline": "javafx.graphics/javafx/animation/Timeline.html", + "KeyFrame": "javafx.graphics/javafx/animation/KeyFrame.html", + "KeyValue": "javafx.graphics/javafx/animation/KeyValue.html", + "Interpolator": "javafx.graphics/javafx/animation/Interpolator.html", + "Interpolator.DISCRETE": "javafx.graphics/javafx/animation/Interpolator.html#DISCRETE", + "Interpolator.LINEAR": "javafx.graphics/javafx/animation/Interpolator.html#LINEAR", + "Interpolator.EASE_IN": "javafx.graphics/javafx/animation/Interpolator.html#EASE_IN", + "Interpolator.EASE_OUT": "javafx.graphics/javafx/animation/Interpolator.html#EASE_OUT", + "Interpolator.EASE_BOTH": "javafx.graphics/javafx/animation/Interpolator.html#EASE_BOTH", + "Interpolator.SPLINE": "javafx.graphics/javafx/animation/Interpolator.html#SPLINE(double,double,double,double)", + "Interpolator.TANGENT": "javafx.graphics/javafx/animation/Interpolator.html#TANGENT(javafx.util.Duration,double,javafx.util.Duration,double)", + "AnimationTimer": "javafx.graphics/javafx/animation/AnimationTimer.html" +} \ No newline at end of file diff --git a/app/data/jep.json b/app/data/jep.json new file mode 100644 index 0000000..80bcff7 --- /dev/null +++ b/app/data/jep.json @@ -0,0 +1,73 @@ +{ + "root": "https://openjdk.org/jeps/", + + "1": "JDK Enhancement-Proposal & Roadmap Process", + "11": "Incubator Modules", + "12": "Preview Features", + + "110": "HTTP/2 Client (Incubator)", + + "220": "Modular Run-Time Images", + "225": "Javadoc Search", + "286": "Local-Variable Type Inference", + + "310": "Application Class-Data Sharing", + "321": "HTTP Client", + "325": "Switch Expressions (Preview)", + "326": "Raw String Literals (Preview)", + "328": "Flight Recorder", + "333": "ZGC: A Scalable Low-Latency Garbage Collector (Experimental)", + "345": "NUMA-Aware Memory Allocation for G1", + "346": "Promptly Return Unused Committed Memory from G1", + "349": "JFR Event Streaming", + "354": "Switch Expressions (Second Preview)", + "357": "Migrate from Mercurial to Git", + "358": "Helpful NullPointerExceptions", + "361": "Switch Expressions", + "363": "Remove the Concurrent Mark Sweep (CMS) Garbage Collector", + "369": "Migrate to GitHub", + "372": "Remove the Nashorn JavaScript Engine", + "374": "Disable and Deprecate Biased Locking", + "376": "ZGC: Concurrent Thread-Stack Processing", + "377": "ZGC: A Scalable Low-Latency Garbage Collector (Production)", + "384": "Records (Second Preview)", + "387": "Elastic Metaspace", + "391": "macOS/AArch64 Port", + "395": "Records", + "397": "Sealed Classes (Second Preview)", + "398": "Deprecate the Applet for Removal", + + "400": "UTF-8 by Default", + "407": "Remove RMI Activation", + "408": "Simple Web Server", + "409": "Sealed Classes", + "411": "Deprecate the Security Manager for Removal", + "413": "Code Snippets in Java API Documentation", + "415": "Context-Specific Deserialization Filters", + "416": "Reimplement Core Reflection with Method Handle", + "418": "Internet-Address Resolution API", + "421": "Deprecate Finalization for Removal", + "445": { + "title": "Implicitly Declared Classes and Instance Main Methods (Second Preview)", + "status": "preview", + "version": "22" + }, + "460": { + "title": "Vector API (Seventh Incubator)", + "status": "incubator", + "version": "22" + }, + "463": { + "title": "Implicitly Declared Classes and Instance Main Methods (Second Preview)", + "status": "preview", + "version": "22" + }, + + "481": { + "title": "Scoped Values (Third Preview)", + "status": "preview", + "version": "22" + }, + + "8300604": "JEP draft: Preview Features: A Look Back, and A Look Ahead" +} \ No newline at end of file diff --git a/app/pages/community/jcs/index.md b/app/pages/community/jcs/index.md index b2b02b1..e1a5a52 100644 --- a/app/pages/community/jcs/index.md +++ b/app/pages/community/jcs/index.md @@ -7,9 +7,9 @@ subheader_select: jcs -The Java Champions Program consists of a unique group of skilled Java technologists and community leaders sponsored by Oracle. Java Champions come from a broad cross-section of the Java community. They are leaders, influencers and enablers who help grow the size of the Java community. Java Champions are active in many ways such as actively participating in Java projects, engaging with Java User Group communities, speaking at conferences, authoring content, teaching other developers, fostering inclusive participation and so much more. +The Java Champions Program consists of a unique group of skilled Java technologists and community leaders. Java Champions come from a broad cross-section of the Java community. They are leaders, influencers and enablers who help grow the size of the Java community. Java Champions are active in many ways such as actively participating in Java projects, engaging with Java User Group communities, speaking at conferences, authoring content, teaching other developers, fostering inclusive participation and so much more. -They are technical and community luminaries; some are Java engineers or architects who are relatively skilled and has lots of experience, or are community builders who take time to build personal, long-lasting relationships that go beyond programming. Java Champions are independent-minded and credible, who embody the best virtues of the Java community including honesty, dedication and sincerity. +They are technical and community luminaries; some are Java engineers or architects who are relatively skilled and have lots of experience, or are community builders who take time to build personal, long-lasting relationships that go beyond programming. Java Champions are independent-minded and credible, who embody the best virtues of the Java community including honesty, dedication and sincerity. Java Champions are also able to advocate or influence other developers through their own professional activities (via consulting, teaching, writing, speaking, etc.). They have the opportunity to provide independent, constructive feedback and ideas absent of a competitive agenda that will help Oracle continue to move Java forward. This interchange may be in the form of technical discussions and/or community-building activities with Oracle's Java Advocacy Team. diff --git a/app/pages/future/innovation/index.md b/app/pages/future/innovation/index.md index e86051e..8825b54 100644 --- a/app/pages/future/innovation/index.md +++ b/app/pages/future/innovation/index.md @@ -7,11 +7,11 @@ subheader_select: innovation ## Amber -The goal of Project Amber is to explore and incubate smaller, productivity-oriented Java language features that have been accepted as candidate JEPs under the [OpenJDK JEP process](https://openjdk.java.net/jeps/1). This Project is sponsored by the [Compiler Group](https://openjdk.java.net/groups/compiler/). +The goal of Project Amber is to explore and incubate smaller, productivity-oriented Java language features that have been accepted as candidate JEPs under [JDK Enhancement-Proposal & Roadmap Process](jep:1). This Project is sponsored by the [Compiler Group](https://openjdk.org/groups/compiler/). -Most Project Amber features go through at least one round of Preview before becoming an official part of Java SE. See [JEP 12](https://openjdk.java.net/jeps/12) for an explanation of the Preview process, and [our tutorial](id:new_features.using_preview) on how to use preview features. For a given feature, there are separate JEPs for each round of preview and for final standardization. +Most Project Amber features go through at least one round of Preview before becoming an official part of Java SE. See [Preview Features](jep:12) for an explanation of the Preview process, and [our tutorial](id:new_features.using_preview) on how to use preview features. For a given feature, there are separate JEPs for each round of preview and for final standardization. -Learn more at Project Amber's [Wiki](https://openjdk.java.net/projects/amber/), as well as Inside.java's [Amber page](https://inside.java/tag/amber). +Learn more at Project Amber's [Wiki](https://openjdk.org/projects/amber/), as well as Inside.java's [Amber page](https://inside.java/tag/amber). ## Loom @@ -23,7 +23,7 @@ Project Loom is to intended to explore, incubate and deliver Java VM features an This OpenJDK project is sponsored by the HotSpot Group. -Learn more at Project Loom's [Wiki](https://wiki.openjdk.java.net/display/loom/Main), as well as Inside.java's [Loom page](https://inside.java/tag/loom). +Learn more at Project Loom's [Wiki](https://wiki.openjdk.org/display/loom/Main), as well as Inside.java's [Loom page](https://inside.java/tag/loom). ## Panama @@ -43,7 +43,7 @@ To this end, Project Panama will include most or all of these components: * tooling or wrapper interposition for safety * exploratory work with difficult-to-integrate native libraries -Learn more at Project Panama's [Wiki](https://openjdk.java.net/projects/panama/), as well as Inside.java's [Panama page](https://inside.java/tag/panama). +Learn more at Project Panama's [Wiki](https://openjdk.org/projects/panama/), as well as Inside.java's [Panama page](https://inside.java/tag/panama). ## Valhalla @@ -58,7 +58,7 @@ The three main goals are: A number of people describe Valhalla recently as being "primarily about performance". While it is understandable why people might come to that conclusion -- many of the motivations for Valhalla are, in fact, rooted in performance considerations -- this characterization misses something very important. Yes, performance is an important part of the story -- but so are safety, abstraction, encapsulation, expressiveness, maintainability, and compatible library evolution. -Learn more at the Valhalla Project [Wiki](https://wiki.openjdk.java.net/display/valhalla/Main), as well as Inside.java's [Valhalla page](https://inside.java/tag/valhalla). +Learn more at the Valhalla Project [Wiki](https://wiki.openjdk.org/display/valhalla/Main), as well as Inside.java's [Valhalla page](https://inside.java/tag/valhalla). ## ZGC @@ -81,5 +81,5 @@ At a glance, ZGC is: At its core, ZGC is a concurrent garbage collector, meaning all heavy lifting work is done while Java threads continue to execute. This greatly limits the impact garbage collection will have on your application's response time. -Learn more at the ZGC [Wiki](https://wiki.openjdk.java.net/display/zgc/Main), as well as Inside.java's [GC page](https://inside.java/tag/gc). +Learn more at the ZGC [Wiki](https://wiki.openjdk.org/display/zgc/Main), as well as Inside.java's [GC page](https://inside.java/tag/gc). diff --git a/app/pages/learn/01_tutorial/01_your-first-java-app/01_getting-started-with-java.md b/app/pages/learn/01_tutorial/01_your-first-java-app/01_getting-started-with-java.md index e0c1c3d..a26a943 100644 --- a/app/pages/learn/01_tutorial/01_your-first-java-app/01_getting-started-with-java.md +++ b/app/pages/learn/01_tutorial/01_your-first-java-app/01_getting-started-with-java.md @@ -24,7 +24,6 @@ toc: - Going Further {going-further} description: "Downloading and setting up the JDK, writing your first Java class, and creating your first Java application." last_update: 2022-10-29 -author: ["JoséPaumard"] ---   diff --git a/app/pages/learn/01_tutorial/01_your-first-java-app/02_building-with-intellij-idea.md b/app/pages/learn/01_tutorial/01_your-first-java-app/02_building-with-intellij-idea.md new file mode 100644 index 0000000..18d4958 --- /dev/null +++ b/app/pages/learn/01_tutorial/01_your-first-java-app/02_building-with-intellij-idea.md @@ -0,0 +1,292 @@ +--- +id: first_app.intellij-idea +title: Building a Java application in IntelliJ IDEA +slug: learn/intellij-idea +type: tutorial +category: start +layout: learn/tutorial.html +subheader_select: tutorials +main_css_id: learn +more_learning: + youtube: + - H_XxH66lm3U + - 70_B2DyM8mU +toc: +- Overview {overview} +- Installing IntelliJ IDEA {install} +- Creating a new project {new} +- Writing and editing code {edit} +- Running your application {run} +- Testing {test} +- Debugging {debug} +- Refactoring code {refactor} +- Documenting code {document} +- Searching and navigating {navigation} +- Summary {summary} +description: "Learn how to code, run, test, debug and document a Java application in IntelliJ IDEA." +last_update: 2024-04-22 +author: ["MaritvanDijk"] +--- + +  +## Overview + +An IDE (Integrated Development Environment) allows you to quickly create applications by combining a source-code editor with the ability to compile and run your code, as well as integration with build, test and debug tools, version control systems, and so on. Finally, an IDE will let you search and navigate your codebase in ways your file system won’t. + +One of the [most widely used integrated development environments (IDEs)](https://www.jetbrains.com/lp/devecosystem-2023/java/#java_ide) for Java is IntelliJ IDEA. Its user-friendly interface, rich feature set, and vast ecosystem make it an ideal environment for beginners to learn and grow as developers. In this tutorial you’ll learn how to use some of its features to simplify your development process and accelerate your learning curve with Java programming. + +  +## Installing IntelliJ IDEA + +To install IntelliJ IDEA, download the version you want to use from the [IntelliJ IDEA website](https://www.jetbrains.com/idea/) and follow the instructions. +Note that IntelliJ IDEA is available in two editions: +- **_IntelliJ IDEA Community Edition_** - free and open-source. It provides all the basic features for Java development. +- **_IntelliJ IDEA Ultimate_** - commercial, distributed with a 30-day trial period. It provides additional tools and features for web and enterprise development. + +For this tutorial, you can download the Community Edition. For more information on installing IntelliJ IDEA on your OS, see [the documentation](https://www.jetbrains.com/help/idea/installation-guide.html#standalone). + +When you launch IntelliJ IDEA for the first time, you’ll see the **Welcome** screen. From here, you can create a new project, open an existing project, or get a project from a version control system (like GitHub). + +[![Welcome screen](/assets/images/intellij-idea/welcome-screen.png)](/assets/images/intellij-idea/welcome-screen.png) + +To start working with Java, you will need to install a JDK. You can do this yourself, as described in [Getting Started with Java](https://dev.java/learn/getting-started/#setting-up-jdk), or you can do so in IntelliJ IDEA when creating a new project, without having to switch from your IDE and other tools (such as your browser, file system, etc.) to download and configure a JDK. + +  +## Creating a new project + +We can create a new project from the **Welcome** screen, or we can go to **File | New | Project** in the main menu. + +[![New Project menu](/assets/images/intellij-idea/new-project-menu.png)](/assets/images/intellij-idea/new-project-menu.png) + +In the **New Project** wizard, make sure that **Java** is selected on the left-hand side, and give your project a name (for example, `java-demo`). +Next, we'll select a **Build system**. IntelliJ IDEA supports both Maven and Gradle; the most used build systems for Java. A build tool, like Maven or Gradle, helps you to build your project, and manage any dependencies (like additional libraries) that you want to use in your Java code. Using a build tool will also make it easier to share your application and build it on a different machine. If you don't want to use either, you can use the IntelliJ build system. In this tutorial, let’s create a Maven project. + +[![New Project](/assets/images/intellij-idea/new-project.png)](/assets/images/intellij-idea/new-project.png) + +To develop a Java application, we’ll need a JDK. If the necessary JDK is already defined in IntelliJ IDEA, select it from the **JDK** list. + +If the JDK is installed on your computer, but not defined in the IDE, select the option **Add JDK** from the list and specify the path to the JDK home directory (for example, `/Library/Java/JavaVirtualMachines/jdk-21.0.2.jdk`). + +If you don't have the necessary JDK on your computer, select **Download JDK**. In the **Download JDK** popup, specify the JDK vendor (for example, Oracle OpenJDK) and version, change the installation path if required, and click **Download**. + +[![Download JDK popup](/assets/images/intellij-idea/download-jdk-popup.png)](/assets/images/intellij-idea/download-jdk-popup.png) + +If you select **Add sample code**, a `Main` class which prints out “Hello World” will be added to your project. Leave it unchecked, so we can add our own later. + +Once you’re satisfied with your input in the **New Project** popup, click **Create**. + +IntelliJ IDEA will create a project for you, with a basic `pom.xml` and a default directory structure for a Maven project with the source folder defined. The `pom.xml` is a file that contains information about the project and configuration details used by Maven to build the project. For more information, see [the Maven documentation](https://maven.apache.org/guides/introduction/introduction-to-the-pom.html). + +[![Project](/assets/images/intellij-idea/project.png)](/assets/images/intellij-idea/project.png) + +We can see the project structure in the [Project tool window](https://www.jetbrains.com/help/idea/project-tool-window.html) on the left. The `.idea` folder contains your project configuration. The `src` folder contains your code. When you expand that folder, you'll see that IntelliJ IDEA has created a `main` folder for your Java code and a `test` folder for your tests. + +Let’s add some code by creating a new class. +* In the **Project** tool window on the left, select the directory `src/main/java`. +* To add a new Java class, right-click the **Project** tool window to open the context menu and select **New | Java class**. +* Name this class `HelloWorld`. + +[![New Java class](/assets/images/intellij-idea/new-java-class.png)](/assets/images/intellij-idea/new-java-class.png) + +--- +**IDE TIP** + +You can add a new Java file using the shortcut **⌘N** (on macOS) or **Alt+Insert** (on Windows/Linux). + +--- + +  +## Writing and editing code + +The entrypoint to execute your `HelloWorld` Java program is the main method. Add the main method by typing: +```java +public static void main(String[] args) { + +} +``` +I recommend that you type the code, rather than pasting, as that will help you get more familiar with the syntax. + +To keep things simple, let's make our program print `Hello World!` to the console and move the cursor to a new line, by adding `System.out.println("Hello World!");` to the method body: + +```java +public static void main(String[] args) { + System.out.println("Hello World!"); +} +``` + +As you start typing, you’ll notice that IntelliJ IDEA gives you [code completion](https://www.jetbrains.com/help/idea/auto-completing-code.html). It will help you complete the names of classes, methods, fields, and keywords, and other types of completion. Use the arrow keys to select the option you want from the list, and the **Return** (on macOS) or **Enter** (on Windows/linux) key to apply your selection. + +[![HelloWorld](/assets/images/intellij-idea/hello-world.gif)](/assets/images/intellij-idea/hello-world.gif) + +IntelliJ IDEA will show you if you’ve typed or selected something that doesn’t compile, or if it sees any other problems. If you press **Alt+Enter** it will offer options to fix the problem. You can use **F2** to move to the next problem, and **Shift+F2** to go to the previous problem. IntelliJ IDEA will help you make sure that your syntax is correct and your code can be compiled, by offering suggestions that are context-sensitive. + +[![Suggestions](/assets/images/intellij-idea/alt-enter.png)](/assets/images/intellij-idea/alt-enter.png) + +To speed up development, we can also [generate code](https://www.jetbrains.com/help/idea/generating-code.html). IntelliJ IDEA can generate constructors, getters and setters, `toString()`, `equals()` and `hashCode()` methods, and more. + +[![Generate code](/assets/images/intellij-idea/generate-code.png)](/assets/images/intellij-idea/generate-code.png) + +IntelliJ IDEA will manage the formatting of your code as you write it. If needed, you can explicitly reformat the code, using the shortcut **⌘⌥L** (on macOS) or **Ctrl+Alt+L** (on Windows/Linux). + +  +## Running your application + +A major benefit of using an IDE is that you can directly run your code without having to first manually compile it on the command line. + +You can run the `HelloWorld` application directly from the editor, by clicking the green Run button in the gutter near the class declaration, or using the shortcut **⌃⇧R** (on macOS) or **Ctrl+Shift+F10** (on Windows/Linux). + +Alternatively, we can run our application using the green Run button in the top right corner, or using the shortcut **⌃R** (on macOS) or **Ctrl+F10** (on Windows/Linux) to run the latest file. + +[![Run](/assets/images/intellij-idea/run.png)](/assets/images/intellij-idea/run.png) + +If we want to pass arguments to our application, we can do so in our [Run Configurations](https://www.jetbrains.com/help/idea/run-debug-configuration.html). + +To edit your run configurations, select the configuration in the run/debug configuration switcher, by clicking the down arrow next to the current configuration or the three dots to the right of the run configuration, and select **Edit Configurations**. + +[![Edit Configurations](/assets/images/intellij-idea/edit-configurations.png)](/assets/images/intellij-idea/edit-configurations.png) + +The popup **Run/Debug Configurations** appears, and there you can modify JVM options, add program arguments, and many more. + +[![Run / Debug Configuration](/assets/images/intellij-idea/run-config.png)](/assets/images/intellij-idea/run-config.png) + +  +## Testing + +Testing your code helps you to verify that the code does what you expect it to do. You can run your application and test it yourself, or add automated tests that can verify your code for you. Thinking about what to test and how, can help you to break a problem up into smaller pieces. This will help you get a better solution faster! + +For example, let's say we have a `Calculator` class containing the following method that calculates the average of a list of values, and we want to make sure the average is calculated correctly: +```java +public class Calculator { + + public static double average(int[] numbers) { + int sum = 0; + for (int number : numbers) { + sum += number; + } + double avg = (double) sum / numbers.length; + return avg; + } +} +``` +IntelliJ IDEA makes it easy to add tests to your code. You can navigate to the test for a particular class using the shortcut **⇧⌘T** on macOS or **Ctrl+Shift+T** on Windows/Linux. If no test class exists yet, IntelliJ IDEA will create one for you. This class will be created in the `src/test/java` directory. +We can select a **Testing library** in the **Create test** popup. + +[![Create test](/assets/images/intellij-idea/create-test.png)](/assets/images/intellij-idea/create-test.png) + +IntelliJ IDEA supports multiple testing libraries, including [JUnit 5](https://junit.org/junit5/), which is the [most used testing library for Java developers](https://www.jetbrains.com/lp/devecosystem-2023/java/#java_unittesting). If JUnit 5 is not part of your project yet, IntelliJ IDEA will note “JUnit5 library not found in the module.” Click **Fix** to have IntelliJ IDEA fix this for you. + +Note that the JUnit 5 dependency `junit-jupiter` is added to the `pom.xml` in the `` section. +To make sure the dependencies work correctly in your project, **Load Maven Changes** by clicking the popup in the top right corner, or using the shortcut **⇧⌘I** (on macOS) or **Ctrl+Shift+O** (on Windows/Linux). + +[![JUnit5 dependencies](/assets/images/intellij-idea/junit5-dependencies.png)](/assets/images/intellij-idea/junit5-dependencies.png) + +Go back to the test file to add tests. We can let IntelliJ IDEA help us generate our test for us. In the test class, we can use **Generate** (**⌘N** on macOS or **Alt+Insert** on Windows/Linux) and select **Test Method** to add a test. Give the test a name that explains the intended behavior, and add the relevant test code. + +For example, let's make sure that the method `average()` correctly calculates the average for an array of positive numbers, and returns `0` for an empty array. You might want to add additional tests for different scenarios, such as an array of negative numbers, a mix of positive and negative numbers, etc. +```java +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CalculatorTest { + + @Test + void shouldCalculateAverageOfPositiveNumbers() { + int[] numbers = {1, 2, 3, 4, 5}; + + double actualAverage = Calculator.average(numbers); + + double expectedAverage = 3.0; + assertEquals(expectedAverage, actualAverage); + } + + @Test + void shouldReturnZeroAverageForEmptyArray() { + int[] numbers = {}; + + double actualAverage = Calculator.average(numbers); + + double expectedAverage = 0; + assertEquals(expectedAverage, actualAverage); + } +} +``` +In our test class, we can select **Run All Tests** (**⌃⇧R** on macOS or **Ctrl+Shift+F10** on Windows/Linux). + +In our example, we see that the second test fails. We expected to get the value `0` as the average of an empty array, but got `NaN` (not a number) instead. Let's find out why, using the debugger. + +  +## Debugging + +We might want to see how our code runs, either to help us understand how it works and/or when we need to fix a bug or failing test, like the one above. We can run our code through the [debugger](https://www.jetbrains.com/help/idea/debugging-code.html) to see the state of our variables at different times, and the call stack - the order in which methods are called when the program executes. To do so, we must first add a [breakpoint](https://www.jetbrains.com/help/idea/using-breakpoints.html) to the code. + +To add a breakpoint, click the gutter at the line of code where you want execution to stop. Alternatively, place the caret at the line and press **⌘F8** (on macOS) or **Ctrl+F8** (on Windows/Linux). We can run our test or application using the **Debug** option; either by right-clicking the **Run** button in the gutter and selecting the **Debug** option from the list, or by selecting the **Debug** button at the top right. + +Execution will stop at the breakpoint, so we can investigate the state of our application. We can see the current values of variables and objects. We can evaluate an expression to see its current value and look at more details. We can even change the expressions to evaluate different results. We can continue execution by either stepping into a method to see what happens inside a called method (using the shortcut **F7**, or the corresponding button in the **Debug** tool window) or stepping over a line to go to the next line even if a method is called (using the shortcut **F8**, or the corresponding button in the **Debug** tool window), depending on what we’re interested in. Finally, we can resume the program to finish the execution of the test. + +Let's debug the failing test from the previous section. In the code, place a breakpoint on line 4. Run the failing test through the debugger. Step over the code until you get to line 8, and observe the values of the variables. When we get to line 8, select `sum / numbers.length`, right-click to open the context menu and select **Evaluate Expression**. Press **Enter** to evaluate the selected expression. We see that `sum / numbers.length` results in a `java.lang.ArithmeticException: / by zero`. The empty array has a length of `0` and Java does not allow dividing by zero. +When we evaluate `(double) sum / numbers.length` we get the result `NaN`. We expected `0`, so our test fails. + +[![Debugging](/assets/images/intellij-idea/debug.gif)](/assets/images/intellij-idea/debug.gif) + +As we didn't consider this scenario in our initial implementation, we can fix this by changing our method to return `0` when an empty array is given as input: +```java +public class Calculator { + + public static double average(int[] numbers) { + if (numbers.length == 0) { + return 0; + } + int sum = 0; + for (int number : numbers) { + sum += number; + } + double avg = (double) sum / numbers.length; + return avg; + } +} +``` +Now, when we run our tests, we see that they pass. + +For more information on debugging, see [Debugging in Java](https://dev.java/learn/debugging/). + +  +## Refactoring code + +While working with our code, we may want to make small improvements without changing the functionality. We can use [refactoring](https://www.jetbrains.com/help/idea/refactoring-source-code.html) to reshape the code. + +* We can rename classes, variables and methods using **Refactor | Rename** (**⇧F6** on macOS, or **Shift+F6** on Windows/Linux). +* We can inline variables (**⌘⌥N** on macOS, or **Ctrl+Alt+N** on Windows/Linux), or extract variables (**⌘⌥V** on macOS, or **Ctrl+Alt+V** on Windows/Linux) as needed. +* We can break long methods into smaller parts by extracting a method (**⌘⌥M** on macOS, or **Ctrl+Alt+M** on Windows/Linux) and giving it a meaningful name. + +All these options help us refactor our code to a more familiar style, or to use new idioms and language features. + +Pull up the refactoring menu to see what is possible, using the shortcut **⌃T** (on macOS) or **Ctrl+Alt+Shift+T** (on Windows/Linux). + +[![Refactoring](/assets/images/intellij-idea/refactor.gif)](/assets/images/intellij-idea/refactor.gif) + +  +## Documenting code + +We can add documentation to our code. IntelliJ IDEA provides completion for documentation comments, which is enabled by default. Type `/**` before a declaration and press **Enter**. IntelliJ IDEA auto-completes the documentation comment for you. + +IntelliJ IDEA provides a way for you to easily understand and read Javadoc comments by selecting _Reader Mode_. **Toggle Rendered View** in the editor using **^⌥Q** (on macOS) or **Ctrl+Alt+Q** (on Windows/Linux). Right-click the icon in the gutter to select **Render All Doc Comments** if you want all comments to show in reader mode. + +  +## Searching and navigating + +IntelliJ IDEA also helps us by providing ways to navigate around our codebase, for example, by going backwards and forwards between files, finding usages and declarations, finding interfaces and their implementations, viewing recently opened files and location, or even opening a window by name. + +One popular way to search is [Search Everywhere](https://www.jetbrains.com/help/idea/searching-everywhere.html) (using **Shift** twice). Search everywhere allows you to search your project files and directories, as well as your project settings and IntelliJ IDEA settings. + +[![Search everywhere](/assets/images/intellij-idea/search-everywhere.png)](/assets/images/intellij-idea/search-everywhere.png) + +Another popular way to search is [Find in Files](https://www.jetbrains.com/help/idea/finding-and-replacing-text-in-project.html#find_in_project). Open **Find in Files** from the main menu using **Edit | Find | Find in Files**, or by using the shortcut **⌘⇧F** (on macOS) or **Ctrl+Shift+F** (on Windows/Linux). You can narrow down the results from **In Project** to **Module**, **Directory**, or **Scope**. + +[![Find in Files](/assets/images/intellij-idea/find-in-files.png)](/assets/images/intellij-idea/find-in-files.png) + +  +## Summary + +In this article, we’ve seen how IntelliJ IDEA can help you with code suggestions and completion while writing code, running your application, adding tests and using the debugger to help figure out how code is run, refactoring code, and more. + +IntelliJ IDEA continues to improve and evolve, adding new features and offering new integration. You can sharpen your coding skills by taking a look at the [documentation](https://www.jetbrains.com/help/idea/getting-started.html), [blog](https://blog.jetbrains.com/idea/), [YouTube channel](https://www.youtube.com/intellijidea), or [guide](https://www.jetbrains.com/guide/java/). diff --git a/app/pages/learn/01_tutorial/01_your-first-java-app/03_building-with-eclipse.md b/app/pages/learn/01_tutorial/01_your-first-java-app/03_building-with-eclipse.md new file mode 100644 index 0000000..e510d68 --- /dev/null +++ b/app/pages/learn/01_tutorial/01_your-first-java-app/03_building-with-eclipse.md @@ -0,0 +1,326 @@ +--- +id: first_app.eclipse +title: Building a Java Application in the Eclipse IDE +slug: learn/eclipse +type: tutorial +category: start +layout: learn/tutorial.html +subheader_select: tutorials +main_css_id: learn +toc: +- Introduction and Installation {intro} +- Creating a Java Project {creating} +- Content Assist {content_assist} +- Running Your Program {run} +- Dealing With Compilation Errors and Warnings {errors} +- Debugging {debugging} +- Generating Code {generating} +- Refactoring {refactoring} +- Summary {summary} +description: "Installing and getting started with the Eclipse IDE for developing Java applications" +last_update: 2024-04-15 +author: ["DanielSchmid"] +--- +  +## Introduction and Installation + +The Eclipse IDE (or Eclipse for short) is a commonly used application that provides tooling that helps developers write, run and debug Java code. This article describes how to get started with Eclipse for developing Java applications. + +The easiest way to install Eclipse is to download and run the Eclipse installer from [this site](https://www.eclipse.org/downloads/packages/installer). This provides multiple options for packages to install. In most cases, `Eclipse IDE for Java Developers` is a good installation for Java development. + +[![Eclipse Installer](/assets/images/eclipse/install.png)](/assets/images/eclipse/install.png) + +After installing Eclipse, you can select a workspace. The workspace is the directory where most projects are located. + +[![Workspace selection](/assets/images/eclipse/workspace_selection.png)](/assets/images/eclipse/workspace_selection.png) + +Upon selecting a workspace, it will show a Welcome screen presenting you with mutliple options. For example, there is an option to start an interactive tutorial that shows you how to create a simple Hello-World application. + +[![Workspace selection](/assets/images/eclipse/welcome.png)](/assets/images/eclipse/welcome.png) + +This article will show you how to create Java projects manually so you can close this Welcome screen by clicking on the `Hide` button on the top right of the Welcome tab. + +  +## Creating a Java Project + +After installing Eclipse you should have an empty workspace. In order to create a new Java project, click on the `File` toolbar in the top left corner of the Eclipse window and select `New` > `Java Project`. + +[![File > New > Java Project](/assets/images/eclipse/file_create_project.png)](/assets/images/eclipse/file_create_project.png) + +This will then open up a dialog window that allows you to configure your project. You will need to enter a name next to `Project name:` at the top. For example, you can choose the name `HelloWorld`. In the `Module` section at the bottom, disable the option `Create module-info.java file`. You can configure a custom Java installation (commonly referred to as the *JDK* or Java Development Kit) in the `JRE` box. + +[![Java project creation dialog](/assets/images/eclipse/create_java_project.gif)](/assets/images/eclipse/create_java_project.gif) + +This creates a Java project that is shown on the left side of the Eclipse window. When expanding this project, there should be a folder named `src`. Java classes can be created inside this directory by right-clicking on it and selecting `New` > `Class`. + +[![New > Class](/assets/images/eclipse/create_class.png)](/assets/images/eclipse/create_class.png) + +This opens a dialog similar to the project creation dialog. It allows specifying various options about the class you want to create. For now, you will need to enter a class name like `HelloWorld`. If you want to, you can also configure a package which can be used to group multiple classes together. + +[![Java class creation dialog](/assets/images/eclipse/java_class_creation.png)](/assets/images/eclipse/java_class_creation.png) + +  +## Content Assist + +Eclipse can help you write Java code by automatically completing parts of it. When pressing the key combination `Ctrl`+`Space` (or `⌘`+`Space` on macOS or `Alt`+`/` on Chinese systems) while editing Java code, Eclipse automatically suggests ways to complete the code. These suggestions can be confirmed by pressing `Enter` or double-clicking on the suggestions. + +For example, typing `main` in a class followed by pressing `Ctrl`+`Space` suggests adding a main method. + +[![Content assist suggesting a main method](/assets/images/eclipse/content_assist_main.png)](/assets/images/eclipse/content_assist_main.png) + +Inside methods, Eclipse can suggest changing `sysout` to a `System.out.println();` statement. + +[![Content assist suggesting a System.out statement](/assets/images/eclipse/content_assist_sysout.png)](/assets/images/eclipse/content_assist_sysout.png) + +Furthermore, it can complete class and method names. + +[![Content assist completing the class name String](/assets/images/eclipse/content_assist_suggest_class.png)](/assets/images/eclipse/content_assist_suggest_class.png) + +[![Content assist completing the method String#length](/assets/images/eclipse/content_assist_suggest_method.png)](/assets/images/eclipse/content_assist_suggest_method.png) + + +  +## Running Your Program + +In order to run a Java application, you first need to have a class with a `main` method. You can right-click the class in the package explorer or right-click in the editor where you are writing the code for the class and select `Run as` > `Java application`. + +[![Run As > Java application in the editor](/assets/images/eclipse/run_as_editor.png)](/assets/images/eclipse/run_as_editor.png) + +[![Run As > Java application in the editor](/assets/images/eclipse/run_as_package_explorer.png)](/assets/images/eclipse/run_as_package_explorer.png) + +Alternatively, you can run the application using the Run [![Run button](/assets/images/eclipse/run_button.png)](/assets/images/eclipse/run_button.png) button in the toolbar. [![Run button in toolbar](/assets/images/eclipse/run_buttons_toolbar.png)](/assets/images/eclipse/run_buttons_toolbar.png) + +When running the program, Eclipse should show the output of the program in the `Console` view. + +[![Program with output in console](/assets/images/eclipse/console_output.png)](/assets/images/eclipse/console_output.png) + +  +## Dealing with Compilation Errors and Warnings + +When Eclipse detects a compilation error, the relevant lines are underlined in red. When hovering over the line with the error or the error icon to the left of the said line, Eclipse provides information about what went wrong and also suggests Quick Fixes which can fix the error. However, in many cases, there are multiple ways to get rid of the error. You need to carefully check whether the suggestions are actually matching what you want to do. After all, IDEs cannot predict your intent. + +[![Compilation error due to calling a non-existing method](/assets/images/eclipse/compilation_error.png)](/assets/images/eclipse/compilation_error.png) + +Furthermore, Eclipse shows a list of errors in the `Problems` view. If this view is not displayed, it can be shown using the menu `Window` > `Show View` > `Problems`. + +[![opening Problems view](/assets/images/eclipse/open_problems_view.png)](/assets/images/eclipse/open_problems_view.png) + +[![Problems view showing an error](/assets/images/eclipse/problems_view.png)](/assets/images/eclipse/problems_view.png) + +As with Errors, Eclipse can also detect code that compiles but likely contains some issues or is pointless. In this case, Eclipse will display a warning. + +[![Warning due to unused variable](/assets/images/eclipse/warning.png)](/assets/images/eclipse/warning.png) + +[![Problems view showing a warning](/assets/images/eclipse/problems_view_warning.png)](/assets/images/eclipse/problems_view_warning.png) + +  +## Debugging + +When a program doesn't do what you expect it to do, you might want to debug it. The process of debugging is explained in [this article](id:debugging). Eclipse provides a lot of functionality making it easy to debug Java applications. + +In order to debug an application, you need to set a breakpoint. When the program gets to executing the line with the breakpoint, it will temporarily stop ("suspend"), allow you to inspect its current state and step through the program. To set a breakpoint, you need to double-click on the area to the left of the line you want to suspend the program at. After doing that, a blue dot should appear there. + +[![A breakpoint next to source code](/assets/images/eclipse/breakpoint.png)](/assets/images/eclipse/breakpoint.png) + +When running a program normally, it will ignore all breakpoints. For debugging, you need to run the program in debug mode. This can be done by clicking on the green button with the bug icon [![The debug button](/assets/images/eclipse/debug_button.png)](/assets/images/eclipse/debug_button.png) next to the run button or using `Debug As` > `Java Application`. + +[![The debug button next to run buttons](/assets/images/eclipse/debug_button_in_toolbar.png)](/assets/images/eclipse/debug_button_in_toolbar.png) + +When the program execution gets to a breakpoint in debug mode, Eclipse will ask you to switch to the Debug perspective. This perspective gives you more information about the program you are currently debugging so you likely want to do this and click on the `Switch` button. + +[![Eclipse asking to switch to the Debug perspective](/assets/images/eclipse/debug_perspective_switch.png)](/assets/images/eclipse/debug_perspective_switch.png) + +Upon opening the debug perspective, you should still see your code in the middle. However, there should be one line with a green background next to the breakpoint. This indicates the next line the program would execute. On the right side, you should see a `Variables` view containing a list of variables and their current values. + +[![The debug perspective](/assets/images/eclipse/debug_perspective.png)](/assets/images/eclipse/debug_perspective.png) + +While the program is suspended, you can tell it how to continue executing using buttons in the toolbar at the top. +[![Buttons for controlling execution flows in the toolbar](/assets/images/eclipse/debug_toolbar_buttons.png)](/assets/images/eclipse/debug_toolbar_buttons.png) +You can execute one line using `Step Over` [![Step Over button](/assets/images/eclipse/debug_step_over.png)](/assets/images/eclipse/debug_step_over.png) (`F6`), go into a method using `Step Into` [![Step Into button](/assets/images/eclipse/debug_step_into.png)](/assets/images/eclipse/debug_step_into.png) (`F5`) or continue executing the program until the next breakpoint with `Resume` [![Resume button](/assets/images/eclipse/debug_resume.png)](/assets/images/eclipse/debug_resume.png) (`F8`). + +  +## Generating Code + +Sometimes you might need to write repetitive code that doesn't contain much business logic and can be generated using information from existing code. An example of this is getters/setters or `equals`/`hashCode`/`toString` methods which typically just need to access some fields. While it is often preferable to use [records](/learn/records), Eclipse allows comes with functionality to generate these pieces of repetitive code. + +In order to do this, you first need to create a class with some fields you want to generate these methods for. In this example, we will create a `Person` class that stores the first name, last name and age of a person. +```java +public class Person { + private String firstName; + private String lastName; + private int age; + //we want to generate code here + +} +``` + +When right-clicking in that class, there is an option called `Source` providing various ways to generate code. Here, we can select `Generate Getters and Setters...` in order to generate accessor methods for the fields in the `Person` class. + +[![Generate Getters and Setters](/assets/images/eclipse/context_generate_getters_setters.png)](/assets/images/eclipse/context_generate_getters_setters.png) + +This option should open up a new window allowing us to configure which fields we want to generate accessors for. In order to create accessors for all fields, use the `Select All` button. and click `Generate` on the bottom right. + +[![Generate Getters and Setters](/assets/images/eclipse/getter_setter_modal.png)](/assets/images/eclipse/getter_setter_modal.png) + +After doing this, the class should look as follows: +```java +public class Person { + private String firstName; + private String lastName; + private int age; + //we want to generate code here + public String getFirstName() { + return firstName; + } + public void setFirstName(String firstName) { + this.firstName = firstName; + } + public String getLastName() { + return lastName; + } + public void setLastName(String lastName) { + this.lastName = lastName; + } + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } + +} +``` + +Similarly, it is possible to generate the `hashCode` and `equals` methods using the menu `Source` > `Generate hashCode() and equals()...`. + +[![Generate hashCode and equals](/assets/images/eclipse/context_generate_hashcode_equals.png)](/assets/images/eclipse/generate_hashcode_equals.png) + +This also opens a window which allows to select the fields to include in the `hashCode` and `equals` methods. +[![Selecting fields to use in hashCode and equals](/assets/images/eclipse/hashcode_equals_modal.png)](/assets/images/eclipse/hashcode_equals_modal.png) + +After clicking `Generate`, Eclipse automatically adds these methods to the class. +```java +import java.util.Objects; + +public class Person { + private String firstName; + private String lastName; + private int age; + //we want to generate code here + public String getFirstName() { + return firstName; + } + public void setFirstName(String firstName) { + this.firstName = firstName; + } + public String getLastName() { + return lastName; + } + public void setLastName(String lastName) { + this.lastName = lastName; + } + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } + @Override + public int hashCode() { + return Objects.hash(age, firstName, lastName); + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Person other = (Person) obj; + return age == other.age && Objects.equals(firstName, other.firstName) + && Objects.equals(lastName, other.lastName); + } + +} +``` + +Another method that is often generated is `toString()` which returns a `String` representation of the object. +To generate that method, select `Generate toString()...` in the `Source` menu. + +[![Generate toString](/assets/images/eclipse/context_tostring.png)](/assets/images/eclipse/context_tostring.png) + +As before, this opens a window allowing to specify options on how exactly the code should be generated. + +[![Options for toString](/assets/images/eclipse/tostring_options.png)](/assets/images/eclipse/tostring_options.png) + +Using the `Generate` button, Eclipse generates the `toString` method as it did with the other methods before. +```java +import java.util.Objects; + +public class Person { + private String firstName; + private String lastName; + private int age; + //we want to generate code here + public String getFirstName() { + return firstName; + } + public void setFirstName(String firstName) { + this.firstName = firstName; + } + public String getLastName() { + return lastName; + } + public void setLastName(String lastName) { + this.lastName = lastName; + } + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } + @Override + public int hashCode() { + return Objects.hash(age, firstName, lastName); + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Person other = (Person) obj; + return age == other.age && Objects.equals(firstName, other.firstName) + && Objects.equals(lastName, other.lastName); + } + @Override + public String toString() { + return "Person [firstName=" + firstName + ", lastName=" + lastName + ", age=" + age + "]"; + } + +} +``` + + +  +## Refactoring + +When working on Java applications, it is often necessary to change existing code in various ways while preserving functionality. Eclipse supports developers doing that by providing various refactoring options. An example of that is renaming class, methods or fields. This can be done by clicking on a class, method or variable name, right-clicking and selecting `Refactor` > `Rename`. + +[![Rename context menu](/assets/images/eclipse/context_rename.png)](/assets/images/eclipse/context_rename.png) + +It is then possible to change to name to something different and confirming it using the `Enter` key. This also updates all references to the renamed element. + +[![Renaming a class name](/assets/images/eclipse/rename_box.png)](/assets/images/eclipse/rename_box.png) + +[![Renaming a class name](/assets/images/eclipse/rename_different_text.png)](/assets/images/eclipse/rename_different_text.png) + + +  +## Summary + +As you can see, the Eclipse IDE provides a lot of tools that help developers write Java applications. While this article shows some, Eclipse comes with many more features that can be especially useful when working on bigger applications. If you are interested in reading more, check out the [Java Development user guide](https://help.eclipse.org/latest/index.jsp?nav=%2F1). \ No newline at end of file diff --git a/app/pages/learn/01_tutorial/02_new-features/00_virtual-threads.md b/app/pages/learn/01_tutorial/02_new-features/00_virtual-threads.md new file mode 100644 index 0000000..bd5cfca --- /dev/null +++ b/app/pages/learn/01_tutorial/02_new-features/00_virtual-threads.md @@ -0,0 +1,376 @@ +--- +id: new_features.virtual_threads +title: Virtual Threads +slug: learn/new-features/virtual-threads +type: tutorial +category: awareness +category_order: 1 +layout: learn/tutorial.html +main_css_id: learn +subheader_select: tutorials +toc: + - Why Virtual Threads? {why} + - Creating Virtual Threads {creating} + - Thread API Changes {api-changes} + - Capturing Task Results {task-results} + - Rate Limiting {rate-limiting} + - Pinning {pinning} + - Thread Locals {thread-locals} + - Conclusion {conclusion} +description: "Virtual Threads: What, Why, and How?" +author: ["CayHorstmann"] +--- + +  +## Why Virtual Threads? + +When Java 1.0 was released in 1995, its API had about a hundred classes, among them `java.lang.Thread`. Java was the first mainstream programming language that directly supported concurrent programming. + +Since Java 1.2, each Java thread runs on a *platform thread* supplied by the underlying operating system. (Up to Java 1.1, on some platforms, all Java threads were executed by a single platform thread.) + +Platform threads have nontrivial costs. They require a few thousand CPU instructions to start, and they consume a few megabytes of memory. Server applications can serve so many concurrent requests that it becomes infeasible to have each of them execute on a separate platform thread. In a typical server application, these requests spend much of their time *blocking*, waiting for a result from a database or another service. + +The classic remedy for increasing throughput is a non-blocking API. Instead of waiting for a result, the programmer indicates which method should be called when the result has become available, and perhaps another method that is called in case of failure. This gets unpleasant quickly, as the callbacks nest ever more deeply. + +JEP 425 introduced *virtual threads* in Java 19. Many virtual threads run on a platform thread. Whenever a virtual thread blocks, it is *unmounted*, and the platform thread runs another virtual thread. (The name “virtual thread” is supposed to be reminiscent of virtual memory that is mapped to actual RAM.) Virtual threads became a preview feature in Java 20 (JEP 436) and are final in Java 21. + +With virtual threads, blocking is cheap. When a result is not immediately available, you simply block in a virtual thread. You use familiar programming structures—branches, loops, try blocks—instead of a pipeline of callbacks. + +Virtual threads are useful when the number of concurrent tasks is large, and the tasks mostly block on network I/O. They offer no benefit for CPU-intensive tasks. For such tasks, consider [parallel streams](https://dev.java/learn/api/streams/parallel-streams/) or [recursive fork-join tasks](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/RecursiveTask.html). + +  +## Creating Virtual Threads + +The factory method `Executors.newVirtualThreadPerTaskExecutor()` yields an `ExecutorService` that runs each task in a separate virtual thread. For example: + +```java +import java.util.concurrent.*; + +public class VirtualThreadDemo { + public static void main(String[] args) { + final int NTASKS = 100; + ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); + for (int i = 0; i < NTASKS; i++) { + service.submit(() -> { + long id = Thread.currentThread().threadId(); + LockSupport.parkNanos(1_000_000_000); + System.out.println(id); + }); + } + service.close(); + } +} +``` + +By the way, the code uses `LockSupport.parkNanos` instead of `Thread.sleep` so that we don't have to catch the pesky `InterruptedException`. + +Perhaps you are using a lower-level API that asks for a thread factory. To obtain a factory for virtual threads, use the new `Thread.Builder` class: + +```java +Thread.Builder builder = Thread.ofVirtual().name("request-", 1); +ThreadFactory factory = builder.factory(); +``` + +Now, calling `factory.newThread(myRunnable)` creates a new (unstarted) virtual thread. The `name` method configures the builder to set thread names `request-1`, `request-2`, and so on. + +You can also use a builder to create a single virtual thread: + +```java +Thread t = builder.unstarted(myRunnable); +``` + +Alternatively, if you want to start the thread right away: + +```java +Thread t = builder.started(myRunnable); +``` + +Finally, for a quick demo, there is a convenience method: + +```java +Thread t = Thread.startVirtualThread(myRunnable); +``` + +Note that only the first approach, with an executor service, works with result-bearing tasks (callables). + +  +## Thread API Changes + +After a series of experiments with different APIs, the designers of Java virtual threads decided to simply reuse the familiar `Thread` API. A virtual thread is an instance of `Thread`. Cancellation works the same way as for platform threads, by calling `interrupt`. As always, the thread code must check the “interrupted” flag or call a method that does. (Most blocking methods do.) + +There are a few differences. In particular, all virtual threads: + +* Are in a single thread group +* Have priority `NORM_PRIORITY` +* Are daemon threads + +There is no API for constructing a virtual thread with another thread group. Trying to call `setPriority` or `setDaemon` on a virtual thread has no effect. + +The static `Thread::getAllStackTraces` method returns a map of stack traces of all *platform* threads. Virtual threads are not included. + +A new `Thread::isVirtual` instance method tells whether a thread is virtual. + +Note that there is no way to find the platform thread on which a virtual thread executes. + +Java 19 has a couple of changes to the `Thread` API that have nothing to do with virtual threads: + +* There are now instance methods `join(Duration)` and `sleep(Duration)`. +* The non-final `getId` method is deprecated since someone might override it to return something other than the thread ID. Call the final `threadId` method instead. + +As of Java 20, the `stop`, `suspend`, and `resume` methods throw an `UnsupportedOperationException` for both platform and virtual threads. These methods have been deprecated since Java 1.2 and deprecated for removal since Java 18. + +  +## Capturing Task Results + +You often want to combine the results of multiple concurrent tasks: + +```java +Future f1 = service.submit(callable1); +Future f2 = service.submit(callable2); +result = combine(f1.get(), f2.get()); +``` + +Before virtual threads, you might have felt bad about the blocking `get` calls. But now blocking is cheap. Here is a sample program with a more concrete example: + +```java +import java.util.concurrent.*; +import java.net.*; +import java.net.http.*; + +public class VirtualThreadDemo { + public static void main(String[] args) throws InterruptedException, ExecutionException { + ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); + Future f1 = service.submit(() -> get("https://horstmann.com/random/adjective")); + Future f2 = service.submit(() -> get("https://horstmann.com/random/noun")); + String result = f1.get() + " " + f2.get(); + System.out.println(result); + service.close(); + } + + private static HttpClient client = HttpClient.newHttpClient(); + + public static String get(String url) { + try { + var request = HttpRequest.newBuilder().uri(new URI(url)).GET().build(); + return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); + } catch (Exception ex) { + var rex = new RuntimeException(); + rex.initCause(ex); + throw rex; + } + } +} +``` + +If you have a list of tasks with the same result type, you can use the `invokeAll` method and then call `get` on each `Future`: + +```java +List> callables = ...; +List results = new ArrayList<>(); +for (Future f : service.invokeAll(callables)) + results.add(f.get()); +``` + +Again, a more concrete sample program: + +```java +import java.util.*; +import java.util.concurrent.*; +import java.net.*; +import java.net.http.*; + +public class VirtualThreadDemo { + public static void main(String[] args) throws InterruptedException, ExecutionException { + ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); + List> callables = new ArrayList<>(); + final int ADJECTIVES = 4; + for (int i = 1; i <= ADJECTIVES; i++) + callables.add(() -> get("https://horstmann.com/random/adjective")); + callables.add(() -> get("https://horstmann.com/random/noun")); + List results = new ArrayList<>(); + for (Future f : service.invokeAll(callables)) + results.add(f.get()); + System.out.println(String.join(" ", results)); + service.close(); + } + + private static HttpClient client = HttpClient.newHttpClient(); + + public static String get(String url) { + try { + var request = HttpRequest.newBuilder().uri(new URI(url)).GET().build(); + return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); + } catch (Exception ex) { + var rex = new RuntimeException(); + rex.initCause(ex); + throw rex; + } + } +} +``` + +  +## Rate Limiting + +Virtual threads improve application throughput since you can have many more concurrent tasks than with platform threads. That can put pressure on the services that the tasks invoke. For example, a web service may not tolerate huge numbers of concurrent requests. + +With platform threads, an easy (if crude) tuning factor is the size of the thread pool for those tasks. But you should not pool virtual threads. Scheduling tasks on virtual threads that are then scheduled on platform threads is clearly inefficient. And what is the upside? To limit the number virtual threads to the smallish number of concurrent requests that your service tolerates? Then why are you using virtual threads in the first place? + +With virtual threads, you should use alternative mechanisms for controlling access to limited resources. Instead of an overall limit on concurrent tasks, protect each resource in an appropriate way. For database connections, the connection pool may already do the right thing. When accessing a web service, you know your service, and can provide appropriate rate limiting. + +As an example, on my personal web site, I provide demo services for producing random items. If a large number of requests comes at an instant from the same IP address, the hosting company blacklists the IP address. + +The following sample program shows rate limiting with a simple semaphore that allows a small number of concurrent requests. When the maximum is exceeded, the `acquire` method blocks, but that is ok. With virtual threads, blocking is cheap. + +```java +import java.util.*; +import java.util.concurrent.*; +import java.net.*; +import java.net.http.*; + +public class RateLimitDemo { + public static void main(String[] args) throws InterruptedException, ExecutionException { + ExecutorService service = Executors.newVirtualThreadPerTaskExecutor(); + List> futures = new ArrayList<>(); + final int TASKS = 250; + for (int i = 1; i <= TASKS; i++) + futures.add(service.submit(() -> get("https://horstmann.com/random/word"))); + for (Future f : futures) + System.out.print(f.get() + " "); + System.out.println(); + service.close(); + } + + private static HttpClient client = HttpClient.newHttpClient(); + + private static final Semaphore SEMAPHORE = new Semaphore(20); + + public static String get(String url) { + try { + var request = HttpRequest.newBuilder().uri(new URI(url)).GET().build(); + SEMAPHORE.acquire(); + try { + Thread.sleep(100); + return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); + } finally { + SEMAPHORE.release(); + } + } catch (Exception ex) { + ex.printStackTrace(); + var rex = new RuntimeException(); + rex.initCause(ex); + throw rex; + } + } +} +``` + +  +## Pinning + +The virtual thread scheduler mounts virtual threads onto carrier threads. By default, there are as many carrier threads as there are CPU cores. You can tune that count with the `jdk.virtualThreadScheduler.parallelism` VM option. + +When a virtual thread executes a blocking operation, it is supposed to be unmounted from its its carrier thread, which can then execute a different virtual thread. However, there are situations where this unmounting is not possible. In some situations, the virtual thread scheduler will compensate by starting another carrier thread. For example, in JDK 21, this happens for many file I/O operations, and when calling `Object.wait`. You can control the maximum number of carrier threads with the `jdk.virtualThreadScheduler.maxPoolSize` VM option. + +A thread is called *pinned* in either of the two following situations: + +1. When executing a `synchronized` method or block +2. When calling a native method or foreign function + +Being pinned is not bad in itself. But when a pinned thread blocks, it cannot be unmounted. The carrier thread is blocked, and, in Java 21, no additional carrier thread is started. That leaves fewer carrier threads for running virtual threads. + +Pinning is harmless if `synchronized` is used to avoid a race condition in an in-memory operation. However, if there are blocking calls, it would be best to replace `synchronized` with a `ReentrantLock`. This is of course only an option if you have control over the source code. + +To find out whether pinned threads are blocked, start the JVM with one of the options + +```shell +-Djdk.tracePinnedThreads=short +-Djdk.tracePinnedThreads=full +``` + +You get a stack trace that shows when a pinned thread blocks: + +```shell +... +org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) <== monitors:1 +... +``` + +Note that you get only one warning per pinning location! + +Alternatively, record with Java Flight Recorder, view with your favorite mission control viewer, and look for `VirtualThreadPinned` and `VirtualThreadSubmitFailed` events. + +The JVM will eventually be implemented so that `synchronized` methods or blocks no longer lead to pinning. Then you only need to worry about pinning for native code. + +The following sample program shows pinning in action. We launch a number of virtual threads that sleep in a synchronized method, blocking their carrier threads. A number of virtual threads are added that do no work at all. But they can't be scheduled because the carrier thread pool has been completely exhausted. Note that the problem goes away when you + +* use a `ReentrantLock` +* don't use virtual threads + +```java +import java.util.concurrent.*; +import java.util.concurrent.locks.*; + +public class PinningDemo { + public static void main(String[] args) throws InterruptedException, ExecutionException { + ExecutorService service = + Executors.newVirtualThreadPerTaskExecutor(); + // Executors.newCachedThreadPool(); + + final int TASKS = 20; + long start = System.nanoTime(); + for (int i = 1; i <= TASKS; i++) { + service.submit(() -> block()); + // service.submit(() -> rblock()); + } + for (int i = 1; i <= TASKS; i++) { + service.submit(() -> noblock()); + } + service.close(); + long end = System.nanoTime(); + System.out.printf("%.2f%n", (end - start) * 1E-9); + } + + public static synchronized void block() { + System.out.println("Entering block " + Thread.currentThread()); + LockSupport.parkNanos(1_000_000_000); + System.out.println("Exiting block " + Thread.currentThread()); + } + private static Lock lock = new ReentrantLock(); + public static void rblock() { + lock.lock(); + try { + System.out.println("Entering rblock " + Thread.currentThread()); + LockSupport.parkNanos(1_000_000_000); + System.out.println("Exiting rblock " + Thread.currentThread()); + } finally { + lock.unlock(); + } + } + public static void noblock() { + System.out.println("Entering noblock " + Thread.currentThread()); + LockSupport.parkNanos(1_000_000_000); + System.out.println("Exiting noblock " + Thread.currentThread()); + } +} +``` + +  +## Thread Locals + +A *thread-local variable* is an object whose `get` and `set` methods access a value that depends on the current thread. Why would you want such a thing instead of using a global or local variable? The classic application is a service that is not threadsafe, such as `SimpleDateFormat`, or that would suffer from contention, such as a random number generator. Per-thread instances can perform better than a global instance that is protected by a lock. + +Another common use for thread locals is to provide “implicit” context, such as a database connection, that is properly configured for each task. Instead of passing the context from one method to another, the task code simply reads the thread-local variable whenever it needs to access the database. + +Thread locals can be a problem when migrating to virtual threads. There will likely be far more virtual threads than threads in a thread pool, and now you have many more thread-local instances. In such a situation, you should rethink your sharing strategy. + +To locate uses of thread locals in your app, run with the VM flag `jdk.traceVirtualThreadLocals`. You will get a stack trace when a virtual thread mutates a thread-local variable. + +  +## Conclusion + +* Use virtual threads to increase throughput when you have many tasks that mostly block on network I/O +* The primary benefit is the familiar “synchronous” programming style, without callbacks +* Don't pool virtual threads; use other mechanisms for rate limiting +* Check for pinning and mitigate if necessary +* Minimize thread-local variables in virtual threads diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/02_basics/01_creating-variables.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/02_basics/01_creating-variables.md index 7557b5b..f6416c7 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/02_basics/01_creating-variables.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/02_basics/01_creating-variables.md @@ -13,7 +13,6 @@ toc: - Variables {creating} - Naming Variables {naming} description: "Rules to name variables." -author: ["ChadArimura"] last_update: 2021-09-23 --- @@ -45,6 +44,6 @@ Having said that, the remainder of this tutorial uses the following general guid Every programming language has its own set of rules and conventions for the kinds of names that you're allowed to use, and the Java programming language is no different. The rules and conventions for naming your variables can be summarized as follows: -- Variable names are case-sensitive. A variable's name can be any legal identifier — an unlimited-length sequence of Unicode letters and digits, beginning with a letter, the dollar sign "$", or the underscore character "_". The convention, however, is to always begin your variable names with a letter, not "$" or "_". Additionally, the dollar sign character, by convention, is never used at all. You may find some situations where auto-generated names will contain the dollar sign, but your variable names should always avoid using it. A similar convention exists for the underscore character; while it's technically legal to begin your variable's name with "_", this practice is discouraged. White space is not permitted. +- Variable names are case-sensitive. A variable's name can be any legal identifier — an unlimited-length sequence of Unicode letters and digits, beginning with a letter, the dollar sign `$`, or the underscore character `_`. The convention, however, is to always begin your variable names with a letter, not `$` or `_`. Additionally, the dollar sign character, by convention, is never used at all. You may find some situations where auto-generated names will contain the dollar sign, but your variable names should always avoid using it. A similar convention exists for the underscore character; while it's technically legal to begin your variable's name with `_`, this practice is discouraged. White space is not permitted. - Subsequent characters may be letters, digits, dollar signs, or underscore characters. Conventions (and common sense) apply to this rule as well. When choosing a name for your variables, use full words instead of cryptic abbreviations. Doing so will make your code easier to read and understand. In many cases it will also make your code self-documenting; fields named cadence, speed, and gear, for example, are much more intuitive than abbreviated versions, such as s, c, and g. Also keep in mind that the name you choose must not be a keyword or reserved word. - If the name you choose consists of only one word, spell that word in all lowercase letters. If it consists of more than one word, capitalize the first letter of each subsequent word. The names `gearRatio` and `currentGear` are prime examples of this convention. If your variable stores a constant value, such as static final int NUM_GEARS = 6, the convention changes slightly, capitalizing every letter and separating subsequent words with the underscore character. By convention, the underscore character is never used elsewhere. diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/00_refactoring.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/00_refactoring.md index 6563b3a..c6e4be4 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/00_refactoring.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/00_refactoring.md @@ -20,4 +20,8 @@ In this series we cover the following conversions from the imperative to the fun | Tutorial |Imperative Style | Functional Style Equivalent | |------------------------------------------------------|-----------------|------------------------------| | [Converting Simple Loops](id:refactoring.simple.loops) | `for()` | `range()` or `rangeClosed()` | +| [Converting Loops with Steps](id:refactoring.loops.withsteps) | `for(...i = i + ...)` | `iterate()` with `takeWhile()` | +| [Converting foreach with if](id:refactoring.foreach.withif) | `foreach(...) { if... }` | `stream()` with `filter()` | +| [Converting Iteration with transformation](id:refactoring.iteration.withtransformation) | `foreach(...) { ...transformation... }` | `stream()` with `map()` | +| [Converting to Streams](id:refactoring.to.streams) | `File read operation` | `Files.lines()` | diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/01_converting_simple_loops.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/01_converting_simple_loops.md index 604334f..3988370 100644 --- a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/01_converting_simple_loops.md +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/01_converting_simple_loops.md @@ -23,7 +23,7 @@ The older versions of Java supported the Object-Oriented paradigm mixed with the Imperative style is where we tell what to do and also how to do it. Functional style is declarative in nature, where we tell what to do and delegate the how or the details to the underlying libraries. Imperative style code may be easier to write since most of us are very familiar with it. However, the code becomes verbose, complex, and hard to read. Functional style may be hard at first, mainly because most programmers are less familiar with it. In general, it's easier to read, understand, and change. With practice, it becomes easier to write as well. -In this tutorial series we will take a look at a number of common imperative style code and find a mapping or an equivalent functional style code that we can use instead. As you work with your code based, when you're ready to fix a bug or make an enhancement, you may find it useful to refactor some of the imperative style code to the functional style. You can use this tutorial as a guide to find the imperative to functional style mappings for some common situations. +In this [tutorial series](id:refactoring) we will take a look at a number of common imperative style code and find a mapping or an equivalent functional style code that we can use instead. As you work with your code based, when you're ready to fix a bug or make an enhancement, you may find it useful to refactor some of the imperative style code to the functional style. You can use this tutorial as a guide to find the imperative to functional style mappings for some common situations. In this tutorial we'll focus on simple loops. diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/02_converting_loops_with_steps.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/02_converting_loops_with_steps.md new file mode 100644 index 0000000..687c068 --- /dev/null +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/02_converting_loops_with_steps.md @@ -0,0 +1,97 @@ +--- +id: refactoring.loops.withsteps +title: Converting Loops with Steps +slug: learn/refactoring-to-functional-style/loopswithsteps +type: tutorial-group +group: refactoring-to-functional-style +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Iterating with Steps {steps} +- From Imperative to Functional Style {imperativetofunctional} +- Unbounded Iteration with a break {break} +- Mappings {mappings} +description: "Converting Imperative Loops with Steps to Functional Style." +last_update: 2023-07-06 +author: ["VenkatSubramaniam"] +--- + +  +## Iterating with Steps + +In the previous article in this [tutorial series](id:refactoring) we looked at converting simple loops written in the imperative style to the functional style. In this article we'll see how to take on loops that are a bit more complex—when we have to step over some values in an interval. + +When looping over a range of values, one at a time, the `range()` method of `IntStream` came in handy to implement in the functional style. This method returns a stream that will generate one value at a time for values within the specified range. At first thought, to skip some values we may be tempted to use the `filter()` method on the stream. However, there's a simpler solution, the `iterate()` method of `IntStream`. + +  +## From Imperative to Functional Style + +Here's a loop that uses step to skip a few values in the desired range: + +```java +for(int i = 0; i < 15; i = i + 3) { + System.out.println(i); +} +``` + +The value of the index variable `i` starts at `0` and then is incremented by `3` as the iteration moves forward. When you find yourself looking at a loop like that where the iteration is not over every single value in a range, but some values are skipped, consider using the `iterate()` method of `IntStream`. + +Before we refactor the code, let's take a closer look at the `for()` loop in the previous code, but with a pair of imaginary glasses that let us look at potential uses for lambdas. + +```java +//imaginary code +for(int i = 0; i < 15; i = i + 3) //imperative +for(seed, i -> i < 15, i -> i + 3) //functional +``` + +The first argument passed to the `for` loop is the starting value or the seed for the iteration and it can stay as is. The second argument is a predicate that tells the value of the index variable, `i`, should not exceed the value of `15`. We can replace that in the functional style with a `IntPredicate`. The third argument is incrementing the value of the index variable and that, in functional style, is simply a `IntUnaryOperator`. The `IntStream` interface has a `static` method named `iterate()` that nicely represents the imaginary code: `iterate(int seed, IntPredicate hasNext, IntUnaryOperator next)`. + +Let's refactor the loop to use functional style. + +```java +import java.util.stream.IntStream; + +... +IntStream.iterate(0, i -> i < 15, i -> i + 3) + .forEach(System.out::println); +``` + +That was pretty straightforward, the `;`s became `,`s, we made use of two lambdas: one for the `IntPredicate` and the other for the `IntUnaryOperator`. + +In addition to stepping over values, we often use an unbounded loop and that throws a bit more complexity on us, but nothing the functional APIs of Java can't handle, as we'll see next. + +  +## Unbounded Iteration with a break + +Let's take a look at the following imperative style loop which, in addition to the step, is unbounded and uses the `break` statement. + +```java +for(int i = 0;; i = i + 3) { + if(i > 20) { + break; + } + + System.out.println(i); +} +``` + +The terminating condition of `i < 15` is gone and the loop is unbounded as indicated by the repeated `;;`s. Within the loop, however, we have the `break` statement to exit out of the iteration if the value of `i` is greater than `20`. + +For the functional style, we can get rid of the second argument, the `IntPredicate` from the `iterate()` method call but that will turn the iteration into an infinite stream. The functional programming equivalent of the imperative style `break` is the `takeWhile()` method. This method will terminate the internal iterator, the stream, if the `IntPredicate` passed to it evaluates to `false`. Let's refactor the previous imperative style unbounded `for` with `break` to functional style. + +```java +IntStream.iterate(0, i -> i + 3) + .takeWhile(i -> i <= 20) + .forEach(System.out::println); +``` + +The `iterate()` method is overloaded and comes in two flavors, one with the `IntPredicate` and the other without. We made use of the version without the predicate to create an infinite stream that generates values from the seed or the starting value. The `IntUnaryOperator` passed as the second argument determines the steps. Thus, in the given code example, the stream will generate values `0`, `3`, `6`, and so on. Since we want to limit the iteration so that the index does not exceed the value of `20` we use the `takeWhile()`. The predicate passed in to `takeWhile()` tells that the iteration may continue as long as the value of the parameter given, the index `i`, does not exceed the value of `20`. + +We saw in the previous article that `range()` and `rangeClosed()` are direct replacement for the simple `for` loop. If the loop gets a bit more complex, no worries, Java has you covered, you can use the `IntStream`'s `iterate()` method and optionally the `takeWhile()` if the loop is terminated using `break`. + +  +## Mappings + +Anywhere you see a `for` loop with step, use the `iterate()` method with three arguments, a seed or the starting value, a `IntPredicate` for the terminating condition, and a `IntUnaryOperator` for the steps. If your loop uses the `break` statement, then drop the `IntPredicate` from the `iterate()` method call and instead use the `takeWhile()` method. The `takeWhile()` is the functional equivalent of the imperative style `break`. + diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/03_converting_foreach_with_if.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/03_converting_foreach_with_if.md new file mode 100644 index 0000000..3dcff3d --- /dev/null +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/03_converting_foreach_with_if.md @@ -0,0 +1,97 @@ +--- +id: refactoring.foreach.withif +title: Converting foreach with if +slug: learn/refactoring-to-functional-style/foreachwithif +type: tutorial-group +group: refactoring-to-functional-style +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Iterating with foreach {foreach} +- From Imperative to Functional Style {imperativetofunctional} +- Picking elements with if {picking} +- Mappings {mappings} +description: "Converting Imperative Iteration using foreach with if to Functional Style." +last_update: 2023-07-06 +author: ["VenkatSubramaniam"] +--- + +  +## Iterating with foreach + +In the previous articles in this [tutorial series](id:refactoring) we looked at converting loops written in the imperative style to the functional style. In this article we'll see how to convert an imperative style iteration using `foreach` to the functional style. In addition, we'll also see how to pick select elements using `if` transforms to the functional style. + +Java 5 introduced the very popular `foreach` syntax. For example, to iterate over a collection of `String`s representing names, we'd write something like `for(String name: names)`. Under the hood, the `foreach` is converted, at the bytecode level, to use an `Iterator`—while the iterator tells us there is another element, fetch the next element for processing. In other words, the `foreach` is a nice concise syntax sugar for iteration with a `while` loop over the elements provided by an `Iterator`. We can convert a `foreach` into the functional style quite easily. Let's see how. + +  +## From Imperative to Functional Style + +Here's an example of iteration, using the `foreach`, over a collection of names: + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +for(String name: names) { + System.out.println(name); +} +``` + +Each step through the iteration, the `name` variable is bound to a new value, as the iteration advances from one element to the next in the given collection. Converting the imperative style `foreach` to the functional style is a straight up use of the `forEach` internal iterator method. It is called an internal iterator because the advancing to the next element is handled internally and automatically instead of externally or explicitly. + +Let's refactor the loop to use functional style. + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +names.forEach(name -> System.out.println(name)); +``` + +That was pretty straightforward, the `for` loop turned into a call to the `forEach()` method on the collection. Each step through the iteration, the lambda provided to the `forEach()` as an argument is invoked with the next element in the collection. + +A slightly variation of this iteration, using `stream()`, is shown next. + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +names.stream() + .forEach(name -> System.out.println(name)); +``` +The `forEach()` method is available both on a `Collection` and on a `Stream`. Functions like `filter()`, which we'll use soon, are available only on a `Stream` and not on the `Collection`. This is by design in order to provide efficiency when multiple intermediate operations may precede the terminal operation like `forEach()`, `findFirst()`, etc. + +  +## Picking select elements with if + +Suppose, in the middle of the iteration, we want to pick some values from the collection based on some condition. For example, what if we want to print only names of length 4? In the imperative style we could do the following: + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +for(String name: names) { + if(name.length() == 4) { + System.out.println(name); + } +} +``` + +For the functional style, the `filter` method of `Stream` becomes a direct replacement of the imperative style `if`. The `filter` method will allow an element in the collection to pass through to the next stage in the functional pipeline if the predicate, passed in as a lambda, to the `filter()` method evaluates to `true`; otherwise, the value is discarded from further processing. + +Let's convert the previous code to functional style: + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +names.stream() + .filter(name -> name.length() == 4) + .forEach(name -> System.out.println(name)); +``` + +The `filter()` method acts like a gate, it opens to let some elements pass through and closes to reject or discard some elements, as the iteration moves forward. + +We saw in the previous articles the functional style equivalent for the traditional `for` loops. In this article we saw how the imperative style `foreach` of Java 5 transforms into an elegant syntax in the functional style. Furthermore, the `if` condition within a loop of the imperative style translates to a call to the `filter()` method of the `Stream` API. + +  +## Mappings + +Anywhere you see a `foreach` loop, use the `forEach()` method directly on the collection. If the body of the `foreach` has a `if` statement to selectively pick some value, then use the `stream()` API with the call to the `filter()` method. + diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/04_converting_foreach_with_transformation.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/04_converting_foreach_with_transformation.md new file mode 100644 index 0000000..844cc4b --- /dev/null +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/04_converting_foreach_with_transformation.md @@ -0,0 +1,116 @@ +--- +id: refactoring.iteration.withtransformation +title: Converting Iteration with transformation +slug: learn/refactoring-to-functional-style/iteartionwithtransformation +type: tutorial-group +group: refactoring-to-functional-style +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Transforming while Iterating {transforming} +- From Imperative to Functional Style {imperativetofunctional} +- Picking Elements to Transform {picking} +- Mappings {mappings} +description: "Converting Imperative Iteration with transformation to Functional Style." +last_update: 2024-01-08 +author: ["VenkatSubramaniam"] +--- + +  +## Transforming while Iterating + +In the previous articles in this [tutorial series](id:refactoring) we looked at converting loops with `if` or conditional statements in the imperative style to the functional style. In this article we'll see how to convert an imperative style iteration that transforms data to the functional style. In addition, we'll also refactor code that mixes transforming data with code that picks select elements before the transformation. + +Anytime we are transforming data in an imperative style loop, we can use the `map()` function in the functional style. Let's see how. + + +  +## From Imperative to Functional Style + +Here's an example of an iteration, using the `foreach`, that transforms to uppercase a collection of names: + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +for(String name: names) { + System.out.println(name.toUpperCase()); +} +``` + +Each step through the iteration, the `name` variable is bound to a new value. As the iteration advances from one element to the next in the given collection, each name is transformed to uppercase using the `toUpperCase()` function and the resulting value is printed. We have already seen, in the previous article, how to convert the imperative style `foreach` to the functional style—using the `stream()` internal iteration. If we merely apply what we have seen before, the resulting functional style code would be rather unwieldy, with the lambda passed to `forEach` performing both transformation and printing, like so: + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +names.forEach(name -> System.out.println(name.toUpperCase())); //Don't do this +``` + +Even though the above functional style code will produce the same results as the imperative style code, the lambda passed to the `forEach()` function is not cohesive, hard to read, and hard to change. + +Before refactoring the imperative style code above to the function style, we should first refactor the imperative style to yet another imperative style implementation to make each line more cohesive, like so: + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +for(String name: names) { + String nameInUpperCase = name.toUpperCase(); + System.out.println(nameInUpperCase); +} +``` + +From the previous articles in this series, we know that the `for` can turn into a `stream()` and the printing of the value can be done from within the `forEach()`. That leaves us with the transformation, the call to the `toUpperCase()` function. Such transformations can be done using the `map()` operation on the `stream()`. + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +names.stream() + .map(name -> name.toUpperCase()) + .forEach(nameInUpperCase -> System.out.println(nameInUpperCase)); +``` +The `map()` operation transforms the data to a different value based on the function invoked from within the lambda expression that's passed to `map()`. In this example, each name is transformed to its uppercase value. The transformed value is then printed out from within the lambda expression passed to `forEach()`. + +We can make the code a bit more concise by using method references instead of lambda expressions, like so: + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +names.stream() + .map(String::toUpperCase) + .forEach(System.out::println); +``` + +Use the `map()` function to transform data while iterating over a collection of data. + +  +## Picking Elements to Transform + +Suppose, in the middle of the iteration, we want to pick some values from the collection based on some condition and apply a transformation only on those elements. For example, what if we want to transform and print only names of length 4? In the imperative style we could do the following: + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +for(String name: names) { + if(name.length() == 4) { + System.out.println(name.toUpperCase()); + } +} +``` + +We already know that the imperative style `if` can be refactored to the `filter()` function in the functional style. We can perform the transformation, using `map()`, after the `filter()` operation, like so: + +```java +List names = List.of("Jack", "Paula", "Kate", "Peter"); + +names.stream() + .filter(name -> name.length() == 4) + .map(String::toUpperCase) + .forEach(System.out::println); +``` + +The `filter()` function discards data that's not desired and passes on only the values we like to use. The `map()` function transforms the values it sees after the filter. + +  +## Mappings + +Anywhere you see transformation of data within a for loop, use the `map()` function to perform the transformation in the functional style. In addition, if the body of the loop has a `if` statement to selectively some value for transformation, then use the `stream()` API with the call to the `filter()` method before using the `map()` method. diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md new file mode 100644 index 0000000..6cf4944 --- /dev/null +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/03_refactoring_to_functional_style/05_converting_to_streams.md @@ -0,0 +1,103 @@ +--- +id: refactoring.to.streams +title: Converting Data Sources to Streams +slug: learn/refactoring-to-functional-style/convertingtostreams +type: tutorial-group +group: refactoring-to-functional-style +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Thinking in Streams {streams} +- From Imperative to Functional Style {imperativetofunctional} +- Mappings {mappings} +description: "Converting Data Sources to Streams." +last_update: 2024-03-25 +author: ["VenkatSubramaniam"] +--- + +  +## Thinking in Streams + +In the previous articles in this [tutorial series](id:refactoring) we looked at converting loops written in the imperative style to the functional style. In this article we'll look at viewing the source of data, through the functional eyes, as a stream of data and convert the iteration to use the Streams API. + +We saw how we can use `filter()` and `map()` functions to select and transform data, respectively. We can perform these operations in the middle of the functional pipeline. In the examples in the previous articles we used functions like `range()` and `rangeClosed()` to create a stream of values in a range of numbers. That worked nicely when we want to iterate over a known range of values, but, often we may want to work with data that comes from external resources, like from a file, for example. If we are able to work with the external resource as a stream, then we can readily apply the operations of the functional pipeline. In this article we'll take a look at an example that illustrates that idea. + +  +## From Imperative to Functional Style + +Suppose we want to iterate over a file and count the number of lines with one or more occurrences of a word. Here's an all too familiear imperative style code to accomplish that task: + +```java +//Sample.java +import java.nio.file.*; + +public class Sample { + public static void main(String[] args) { + try { + final var filePath = "./Sample.java"; + final var wordOfInterest = "public"; + + try (var reader = Files.newBufferedReader(Path.of(filePath))) { + String line = ""; + long count = 0; + + while((line = reader.readLine()) != null) { + if(line.contains(wordOfInterest)) { + count++; + } + } + + System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest)); + } + } catch(Exception ex) { + System.out.println("ERROR: " + ex.getMessage()); + } + } +} + +``` + +In order to make it easy to work with this example, we look for the number of lines with the word "public" in the same source file as the code resides. You may change the value of the `filePath` to refer to a different file and/or the value of the `wordOfInterest` to something else if you like. + + +There are two major parts in this example. We use the `BufferedReader` returned by the `newBufferedReader()` method to access the contents of the file we're interested in looking into. Then, in the `while` loop we check each line to see if it contains the desired word and, if so, increment the `count` to indicate we found another line with the word. Let's examine the two parts, with the second one first. + +Looking closely at the loop, from our discussions in the previous articles, we can recognize that the presence of `if` is a sign that we may use the `filter()` operation if we can write the code as a functional pipeline. Once we filter out or select the lines with the desired word, we can count the number of lines, using the `count()` method of stream. You're most likely curious and bursting to ask, "but, where's the Stream?" To answer that question, let's take a look at the first part of the code. + +The data, that is the lines of text, is coming from the file whose path is provided in the variable `filePath`. We're reading the data using the `BufferedReader`'s `readLine()` method and the imperative style to iterate over each line of text. In order to use the functional pipeline, with the operations like `filter()` we need a `Stream` of data. Hence the question, "is it possible to get a Stream of data for the contents in a file?" + +The answer, thankfully, is a resounding yes. The developers behind the JDK and the Java language did not merely introduce the capability to do functional programming and say "good luck." They took the pains to enhance the JDK to add functions so we, as programmers, can make good use of the functional capabilities of Java for our routine tasks. + +An easy way to turn the contents of a file into a stream of data is using the `lines()` method of the `Files` class that is part of the `java.nio.file` package. Let's refactor the previous imperative style code to the functional style, with the help of the `lines()` method that gives us the `Stream` over the contents of a file, like so: + +```java +//Sample.java +import java.nio.file.*; + +public class Sample { + public static void main(String[] args) { + try { + final var filePath = "./Sample.java"; + final var wordOfInterest = "public"; + + try(var stream = Files.lines(Path.of(filePath))) { + long count = stream.filter(line -> line.contains(wordOfInterest)) + .count(); + + System.out.println(String.format("Found %d lines with the word %s", count, wordOfInterest)); + } + } catch(Exception ex) { + System.out.println("ERROR: " + ex.getMessage()); + } + } +} + +``` + +Not only does the `lines()` method provide a stream of data over the contents of a file, it remove so much of the cruft around reading the lines. Instead of the external iterator where we fetched one line at a time, the stream makes it possible to use the internal iterator where we can focus on what to do for each line of text as it emerges in the stream's pipeline. + +  +## Mappings + +Whenever you're working with a collection of data from an external resource, ask if there is a way to get a stream of data over the contents of that resource. The chances are that you may find a function for that within the JDK or a third-party library. Once we get a stream, we can use the highly effective functional operators like `filter()`, `map()`, etc. to fluently iterate over the collection of data that's part of the resource. diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/04_classes_objects/00_classes-and-objects.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/04_classes_objects/00_classes-and-objects.md new file mode 100644 index 0000000..38f655b --- /dev/null +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/04_classes_objects/00_classes-and-objects.md @@ -0,0 +1,17 @@ +--- +id: lang.classes +title: "Classes and Objects" +slug: learn/classes-objects +slug_history: +- classes-objects +type: tutorial +category: language +category_order: 4 +group: classes-and-objects +layout: learn/tutorial-group-top.html +subheader_select: tutorials +main_css_id: learn +description: "Defining your own classes, declaring member variables, methods, and constructors." +--- + +This part of the tutorial covers the basics of class definition, object creation, nesting classes, enumerations, declaring member variables, methods, and constructors. \ No newline at end of file diff --git a/app/pages/learn/01_tutorial/03_getting-to-know-the-language/04_classes_objects/01_enums.md b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/04_classes_objects/01_enums.md new file mode 100644 index 0000000..129676c --- /dev/null +++ b/app/pages/learn/01_tutorial/03_getting-to-know-the-language/04_classes_objects/01_enums.md @@ -0,0 +1,217 @@ +--- +id: lang.classes.enums +title: Enums +slug: learn/classes-objects/enums +type: tutorial-group +group: classes-and-objects +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- What are enums? {intro} +- Accessing, evaluating, and comparing enums {accessing} +- Adding members to enums {members} +- Special methods {functionality} +- Using enums as singletons {singletons} +- Abstract methods in enums {abstract} +- Precautions {precautions} +- Conclusion {conclusion} +description: "Working with enums." +last_update: 2024-07-08 +author: ["DanielSchmid"] +--- +  +## What are enums? + +Enums are classes where all instances are known to the compiler. +They are used for creating types that can only have few possible values. + +Enums can be created similar to classes but use the `enum` keyword instead of `class`. +In the body, there is a list of instances of the enum called enum constants which are seperated by `,`. +No instances of the enum can be created outside of enum constants. + +```java +public enum DayOfWeek { + // enum constants are listed here: + MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY +} +``` + +All enums implicitly extend [`java.lang.Enum`](javadoc:Enum) and cannot have any subclasses. + +  +## Accessing, evaluating, and comparing enums + +The values of an enum can be used as constants. +In order to check whether two instances of an enum are the same, the `==` operator can be used. +```java +DayOfWeek weekStart = DayOfWeek.MONDAY; + +if (weekStart == DayOfWeek.MONDAY) { + System.out.println("The week starts on Monday."); +} +``` + +It is also possible to use `switch` for performing actions depending on the value of the enum. + +```java +DayOfWeek someDay = DayOfWeek.FRIDAY; + +switch (someDay) { + case MONDAY -> + System.out.println("The week just started."); + case TUESDAY, WEDNESDAY, THURSDAY -> + System.out.println("We are somewhere in the middle of the week."); + case FRIDAY -> + System.out.println("The weekend is near."); + case SATURDAY, SUNDAY -> + System.out.println("Weekend"); + default -> + throw new AssertionError("Should not happen"); +} +``` + +With [switch expressions](id:lang.classes-objects.switch-expression), +the compiler can check whether all values of the enum are handled. +If any possible value is missing in a switch expression, there will be a compiler error. +This is referred to as exhaustiveness checking and can also be achieved with regular classes +through [sealed classes](https://openjdk.org/jeps/409) and [pattern matching](/learn/pattern-matching/#switch). + + +```java +DayOfWeek someDay = DayOfWeek.FRIDAY; + +String text = switch (someDay) { + case MONDAY -> "The week just started."; + case TUESDAY, WEDNESDAY, THURSDAY -> "We are somewhere in the middle of the week."; + case FRIDAY -> "The weekend is near."; + case SATURDAY, SUNDAY -> "Weekend"; +}; + +System.out.println(text); +``` + +  +## Adding members to enums + +Just like classes, enums can have constructors, methods and fields. +In order to add these, it is necessary to add a `;` after the list of enum constants. +Arguments to the constructor are passed in parenthesis after the declaration of the enum constant. + +```java +public enum DayOfWeek { + MONDAY("MON"), TUESDAY("TUE"), WEDNESDAY("WED"), THURSDAY("THU"), FRIDAY("FRI"), + SATURDAY("SAT"), SUNDAY("SUN"); + + private final String abbreviation; + + DayOfWeek(String abbreviation) { + this.abbreviation = abbreviation; + } + + public String getAbbreviation() { + return abbreviation; + } +} +``` + +  +## Special methods + +All enums have a few methods that are added implicitly. + +For example, the method `name()` is present in all enum instances and can be used to get the name of the enum constant. +Similarly, a method named `ordinal()` returns the position of the enum constant in the declaration. +```java +System.out.println(DayOfWeek.MONDAY.name()); // prints "MONDAY" +System.out.println(DayOfWeek.MONDAY.ordinal()); // prints "0" because MONDAY is the first constant in the DayOfWeek enum +``` + +Aside from instance methods, there are also static methods added to all enums. +The method `values()` returns an array containing all instances of the enum and the method `valueOf(String)` can be used to get a specific instance by its name. +```java +DayOfWeek[] days = DayOfWeek.values(); // all days of the week +DayOfWeek monday = DayOfWeek.valueOf("MONDAY"); +``` + +Furthermore, enums implement the interface [`Comparable`](javadoc:Comparable). +By default, enums are ordered according to their ordinal number +i.e. in the order of occurrence of the enum constant. +This allows for comparing instances of enums as well as sorting or searching. + +```java +public void compareDayOfWeek(DayOfWeek dayOfWeek){ + int comparison = dayOfWeek.compareTo(DayOfWeek.WEDNESDAY); + if (comparison < 0) { + System.out.println("It's before the middle of the work week."); + } else if (comparison > 0) { + System.out.println("It's after the middle of the work week."); + } else { + System.out.println("It's the middle of the work week."); + } +} +``` + +```java +List days = new ArrayList<>(List.of(DayOfWeek.FRIDAY, DayOfWeek.TUESDAY, DayOfWeek.SATURDAY)); +Collections.sort(days); +``` + + +  +## Using enums as singletons + +Since enums can only have a specific number of instances, it is possible to create a singleton by creating an enum with only a single enum constant. +```java +public enum SomeSingleton { + INSTANCE; + //fields, methods, etc. +} +``` + +  +## Abstract methods in enums + +Even though enums cannot be extended, they can still have `abstract` methods. In that case, an implementation must be present in each enum constant. +```java +enum MyEnum { + A() { + @Override + void doSomething() { + System.out.println("a"); + } + }, + B() { + @Override + void doSomething() { + System.out.println("b"); + } + }; + + abstract void doSomething(); +} +``` + +  +## Precautions + +Care should be taken when using enums where the number (or names) of instances is subject to change. +Whenever enum constants are changed, other code expecting the old version of the enum might not work as expected. +This may manifest in compilation errors (e.g. when referencing a removed enum constant), +runtime errors (e.g. if there is a `default` case even though the new enum constant should be handled separately) +or other inconsistencies (e.g. if the value of the enum was saved to a file which is then read and expecting that value to still exist). + +When changing enum constants, it is recommended to review all code using the enum. +This is especially important in cases where the enum is also used by other people's code. + +Furthermore, it might be worth considering to use other options +in case of many instances since listing a lot of instances at a single location in code can be inflexible. +For example, it may be better to use a configuration file for listing all instances +and reading these configuration files in the program in cases like this. + +  +## Conclusion + +Enums provide a simple and safe way of representing a fixed set of constants while keeping most of the flexibilities of classes. They are a special type of class that can be used to write code that is elegant, readable, maintainable and works well with other modern Java features like [switch expressions](id:lang.classes-objects.switch-expression). Another special class is the Record class introduced in Java 19. Visit our [records tutorial](id:lang.records) to learn more. + +To learn more about enums, visit the [`java.lang.Enum`](javadoc:Enum) javadoc. \ No newline at end of file diff --git a/app/pages/learn/01_tutorial/04_mastering-the-api/01_reflection/00_reflection.md b/app/pages/learn/01_tutorial/04_mastering-the-api/01_reflection/00_reflection.md new file mode 100644 index 0000000..14f1aa8 --- /dev/null +++ b/app/pages/learn/01_tutorial/04_mastering-the-api/01_reflection/00_reflection.md @@ -0,0 +1,353 @@ +--- +id: api.reflection +title: "Introduction to java.lang.reflect.*" +slug: learn/introduction_to_java_reflection +type: tutorial +category: api +category_order: 1 +layout: learn/tutorial.html +subheader_select: tutorials +main_css_id: learn +description: "TODO" +author: ["DrHeinzM.Kabutz"] +--- + +  + +## Reflection + +The face looking back from the mirror this morning told a story. +I urgently needed a shave (and still do). And yesterday, whilst sitting +outside in our garden, I should have worn sunscreen, or at least +a hat. I had tried my hand at making my own bacon, not being able +to find such delicacies on Crete, and the five hours of smoking +in the Weber were put to good use writing a Java Specialists +Newsletter about parallel streams and virtual threads (Issue 311). +The reflection in the mirror blinked. Enough time staring, it was +time to start writing about what I had just witnessed - reflection. + +Java reflection allows an object to look in the mirror and discover +what fields, methods, and constructors it has. We can read and +write fields, invoke methods, and even create new objects by +calling the constructors. Just like the stubble on my face and +the slight sunburn, we can see ourselves through others' eyes. + +Why should you care to read this tutorial? If you already know +reflection, you might want to peek through for entertainment +value. But if you have never heard of reflection, well, it's time +to take a good look in the mirror and discover a new magic that will +allow you to sometimes save thousands of fingerbreaking lines of code +with a few well-positioned chunks of reflection poetry. Oh and did I +mention that some employers post nasty interview questions that +have an easy solution via reflection? I hope you will enjoy it and +that you will try out the code snippets in the tutorial. Thank you +for joining me on this journey. + +## The Class `Class` + +No, this is not a typo. There is a class called `Class`. And it is a +subclass of `Object`. And `Object` has a `Class`. A nice circular dependency. + +How can we get hold of our object's Class? Each object has a +`getClass()` method that it inherits from `java.lang.Object`. +When we call that, we get back the actual implementation `Class`. + +For example, consider the following code. Note that for our code +snippets we are using the new unnamed classes, which are a preview +feature of Java 21. See [JEP 445](https://openjdk.org/jeps/445). +We can run them directly with `java --enable-preview --source 21 GetClassDemo.java` + +```java +// GetClassDemo.java +import java.util.List; +import java.util.ArrayList; + +// Using new Unnamed Classes which is a preview feature of Java 21. +// See JEP 445 +void main() { + List list1 = new ArrayList<>(); + System.out.println(list1.getClass()); + var list2 = new ArrayList(); + System.out.println(list2.getClass()); +} +``` + +In other words, it does not matter how the variable is declared, we +always get the actual implementation object's class. How can we get +the List class? That is fairly easy, using class literatls. We simply +write the name of the class, followed by `.class`, like so: + +```java +// ClassLiteral.java +void main() { + System.out.println(Number.class); // class java.lang.Number + System.out.println(java.util.List.class); // interface java.util.List +} +``` + +We can also load classes by name as `String`, without even knowing +whether the class will be available at runtime. For example, here +we are loading whatever class we are entering on the `Console`: + +```java +// ClassForName.java +void main() throws ClassNotFoundException { + var console = System.console(); + String className = console.readLine("Enter class name: "); + System.out.println(Class.forName(className)); +} +``` + +For example: + +```output +heinz$ java --enable-preview --source 21 ClassForName.java +Note: ClassForName.java uses preview features of Java SE 21. +Note: Recompile with -Xlint:preview for details. +Enter class name: java.util.Iterator +interface java.util.Iterator +``` + +Each class is loaded into a `ClassLoader`. The JDK classes all +reside in the bootstrap class loader, whereas our classes are in +the system class loader, also called application class loader. +We can see the class loaders here: + +```java +// ClassLoaderDemo.java +void main() { + System.out.println(String.class.getClassLoader()); + System.out.println(this.getClass().getClassLoader()); +} +``` + +Interesting is that depending on how we invoke this code, we get +different results. For example, if we call it using the +`java ClassLoaderDemo.java`, then the type of class loader is a +`MemoryClassLoader`, whereas if we first compile it and then call +it with `java ClassLoaderDemo`, it is an `AppClassLoader`. The +class loader for JDK classes comes back as `null`. + +```output +heinz$ java --enable-preview --source 21 ClassLoaderDemo.java +null +com.sun.tools.javac.launcher.Main$MemoryClassLoader@6483f5ae +``` + +And + +```output +heinz$ javac --enable-preview --source 21 ClassLoaderDemo.java +heinz$ java --enable-preview ClassLoaderDemo +null +jdk.internal.loader.ClassLoaders$AppClassLoader@3d71d552 +``` + +The purpose of class loaders is to partition classes for security +reasons. Classes in the JDK cannot see our classes at all, and +similarly, classes in the `AppClassLoader` have no relation to +classes in the `MemoryClassLoader`. This can cause some surprises +when we compile our classes and then also launch them with +the single-file command `java SomeClass.java` + +## Shallow Reflective Access + +Once we have the class, we can find out a lot of information about +it, such as who the superclasses are, what public members it has, +what interfaces it has implemented. If it is a `sealed` type, we +can even find the subtypes. + +Let's try find the methods defined on java.util.Iterator: + +```java +// MethodsOnIterator.java +import java.util.Iterator; +import java.util.stream.Stream; + +void main() { + Stream.of(Iterator.class.getMethods()) + .forEach(System.out::println); +} +``` + +We see four methods, two of which are `default` interface methods: + +```java +heinz$ java --enable-preview --source 21 MethodsOnIterator.java +public default void java.util.Iterator.remove() +public default void java.util.Iterator.forEachRemaining(java.util.function.Consumer) +public abstract boolean java.util.Iterator.hasNext() +public abstract java.lang.Object java.util.Iterator.next() +``` + +If we make an object of type `java.util.Iterator`, we would even be able to call these methods. In the next example, we look for the method +called `"forEachRemaining"` and which takes a `Consumer` as a parameter. We then create an `Iterator` from a `List.of()` and +invoke the `forEachRemaining` method using reflection. Note that +several things could go wrong, most notably that the method does not +exist (`NoSuchMethodException`) and that we are not allowed to call +the method (`IllegalAccessException`). Since Java 7, we have a blanket +exception that covers everything that can go wrong with reflection, +the `ReflectiveOperationException`. + +```java +// MethodsOnIteratorCalling.java +import java.util.List; +import java.util.Iterator; +import java.util.function.Consumer; + +void main() throws ReflectiveOperationException { + var iterator = List.of("Hello", "Dev", "Java").iterator(); + var forEachRemainingMethod = Iterator.class.getMethod( + "forEachRemaining", Consumer.class); + Consumer println = System.out::println; + forEachRemainingMethod.invoke(iterator, println); +} +``` + +Our next example is even more interesting, if I may say so myself. +We are going to take a `List` of items and then search through +the `Collections` class to see whether we can find any methods +that we can give the method to. We invoke the method and see what +happens to our list. Since the methods are declared `static` in `Collections`, the first parameter of our `invoke()` method will +be `null`. We could use a stream, but they don't "play nice" with +checked exceptions, thus the plain old for-in loop it will have +to be: + +```java +// CollectionsListMethods.java +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +void main() throws ReflectiveOperationException { + var pi = "3141592653589793".chars() + .map(i -> i - '0') + .boxed().collect(Collectors.toList()); + System.out.println(pi); + for (var method : Collections.class.getMethods()) { + if (method.getReturnType() == void.class + && method.getParameterCount() == 1 + && method.getParameterTypes()[0] == List.class) { + System.out.println("Calling " + method.getName() + "()"); + method.invoke(null, pi); + System.out.println(pi); + } + } +} +``` + +This works nicely and we find three methods that match our +requirements: `sort()`, `shuffle()` and `reverse()`. The order of these +methods is not guaranteed. For example, in the `Collections.java` +file in OpenJDK 21, they are ordered as `sort()`, `reverse()`, +`shuffle()`. However, when I run the code, they appear as: + +```output +heinz$ java --enable-preview --source 21 CollectionsListMethods.java +[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9, 3] +Calling reverse() +[3, 9, 7, 9, 8, 5, 3, 5, 6, 2, 9, 5, 1, 4, 1, 3] +Calling sort() +[1, 1, 2, 3, 3, 3, 4, 5, 5, 5, 6, 7, 8, 9, 9, 9] +Calling shuffle() +[5, 7, 4, 9, 9, 9, 2, 1, 6, 5, 3, 3, 1, 5, 3, 8] +``` + +## Deep Reflective Access + +Up to now, we did not do anything that was particularly +dangerous. All the methods we discovered and called were `public`. +The only part that was a bit dangerous was that we did not have a +compiler check that these methods exist and are accessible. +However, we can also stare more deeply into the mirror. + +For example, let's consider our `Person` class: + +```java +public class Person { + private final String name; + private final int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String toString() { + return name + " (" + age + ")"; + } +} +``` + +Since we are now working with two separate classes, we will need +to compile them. We can use an unnamed class as before for the demo, +but we should still compile them both. It would be a mistake to use a +single-file call, because in that case `Person` and the demo would +exist in different class loaders. This can cause hard to understand +runtime errors. I once spent a day chasing this exact error. Don't +be me. + +Here is our `FountainOfYouth.java` file: + +```java +// FountainOfYouth.java +import java.lang.reflect.*; + +void main() throws ReflectiveOperationException { + var person = new Person("Heinz Kabutz", 51); + System.out.println(person); + Field ageField = Person.class.getDeclaredField("age"); + ageField.setAccessible(true); // deep reflection engaged! + int age = (int) ageField.get(person); + age *= .9; + ageField.set(person, age); + System.out.println(person); +} +``` + +We first compile the `FountainOfYouth` class, which transitively +compiles `Person.java`. We then run it, and *voila*, I've shaved +10% off my age. + +```output +heinz$ javac --enable-preview --source 21 FountainOfYouth.java +heinz$ java --enable-preview FountainOfYouth +Heinz Kabutz (51) +Heinz Kabutz (45) +``` + +Note that the `age` field is `private` *and* `final`, and yet we +were able to change it. If we convert `Person` to a record, then +it will no longer allow us to change the properties via deep +reflection. + +### The Java Module System + +Deep reflection only works if the module in which the class resides +is *open* to us. Ideally we should ask the author of the module +to open the package to our module. They will likely refuse, and +with good reason. By opening up their package, they allow +unfettered access to their most intimate implementation detail. +What if, in the near or distant future, they want to change +field names or types? The deep reflective code would likely stop +working, and they would be forever having to fix other modules. + +We can open up a package in another module for deep reflection +with a command line parameter `--add-opens`, but we should only +use that as an absolute last resort. It is so undesirable, that +I merely mention it here in disgust, but won't show more details +of how to use it. + +## Conclusion + +We hope that you got a bit of insight into how reflection works +in this tutorial. There are many other topics to explore: arrays, +dynamic proxies, generics, sealed classes, etc. How we can read +the properties of a record, how parameter names can be preserved. +But this is long enough and will hopefully get you started in the +right direction. + +For many more deep dives into the Java Programming Language, be +sure to subscribe to +[The Java Specialists' Newsletter](https://www.javaspecialists.eu), +a newsletter for anyone who wants to become more proficient in Java. diff --git a/app/pages/learn/01_tutorial/04_mastering-the-api/02_invoke/00_methodhandle.md b/app/pages/learn/01_tutorial/04_mastering-the-api/02_invoke/00_methodhandle.md new file mode 100644 index 0000000..ba849ae --- /dev/null +++ b/app/pages/learn/01_tutorial/04_mastering-the-api/02_invoke/00_methodhandle.md @@ -0,0 +1,625 @@ +--- +id: api.invoke +title: "Introduction to Method Handles" +slug: learn/introduction_to_method_handles +type: tutorial +category: api +category_order: 1 +layout: learn/tutorial.html +subheader_select: tutorials +main_css_id: learn +description: "Method handles, how they are different from the Reflection API, and the tooling they provide." +author: ["NataliiaDziubenko"] +toc: + - What are method handles {intro} + - Access checking {access} + - Method handle lookup {lookup} + - Method type {methodtype} + - Method handle invocation {invocation} + - Accessing fields {fields} + - Working with arrays {arrays} + - Exception handling {exceptions} + - Method handle transformations {transformations} + - Method Handles vs Reflection API {vsreflection} + - Conversion between Reflection API and method handles {unreflect} + - Conclusion {conclusion} +last_update: 2024-05-30 +--- + +  +## What are method handles +Method handles are a low-level mechanism used for method lookup and invocation. They are often compared to reflection, +because both the Reflection API and method handles provide a means to invoke methods, constructors, and access fields. + +What exactly is a method handle? It's a directly invocable reference to an underlying method, constructor, or field. +The Method Handle API allows manipulations on top of a simple pointer to the method that allows us to insert or reorder the +arguments, transform the return values, etc. + +Let's take a closer look at what method handles can provide and how we can effectively use them. + +  +## Access checking +The access checking for method handle invocations is done differently compared to the Reflection API. With reflection, +each call results in access checks for the caller. For method handles, the access is only checked when the method handle +is created. + +It is important to keep in mind that if the method handle is created within a context where it can access non-public +members, when passed outside, it can still access those non-public members. As a result, non-public members can +potentially be accessed from code where they shouldn't be accessible. It's a developer's responsibility to keep such +method handles private to their context. Alternatively, the method handle can be created with access limitations right +away using the appropriate lookup object. + +  +## Method handle lookup +To create a method handle we first need to create a [`Lookup`](javadoc:Lookup) object, which acts as a factory for +creating method handles. Depending on how the lookup object itself or the method handles are going to be used, we can +decide whether we should limit its access level. + +For example, if we create a method handle pointing to a private method and that method handle is accessible from the outside, +then the private method is as well. Normally we would like to avoid that. One way is to make the lookup object and method handle +`private` too. Another option is to create the lookup object using the [`MethodHandles.publicLookup`](javadoc:MethodHandles.publicLookup()) +method, so it will only be able to search for public members in public classes within packages that are exported unconditionally: + +```java +MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); +``` + +If we are going to keep the lookup object and the method handles private, it's safe to give them access to any members, +including private and protected ones: + +```java +MethodHandles.Lookup lookup = MethodHandles.lookup(); +``` + +  +## Method type +To look up a method handle we also need to provide the type information of the method or field. The method +type information is represented as [`MethodType`](javadoc:MethodType) object. To instantiate a `MethodType`, +we have to provide the return type as the first parameter followed by all the argument types: + +```java +MethodType methodType = MethodType.methodType(int.class /* the method returns integer */, + String.class /* and accepts a single String argument*/); +``` + +Having the `Lookup` and the `MethodType` instances, we can look up the method handle. For instance methods, we should +use [`Lookup.findVirtual`](javadoc:MethodHandles.Lookup.findVirtual(Class,String,MethodType)), and for static methods +[`Lookup.findStatic`](javadoc:MethodHandles.Lookup.findStatic(Class,String,MethodType)). Both of these methods accept the +following arguments: a `Class` where the method is located, a method name represented as a `String`, and a `MethodType` +instance. + +In the example below, we are using `Lookup.findVirtual` method to look up an instance method +[`String.replace`](javadoc:String.replace(char,char)), which accepts two `char` arguments and returns a `String`: + +```java +MethodHandles.Lookup lookup = MethodHandles.lookup(); +MethodType replaceMethodType = MethodType.methodType(String.class, char.class, char.class); +MethodHandle replaceMethodHandle = lookup.findVirtual(String.class, "replace", replaceMethodType); +``` + +In the next example, we are using `Lookup.findStatic` to look up a static method +[`String.valueOf`](javadoc:String.valueOf(Object)), which accepts an `Object` and returns a `String`: + +```java +MethodType valueOfMethodType = MethodType.methodType(String.class, Object.class); +MethodHandle valueOfMethodHandle = lookup.findStatic(String.class, "valueOf", valueOfMethodType); +``` + +Similarly, we could use [`Lookup.findConstructor`](javadoc:MethodHandles.Lookup.findConstructor(Class,MethodType)) +method to look up a method handle pointing to any constructor. + +Finally, when we have obtained a method handle, we can invoke the underlying method. + +  +## Method handle invocation +The invocation can also be done in multiple ways. + +All the methods that facilitate invocation eventually funnel down to a single method that is called in the end: +[`MethodHandle.invokeExact`](javadoc:MethodHandle.invokeExact(Object...)). As the method name suggests, the arguments +provided to `invokeExact` method must strictly match the method handle's type. + +For example, if we invoke a `String.replace` method, the arguments must strictly +correspond to a `String` return type and two `char` arguments: + +```java +MethodType replaceMethodType = MethodType.methodType(String.class, char.class, char.class); +MethodHandle replaceMethodHandle = lookup.findVirtual(String.class, "replace", replaceMethodType); +String result = (String) replaceMethodHandle.invokeExact("dummy", 'd', 'm'); +``` + +[`MethodHandle.invoke`](javadoc:MethodHandle.invoke(Object...)) is more permissive. It attempts to obtain a new method +handle with adjusted types that would strictly match the types of provided arguments. After that, it will be able to +invoke the adjusted method handle using `invokeExact`. + +```java +String result = (String) replaceMethodHandle.invoke((Object)"dummy", (Object)'d', (Object)'m'); // would fail with `invokeExact` +``` + +One other alternative to invoke a method handle is to use [`MethodHandle.invokeWithArguments`](javadoc:MethodHandle.invokeWithArguments(Object...)). +The result of this method invocation is equivalent to `invoke`, with the only difference being that all the arguments can be +provided as an array or list of objects. + +One interesting feature of this method is that if the number of provided arguments exceeds the expected number, all the +leftover arguments will be squashed into the last argument, which will be treated as an array. + +  +## Accessing fields +It is possible to create method handles that have read or write access to fields. For instance fields, this is +facilitated by the [`findGetter`](javadoc:MethodHandles.Lookup.findGetter(Class,String,Class)) and +[`findSetter`](javadoc:MethodHandles.Lookup.findSetter(Class,String,Class)) methods, and for static fields, by the +[`findStaticGetter`](javadoc:MethodHandles.Lookup.findStaticGetter(Class,String,Class)) and +[`findStaticSetter`](javadoc:MethodHandles.Lookup.findStaticSetter(Class,String,Class)) methods. We don't need to provide +a `MethodType` instance; instead, we should provide a single type, which is the type of the field. + +For example, if we have a static field `magic` in our `Example` class: + +```java +private static String magic = "initial value static field"; +``` + +Given that we have created a `Lookup` object: + +```java +MethodHandles.Lookup lookup = MethodHandles.lookup(); +``` + +We can simply create both setter and getter method handles and invoke them separately: + +```java +MethodHandle setterStaticMethodHandle = lookup.findStaticSetter(Example.class, "magic", String.class); +MethodHandle getterStaticMethodHandle = lookup.findStaticGetter(Example.class, "magic", String.class); + +setterStaticMethodHandle.invoke("new value static field"); +String staticFieldResult = (String) getterStaticMethodHandle.invoke(); // staticFieldResult == `new value static field` +``` + +Here is an instance field `abc` of class `Example`: + +```java +private String abc = "initial value"; +``` + +We can similarly create method handles for reading and writing to the instance field: + +```java +MethodHandle setterMethodHandle = lookup.findSetter(Example.class, "abc", String.class); +MethodHandle getterMethodHandle = lookup.findGetter(Example.class, "abc", String.class); +``` + +To use setter and getter method handles with an instance field, we must first obtain an instance of the class where the +field belongs: + +```java +Example example = new Example(); +``` + +Afterward, we must provide an instance of `Example` for invocation of our setter and getter: + +```java +setterMethodHandle.invoke(example, "new value"); +String result = (String) getterMethodHandle.invoke(example); // result == `new value` +``` + +Although it is possible to read and write field values using method handles, it's not common practice. For fields, +it's more suitable to use [`VarHandle`](javadoc:VarHandle)s instead, which can be created using +[`findVarHandle`](javadoc:MethodHandles.Lookup.findVarHandle(Class,String,Class)) and +[`findStaticVarHandle`](javadoc:MethodHandles.Lookup.findStaticVarHandle(Class,String,Class)) +methods. + +  +## Working with arrays +The [`MethodHandles`](javadoc:MethodHandles) class contains methods that provide a number of preset method handles. +These include method handles that allow array manipulations. Creating these method handles doesn't require access checking, +so the lookup object is not necessary. + +Let's create an array of Strings containing 5 elements using [`arrayConstructor`](javadoc:MethodHandles.arrayConstructor(Class)): + +```java +MethodHandle arrayConstructor = MethodHandles.arrayConstructor(String[].class); +String[] arr = (String[]) arrayConstructor.invoke(5); +``` + +To modify a single element, we can use [`arrayElementSetter`](javadoc:MethodHandles.arrayElementSetter(Class)), to which +we provide the reference to the target array, the index of an element, and the new value: + +```java +MethodHandle elementSetter = MethodHandles.arrayElementSetter(String[].class); +elementSetter.invoke(arr, 4, "test"); +``` + +To read the value of a single element, we should use [`arrayElementGetter`](javadoc:MethodHandles.arrayElementGetter(Class)) +method handle, to which we provide the reference to an array and the element index: + +```java +MethodHandle elementGetter = MethodHandles.arrayElementGetter(String[].class); +String element = (String) elementGetter.invoke(arr, 4); // element == "test" +``` + +We could also use the method handle provided by [`arrayLength`](javadoc:MethodHandles.arrayLength(Class)) to get the array +length as integer: + +```java +MethodHandle arrayLength = MethodHandles.arrayLength(String[].class); +int length = (int) arrayLength.invoke(arr); // length == 5 +``` + +  +## Exception handling +Both `invokeExact` and `invoke` throw [`Throwable`](javadoc:Throwable), so there is no limitation to what an underlying +method can throw. The method that invokes a method handle must either explicitly throw a `Throwable` or catch it. + +There are certain methods in the `MethodHandles` API that can make exception handling easier. Let's take a look at +several examples. + +### `catch` wrapper +The [`MethodHandles.catchException`](javadoc:MethodHandles.catchException(MethodHandle,Class,MethodHandle)) method can +wrap a given method handle inside a provided exception handler method handle. + +Say, we have a method `problematicMethod` that performs some business logic, and a method `exceptionHandler` that handles +a particular [`IllegalArgumentException`](javadoc:IllegalArgumentException). The exception handler method must +return the same type as the original method. The first argument it accepts is a `Throwable` that we're interested in, +after which follow the rest of the arguments that we've originally accepted: + +```java +public static int problematicMethod(String argument) throws IllegalArgumentException { + if ("invalid".equals(argument)) { + throw new IllegalArgumentException(); + } + return 1; +} + +public static int exceptionHandler(IllegalArgumentException e, String argument) { + // log exception + return 0; +} +``` + +We can look up the method handles for both these methods and wrap `problematicMethod` inside an `exceptionHandler`. The +resulting `MethodHandle` will handle the `IllegalArgumentException` properly on invocation, continuing to throw any +other exceptions if they arise. + +```java +MethodHandles.Lookup lookup = MethodHandles.lookup(); +MethodHandle methodHandle = lookup.findStatic(Example.class, "problematicMethod", MethodType.methodType(int.class, String.class)); +MethodHandle handler = lookup.findStatic(Example.class, "exceptionHandler", + MethodType.methodType(int.class, IllegalArgumentException.class, String.class)); +MethodHandle wrapped = MethodHandles.catchException(methodHandle, IllegalArgumentException.class, handler); + +System.out.println(wrapped.invoke("valid")); // outputs "1" +System.out.println(wrapped.invoke("invalid")); // outputs "0" +``` + +### `finally` wrapper +The [`MethodHandles.tryFinally`](javadoc:MethodHandles.tryFinally(MethodHandle,MethodHandle)) method works similarly, +but instead of an exception handler, it wraps a target method adding a try-finally block. + +Let's say we have a separate method `cleanupMethod` containing cleanup logic. The return type of this method must be the +same as the target method's return type. It must accept a `Throwable` followed by the resulting value coming from the +target method, followed by all the arguments. + +```java +public static int cleanupMethod(Throwable e, int result, String argument) { + System.out.println("inside finally block"); + return result; +} +``` + +We can wrap the method handle from previous example inside the try-finally block as follows: + +```java +MethodHandle cleanupMethod = lookup.findStatic(Example.class, "cleanupMethod", + MethodType.methodType(int.class, Throwable.class, int.class, String.class)); + +MethodHandle wrappedWithFinally = MethodHandles.tryFinally(methodHandle, cleanupMethod); + +System.out.println(wrappedWithFinally.invoke("valid")); // outputs "inside finally block" and "1" +System.out.println(wrappedWithFinally.invoke("invalid")); // outputs "inside finally block" and throws java.lang.IllegalArgumentException +``` + +  +## Method handle transformations +As seen from previous examples, method handles can encapsulate more behavior than simply pointing to an underlying +method. We can obtain **adapter** method handles, which wrap target method handles to add certain behaviors such as +argument reordering, pre-inserting, or filtering of the return values. + +Let's take a look at a couple of such transformations. + +### Type transformation +A method handle's type can be adapted to a new type using the [`asType`](javadoc:MethodHandle.asType(MethodType)) method. +If such type conversion is impossible, we will get a [`WrongMethodTypeException`](javadoc:WrongMethodTypeException). +Remember, when we apply transformations, we actually have two method handles, where the original method handle is wrapped +into some extra logic. In this case, the wrapper will take in the arguments and try to convert them to match the original +method handle's arguments. Once the original method handle does its job and returns a result, the wrapper will attempt to +cast this result to the given type. + +Assume we have a `test` method that accepts an `Object` and returns a `String`. We can adapt such a method to accept a +more specific argument type, such as `String`: + +```java +MethodHandle targetMethodHandle = lookup.findStatic(Example.class, "test", + MethodType.methodType(String.class, Object.class)); +MethodHandle adapter = targetMethodHandle.asType( + MethodType.methodType(String.class, String.class)); +String originalResult = (String) targetMethodHandle.invoke(111); // works +String adapterResult = (String) adapter.invoke("aaaaaa"); // works +adapterResult = (String) adapter.invoke(111); // fails +``` + +In fact, each time we use `invoke` on a `MethodHandle`, the first thing that happens is an `asType` call. `invoke` +accepts and returns `Object`s, which are then attempted to be converted to more specific types. These specific types are +derived from our code, i.e., the exact values that we pass as arguments and the type that we cast our return value to. +Once the types are successfully converted, the `invokeExact` method is then called for these specific types. + +### Permute arguments +To obtain an adapter method handle with reordered arguments, we can use +[`MethodHandles.permuteArguments`](javadoc:MethodHandles.permuteArguments(MethodHandle,MethodType,int...)). + +For example, let's create a `test` method that accepts a bunch of arguments of different types: + +```java +public static void test(int v1, String v2, long v3, boolean v4) { + System.out.println(v1 + v2 + v3 + v4); +} +``` + +And look up a method handle for it: + +```java +MethodHandle targetMethodHandle = lookup.findStatic(Example.class, "test", + MethodType.methodType(void.class, int.class, String.class, long.class, boolean.class)); +``` + +The `permuteArguments` method accepts: +- Target method handle, in our case the one pointing to `test` method; +- New `MethodType` with all the arguments reordered in desired way; +- An index array designating the new order of the arguments. + +```java +MethodHandle reversedArguments = MethodHandles.permuteArguments(targetMethodHandle, + MethodType.methodType(void.class, boolean.class, long.class, String.class, int.class), 3, 2, 1, 0); +reversedArguments.invoke(false, 1L, "str", 123); // outputs: "123str1false" +``` + +### Insert arguments +The [`MethodHandles.insertArguments`](javadoc:MethodHandles.insertArguments(MethodHandle,int,Object...)) method provides +a `MethodHandle` with one or more bound arguments. + +For example, let's look again at the method handle from previous example: + +```java +MethodHandle targetMethodHandle = lookup.findStatic(Example.class, "test", + MethodType.methodType(void.class, int.class, String.class, long.class, boolean.class)); +``` + +We can easily obtain an adapter `MethodHandle` with `String` and `long` arguments bound in advance: + +```java +MethodHandle boundArguments = MethodHandles.insertArguments(targetMethodHandle, 1, "new", 3L); +``` + +To invoke the resulting adapter method handle, we only need to provide the arguments that are not pre-filled: + +```java +boundArguments.invoke(1, true); // outputs: "1new3true" +``` + +If we try to pass the arguments that are already prefilled, we will fail with a `WrongMethodTypeException`. + +### Filter arguments +We can use [`MethodHandles.filterArguments`](javadoc:MethodHandles.filterArguments(MethodHandle,int,MethodHandle...)) +to apply transformations to arguments before invocation of the target method handle. To make it work, we have to provide: + +- The target method handle; +- The position of the first argument to transform; +- Method handles for the transformations of each argument. + +If certain arguments don't require transformation, we can skip them by passing `null`. It's also possible to skip the +rest of the arguments entirely if we only need to transform a subset of them. + +Let's reuse the method handle from the previous section and filter some of its arguments before invocation. + +```java +MethodHandle targetMethodHandle = lookup.findStatic(Example.class, "test", + MethodType.methodType(void.class, int.class, String.class, long.class, boolean.class)); +``` + +Then we create a method that transforms any `boolean` value by negating it: + +```java +private static boolean negate(boolean original) { + return !original; +} +``` + +and also construct a method that increments any given integer value: + +```java +private static int increment(int original) { + return ++original; +} +``` + +We can obtain method handles for these transformation methods: + +```java +MethodHandle negate = lookup.findStatic(Example.class, "negate", MethodType.methodType(boolean.class, boolean.class)); +MethodHandle increment = lookup.findStatic(Example.class, "increment", MethodType.methodType(int.class, int.class)); +``` + +and use them to get a new method handle having filtered the arguments: + +```java +// applies filter 'increment' to argument at index 0, 'negate' to the last argument, +// and passes the result to 'targetMethodHandle' +MethodHandle withFilters = MethodHandles.filterArguments(targetMethodHandle, 0, increment, null, null, negate); +withFilters.invoke(3, "abc", 5L, false); // outputs "4abc5true" +``` + +### Fold arguments +When we want to perform pre-processing of one or more arguments before the invocation of a `MethodHandle`, we +can use [`MethodHandles.foldArguments`](javadoc:MethodHandles.foldArguments(MethodHandle,int,MethodHandle)) and provide +it with the method handle of any combiner method which will accept arguments starting at any preferred position. + +Let's assume that we have a `target` method: + +```java +private static void target(int ignored, int sum, int a, int b) { + System.out.printf("%d + %d equals %d and %d is ignored%n", a, b, sum, ignored); +} +``` + +Using `foldArguments` we can pre-process a subset of its arguments and insert the resulting value as another +argument and proceed to the execution of the `target` method. + +In our example, we have arguments `int a, int b` at the end. We can pre-process any amount of arguments, but they all +must be at the end. Let's say, we would like to calculate a sum of these two values `a` and `b`, so let's create a method +for that: + +```java +private static int sum(int a, int b) { + return a + b; +} +``` + +Where will the resulting value go exactly? It will be inserted into one of the arguments of our `target` method. It must +be the argument right before the arguments that we are going to fold, so in our example argument `int sum`. The argument +reserved for the folding result can't be at any other position. If the `target` method needs to accept more arguments not +related to this folding logic, they all must go first. + +Let's create the method handles and see how we should combine them together: + +```java +MethodHandle targetMethodHandle = lookup.findStatic(Example.class, "target", + MethodType.methodType(void.class, int.class, int.class, int.class, int.class)); +MethodHandle combinerMethodHandle = lookup.findStatic(Example.class, "sum", + MethodType.methodType(int.class, int.class, int.class)); +MethodHandle preProcessedArguments = MethodHandles.foldArguments(targetMethodHandle, 1, combinerMethodHandle); +``` + +The `foldArguments` method accepts: +- `MethodHandle` *target*: The target method handle, in our case the one pointing to the `target` method. +- `int` *pos*: An integer specifying the starting position of arguments related to folding. In our case, the `sum` argument is located at position `1`, so we passed `1`. If we skip this argument, `pos` will default to `0`. +- `MethodHandle` *combiner*: The combiner method handle, in our case the one pointing to the `sum` method. + +At the end, we can invoke the resulting method handle and pass all the arguments except `sum` which is going to be +pre-calculated: + +```java +preProcessedArguments.invokeExact(10000, 1, 2); // outputs: "1 + 2 equals 3 and 10000 is ignored" +``` + +It is possible that the combiner method processes values but doesn't return anything. In this case, there is no need +for a result placeholder in the `target` method argument list. + +### Filter return value +Similarly to arguments, we can use an adapter that will apply transformations to the return value. + +Let's imagine a situation where we have a method that returns a `String`, and we would like to channel any returned +value from this method into another method that replaces character `d` with `m` and uppercases the resulting value. + +Here's the method handle for the `getSomeString` method which always returns the value `"dummy"`: + +```java +MethodHandle getSomeString = lookup.findStatic(Example.class, "getSomeString", MethodType.methodType(String.class)); +``` + +Here's the `resultTransform` method that performs transformations: + +```java +private static String resultTransform(String value) { + return value.replace('d', 'm').toUpperCase(); +} +``` + +Here is the method handle for our transformer method: + +```java +MethodHandle resultTransform = lookup.findStatic(Example.class, "resultTransform", MethodType.methodType(String.class, String.class)); +``` + +Finally, this is the combination of the two method handles where the result returned by the `getSomeString` method is +then provided to the `resultTransform` method and modified accordingly: + +```java +MethodHandle getSomeUppercaseString = MethodHandles.filterReturnValue(getSomeString, resultTransform); +System.out.println(getSomeUppercaseString.invoke()); // outputs: "MUMMY" +``` + +  +## Method Handles vs Reflection API +Method handles were introduced in [JDK7](https://docs.oracle.com/javase/7/docs/index.html) as a tool to assist +compiler and language runtime developers. They were never meant to replace reflection. + +[The Reflection API](id:api.reflection) offers something +that method handles cannot, which is listing the class members and inspecting their properties. Method handles, on the +other hand, can be transformed and manipulated in a way that is not possible with the Reflection API. + +When it comes to method invocation, there are differences related to access checking and security considerations. The +Reflection API performs access checking against every caller, on every call, while for method handles, access is +checked only during construction. This makes invocation through method handles faster than through reflection. +However, certain precautions have to be taken so the method handle is not passed to the code where it shouldn't be +accessible. + +You can learn more about Reflection in [this tutorial](id:api.reflection). + +  +## Conversion between Reflection API and method handles +The `Lookup` object can be used to convert Reflection API objects to behaviorally equivalent method handles, which +provide more direct and efficient access to the underlying class members. + +To create a method handle pointing to a given [`Method`](javadoc:Method) (given that the lookup class has permission to do so), we can +use `unreflect`. + +Let's say we have a `test` method in our `Example` class which accepts a `String` argument and returns a `String`. Using +the Reflection API, we can obtain a `Method` object: + +```java +Method method = Example.class.getMethod("test", String.class); +``` + +With the help of the lookup object, we can [`unreflect`](javadoc:MethodHandles.Lookup.unreflect(Method)) the `Method` +object to obtain a `MethodHandle`: + +```java +MethodHandles.Lookup lookup = MethodHandles.lookup(); +MethodHandle methodHandle = lookup.unreflect(method); +String result = (String) methodHandle.invoke("something"); +``` + +Similarly, given a [`Field`](javadoc:Field) object, we can obtain getter and setter method handles: + +```java +Field field = Example.class.getField("magic"); +MethodHandle setterMethodHandle = lookup.unreflectSetter(field); +MethodHandle getterMethodHandle = lookup.unreflectGetter(field); +setterMethodHandle.invoke("something"); +String result = (String) getterMethodHandle.invoke(); // result == "something" +``` + +Conversion from `MethodHandle` to a [`Member`](javadoc:Member) is also possible, with the condition that no transformations +have been performed to the given `MethodHandle`. + +Let's say we have a method handle pointing directly to a method. We can use the +[`MethodHandles.reflectAs`](javadoc:MethodHandles.reflectAs(Class,MethodHandle)) method to obtain the `Method` object: + +```java +Method method = MethodHandles.reflectAs(Method.class, methodHandle); +``` + +It works similarly for the `Field` object: + +```java +Field field = MethodHandles.reflectAs(Field.class, getterMethodHandle); // same result is achieved by reflecting `setterMethodHandle` +``` + +  +## Conclusion +In this tutorial, we have looked into the method handle mechanism and learned how to efficiently use it. We now know that method handles provide a means for efficient method invocation, but this mechanism is not meant to replace the +Reflection API. + +Method handles offer a performance advantage for method invocation due to a different access checking approach. However, +since access is checked only on method handle creation, method handles should be passed around with caution. + +Unlike the Reflection API, method handles don't provide any tooling for listing class members and inspecting their properties. +On the other hand, the Method Handle API allows us to wrap direct pointers to methods and fields into more complex +logic, such as argument and return value manipulations. \ No newline at end of file diff --git a/app/pages/learn/01_tutorial/04_mastering-the-api/02_modern_io/01_modern_io.md b/app/pages/learn/01_tutorial/04_mastering-the-api/02_modern_io/01_modern_io.md new file mode 100644 index 0000000..5bd0496 --- /dev/null +++ b/app/pages/learn/01_tutorial/04_mastering-the-api/02_modern_io/01_modern_io.md @@ -0,0 +1,274 @@ +--- +id: api.modernio +title: "Common I/O Tasks in Modern Java" +type: tutorial +category: api +category_order: 2 +layout: learn/tutorial.html +subheader_select: tutorials +main_css_id: learn +toc: + - Introduction {introduction} + - Reading Text Files {reading-text-files} + - Writing Text Files {writing-text-files} + - The Files API {the-files-api} + - Conclusion {conclusion} +last_update: 2024-04-24 +description: "This article focuses on tasks that application programmers are likely to encounter, particularly in web applications, such as reading and writing text files, reading text, images, JSON from the web, and more." +author: ["CayHorstmann"] +--- + + +  +## Introduction + +This article focuses on tasks that application programmers are likely to encounter, particularly in web applications, such as: + +* Reading and writing text files +* Reading text, images, JSON from the web +* Visiting files in a directory +* Reading a ZIP file +* Creating a temporary file or directory + +The Java API supports many other tasks, which are explained in detail in the [Java I/O API tutorial](id:api.javaio.overview). + +This article focuses on API improvements since Java 8. In particular: + +* UTF-8 is the default for I/O since Java 18 (since [UTF-8 by Default](jep:400)) +* The [`java.nio.file.Files`](javadoc:Files) class, which first appeared in Java 7, added useful methods in Java 8, 11, and 12 +* [`java.io.InputStream`](javadoc:InputStream) gained useful methods in Java 9, 11, and 12 +* The [`java.io.File`](javadoc:File) and [`java.io.BufferedReader`](javadoc:BufferedReader) classes are now thoroughly obsolete, even though they appear frequently in web searches and AI chats. + +  +## Reading Text Files + +You can read a text file into a string like this: + +```java +String content = Files.readString(path); +``` + +Here, `path` is an instance of [`java.nio.Path`](javadoc:Path), obtained like this: + +```java +var path = Path.of("/usr/share/dict/words"); +``` + +Before Java 18, you were strongly encouraged to specify the character encoding with any file operations that read or write strings. Nowadays, by far the most common character encoding is UTF-8, but for backwards compatibility, Java used the "platform encoding", which can be a legacy encoding on Windows. To ensure portability, text I/O operations needed parameters [`StandardCharsets.UTF_8`](javadoc:StandardCharsets.UTF_8). This is no longer necessary. + +If you want the file as a sequence of lines, call + +```java +List lines = Files.readAllLines(path); +``` + +If the file is large, process the lines lazily as a [`Stream`](javadoc:Stream): + +```java +try (Stream lines = Files.lines(path)) { + . . . +} +``` + +Also use [`Files.lines`](javadoc:Files.lines(Path)) if you can naturally process lines with stream operations (such as [`map`](javadoc:Stream.map(Function)), [`filter`](javadoc:Stream.filter(Predicate))). Note that the stream returned by [`Files.lines`](javadoc:Files.lines(Path)) needs to be closed. To ensure that this happens, use a _try-with-resources_ statement, as in the preceding code snippet. + +There is no longer a good reason to use the [`readLine`](javadoc:BufferedReader.readLine()) method of [`java.io.BufferedReader`](javadoc:BufferedReader). + +To split your input into something else than lines, use a [`java.util.Scanner`](javadoc:Scanner). For example, here is how you can read words, separated by non-letters: + +```java +Stream tokens = new Scanner(path).useDelimiter("\\PL+").tokens(); +``` + +The [`Scanner`](javadoc:Scanner) class also has methods for reading numbers, but it is generally simpler to read the input as one string per line, or a single string, and then parse it. + +Be careful when parsing numbers from text files, since their format may be locale-dependent. For example, the input `100.000` is 100.0 in the US locale but 100000.0 in the German locale. Use [`java.text.NumberFormat`](javadoc:NumberFormat) for locale-specific parsing. Alternatively, you may be able to use [`Integer.parseInt`](javadoc:Integer.parseInt(String))/[`Double.parseDouble`](javadoc:Double.parseDouble(String)). + +  +## Writing Text Files + +You can write a string to a text file with a single call: + +```java +String content = . . .; +Files.writeString(path, content); +``` + +If you have a list of lines rather than a single string, use: + +```java +List lines = . . .; +Files.write(path, lines); +``` + +For more general output, use a [`PrintWriter`](javadoc:PrintWriter) if you want to use the [`printf`](javadoc:PrintWriter.printf()) method: + +```java +var writer = new PrintWriter(path.toFile()); +writer.printf(locale, "Hello, %s, next year you'll be %d years old!%n", name, age + 1); +``` + +Note that [`printf`](javadoc:PrintWriter.printf()) is locale-specific. When writing numbers, be sure to write them in the appropriate format. Instead of using [`printf`](javadoc:PrintWriter.printf()), consider [`java.text.NumberFormat`](javadoc:NumberFormat) or [`Integer.toString`](javadoc:Integer.toString())/[`Double.toString`](javadoc:Double.toString(double)). + +Weirdly enough, as of Java 21, there is no [`PrintWriter`](javadoc:PrintWriter) constructor with a [`Path`](javadoc:Path) parameter. + +If you don't use [`printf`](javadoc:PrintWriter.printf()), you can use the [`BufferedWriter`](javadoc:BufferedWriter) class and write strings with the [`write`](javadoc:BufferedWriter.write(int)) method. + +```java +var writer = Files.newBufferedWriter(path); +writer.write(line); // Does not write a line separator +writer.newLine(); +``` + +Remember to close the `writer` when you are done. + +  +## Reading From an Input Stream + +Perhaps the most common reason to use a stream is to read something from a web site. + +If you need to set request headers or read response headers, use the [`HttpClient`](javadoc:HttpClient): + +```java +HttpClient client = HttpClient.newBuilder().build(); +HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("https://horstmann.com/index.html")) + .GET() + .build(); +HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); +String result = response.body(); +``` + +That is overkill if all you want is the data. Instead, use: + +```java +InputStream in = new URI("https://horstmann.com/index.html").toURL().openStream(); +``` + +Then read the data into a byte array and optionally turn them into a string: + +```java +byte[] bytes = in.readAllBytes(); +String result = new String(bytes); +``` + +Or transfer the data to an output stream: + +```java +OutputStream out = Files.newOutputStream(path); +in.transferTo(out); +``` + +Note that no loop is required if you simply want to read all bytes of an input stream. + +But do you really need an input stream? Many APIs give you the option to read from a file or URL. + +Your favorite JSON library is likely to have methods for reading from a file or URL. For example, with [Jackson jr](https://github.com/FasterXML/jackson-jr): + +```java +URL url = new URI("https://dog.ceo/api/breeds/image/random").toURL(); +Map result = JSON.std.mapFrom(url); +``` + +Here is how to read the dog image from the preceding call: + +```java +URL url = new URI(result.get("message").toString()).toURL(); +BufferedImage img = javax.imageio.ImageIO.read(url); +``` + +This is better than passing an input stream to the [`read`](javadoc:ImageIO.read(URL)) method, because the library can use additional information from the URL to determine the image type. + +  +## The Files API + +The [`java.nio.file.Files`](javadoc:Files) class provides a comprehensive set of file operations, such as creating, copying, moving, and deleting files and directories. The [File System Basics](id:api.javaio.file_sytem.intro) tutorial provides a thorough description. In this section, I highlight a few common tasks. + +### Traversing Entries in Directories and Subdirectories + +For most situations you can use one of two methods. The [`Files.list`](javadoc:Files.list(Path)) method visits all entries (files, subdirectories, symbolic links) of a directory. + +```java +try (Stream entries = Files.list(pathToDirectory)) { + . . . +} +``` + +Use a _try-with-resources_ statement to ensure that the stream object, which keeps track of the iteration, will be closed. + +If you also want to visit the entries of descendant directories, instead use the method [`Files.walk`](javadoc:Files.walk(Path)) + +```java +Stream entries = Files.walk(pathToDirectory); +``` + +Then simply use stream methods to home in on the entries that you are interested in, and to collect the results: + +```java +try (Stream entries = Files.walk(pathToDirectory)) { + List htmlFiles = entries.filter(p -> p.toString().endsWith("html")).toList(); + . . . +} +``` + +Here are the other methods for traversing directory entries: + +* An overloaded version of [`Files.walk`](javadoc:Files.walk(Path,depth)) lets you limit the depth of the traversed tree. +* Two [`Files.walkFileTree`](javadoc:Files.walkFileTree(Path)) methods provide more control over the iteration process, by notifying a [`FileVisitor`](javadoc:FileVisitor) when a directory is visited for the first and last time. This can be occasionally useful, in particularly for emptying and deleting a tree of directories. See the tutorial [Walking the File Tree](id:api.javaio.file_sytem.walking_tree) for details. Unless you need this control, use the simpler [`Files.walk`](javadoc:Files.walk(Path)) method. +* The [`Files.find`](javadoc:Files.find(Path)) method is just like [`Files.walk`](javadoc:Files.walk(Path)), but you provide a filter that inspects each path and its [`BasicFileAttributes`](javadoc:BasicFileAttributes). This is slightly more efficient than reading the attributes separately for each file. +* Two [`Files.newDirectoryStream(Path)`](javadoc:Files.newDirectoryStream(Path)) methods yields [`DirectoryStream`](javadoc:DirectoryStream) instances, which can be used in enhanced `for` loops. There is no advantage over using [`Files.list`](javadoc:Files.list(Path)). +* The legacy [`File.list`](javadoc:File.list()) or [`File.listFiles`](javadoc:File.listFiles()) methods return file names or [`File`](javadoc:File) objects. These are now obsolete. + +### Working with ZIP Files + +Ever since Java 1.1, the [`ZipInputStream`](javadoc:ZipInputStream) and [`ZipOutputStream`](javadoc:ZipOutputStream) classes provide an API for processing ZIP files. But the API is a bit clunky. Java 8 introduced a much nicer *ZIP file system*: + +```java +try (FileSystem fs = FileSystems.newFileSystem(pathToZipFile)) { + . . . +} +``` + +The _try-with-resources_ statement ensures that the [`close`](javadoc:AutoCloseable.close()) method is called after the ZIP file operations. That method updates the ZIP file to reflect any changes in the file system. + +You can then use the methods of the [`Files`](javadoc:Files) class. Here we get a list of all files in the ZIP file: + +```java +try (Stream entries = Files.walk(fs.getPath("/"))) { + List filesInZip = entries.filter(Files::isRegularFile).toList(); +} +``` + +To read the file contents, just use [`Files.readString`](javadoc:Files.readString(Path)) or [`Files.readAllBytes`](javadoc:Files.readAllBytes(Path)): + +```java +String contents = Files.readString(fs.getPath("/LICENSE")); +``` + +You can remove files with [`Files.delete`](javadoc:Files.delete()). To add or replace files, simply use [`Files.writeString`](javadoc:Files.writeString()) or [`Files.write`](javadoc:Files.write()). + +### Creating Temporary Files and Directories + +Fairly often, I need to collect user input, produce files, and run an external process. Then I use temporary files, which are gone after the next reboot, or a temporary directory that I erase after the process has completed. + +I use the two methods [`Files.createTempFile`](javadoc:Files.createTempFile(String,String,FileAttribute)) and [`Files.createTempDirectory`](javadoc:Files.createTempDirectory(Path,String,FileAttribute...)) for that. + +```java +Path filePath = Files.createTempFile("myapp", ".txt"); +Path dirPath = Files.createTempDirectory("myapp"); +``` + +This creates a temporary file or directory in a suitable location (`/tmp` in Linux) with the given prefix and, for a file, suffix. + +  +## Conclusion + +Web searches and AI chats can suggest needlessly complex code for common I/O operations. There are often better alternatives: + +1. You don't need a loop to read or write strings or byte arrays. +2. You may not even need a stream, reader or writer. +3. Become familiar with the [`Files`](javadoc:Files) methods for creating, copying, moving, and deleting files and directories. +4. Use [`Files.list`](javadoc:Files.list(Path)) or [`Files.walk`](javadoc:Files.walk(Path)) to traverse directory entries. +5. Use a ZIP file system for processing ZIP files. +6. Stay away from the legacy [`File`](javadoc:File) class. + diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/00_javafx_overview.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/00_javafx_overview.md new file mode 100644 index 0000000..86f753e --- /dev/null +++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/00_javafx_overview.md @@ -0,0 +1,26 @@ +--- +id: javafx.fundamentals +title: "JavaFX Fundamentals" +slug: learn/javafx +slug_history: + - javafx +type: tutorial +category: javafx +category_order: 1 +group: rich-client-apps +layout: learn/tutorial-group-top.html +subheader_select: tutorials +main_css_id: learn +description: "Learn to create simple JavaFX applications." +author: ["GailC.Anderson", "PaulAnderson"] +byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.' +--- + +With the Java SDK and JavaFX installed on your system, let’s create some applications and explore the fundamentals of JavaFX. First, we’ll describe the basic structure of a JavaFX application along with selected features that make JavaFX a powerful choice for modern clients. We’ll show you how to create UIs that are appealing and responsive. We’ll look at FXML, the XML-based markup language that lets you define and configure your UI. We’ll also introduce Scene Builder, a stand-alone drag-and-drop utility for designing and configuring a JavaFX UI. + +To further refine or completely restyle your UI, JavaFX uses Cascading Style Sheets (CSS). We’ll show you several ways to use CSS with JavaFX. + +JavaFX properties provide a powerful binding mechanism. We’ll introduce JavaFX properties and binding. We’ll show why JavaFX observables and binding help create compact code that is less error prone than bulky listeners. We’ll also explore several layout controls and show you how easy it is to incorporate animation into your UI. + +We’ll finish the series with a sample application that implements a master-detail UI using JavaFX collections, an editable form, and buttons for typical database CRUD operations. +This, then, is meant to give you a taste of what is possible with JavaFX and to provide the basics for exploring JavaFX even further throughout this book. Let’s begin! \ No newline at end of file diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/01_app_structure.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/01_app_structure.md new file mode 100644 index 0000000..0793dfc --- /dev/null +++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/01_app_structure.md @@ -0,0 +1,184 @@ +--- +id: javafx.fundamentals.structure +title: JavaFX Application Basic Structure By Example +slug: learn/javafx/structure +type: tutorial-group +group: rich-client-apps +category: javafx +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- JavaFX Stage and Scene Graph {stage-scene} +- JavaFX Is Single-Threaded {single-threaded} +- Hierarchical Node Structure {hierarchical-node-structure} +- A Simple Shape Example {shape} +- Color {color} +- Text Is a Shape {text} +- The JavaFX Coordinate System {coordinate-system} +description: "Explore JavaFX application components and build a simple application." +last_update: 2023-09-11 +author: ["GailC.Anderson", "PaulAnderson"] +byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.' +--- + +  +## JavaFX Stage and Scene Graph + +A JavaFX application is controlled by the JavaFX platform, a runtime system that builds your application object and constructs the `JavaFX Application Thread`. To build a JavaFX application, you must extend the `JavaFX Application` class. +The JavaFX runtime system controls the Application lifecycle and invokes the Application `start()` method. + +JavaFX uses a theater metaphor: the top-level container is the [`Stage`](javafxdoc:Stage) and is constructed by the platform for you. In desktop applications, the [`Stage`](javafxdoc:Stage) is the window. Its appearance depends on the host system and varies among Mac OS X, Windows, and Linux platforms. +Normally, the window is decorated with controls that resize, minimize, and quit your application. It’s also possible to construct undecorated windows. You can specialize the Application class for other environments, too. For example, with the `Gluon Mobile Application` framework, your program extends Mobile Application, an application class specifically written for mobile devices. +  +## JavaFX Is Single-Threaded + +You must always construct and modify the [`Stage`](javafxdoc:Stage) and its scene objects on the `JavaFX Application Thread`. Note that JavaFX (like `Swing`) is a single-threaded UI model. For the JavaFX developer, this is mostly a straightforward restriction. +As you create UI elements, respond to event handlers, manage dynamic content with animation, or make changes in the scene graph, work continues to execute on the JavaFX Application Thread. + +To keep the UI responsive, however, you should assign long-running work to background tasks in separate threads. In this case, work that modifies the UI must be separate from work being executed on a background thread. +Fortunately, JavaFX has a well-developed concurrency API that helps developers assign long-running tasks to one or more separate threads. This keeps the UI thread responsive to user events. +  +## Hierarchical Node Structure + +Continuing with the theater metaphor, the [`Stage`](javafxdoc:Stage) holds a scene. The scene consists of JavaFX elements such as the root, which is the top scene element and contains what is called the scene graph. + +The scene graph is a strictly hierarchical structure of elements that visualize your application. These elements are called Nodes. A Node has exactly one parent (except the root node) and may contain other Nodes. Or, a Node can be a leaf node with no children. Nodes must be added to the scene graph in order to participate in the rendering of that scene. +Furthermore, a Node may be added only once to a scene, unless it is first removed and then added somewhere else. + +Parent nodes in general manage their children by arranging them within the scene according to layout rules and any constraints you configure. JavaFX uses a two-dimensional coordinate system for 2D graphics with the origin at the upper-left corner of the scene, +as shown in the figure below. Coordinate values on the x-axis increase to the right, and y-axis values increase as you move down the scene. + +[![JavaFX 2D coordinate system](/assets/images/javafx/javafx-coordinates.png)](/assets/images/javafx/javafx-coordinates.png) + +JavaFX also supports 3D graphics and represents the third dimension with z-axis values, providing depth. +JavaFX has an absolute coordinate system, in addition to local coordinate systems that are relative to the parent. In each case, the coordinate system’s origin is the upper-left corner of the parent. +In general, layout controls hide the complexities of component placement within the scene and manage the placement of its children for you. Component placement is based on the specific layout control and how you configure it. +It’s also possible to nest layout controls. For example, you can place multiple VBox controls in an [`HBox`](javafxdoc:HBox) or put an [`AnchorPane`](javafxdoc:AnchorPane) into one pane of a [`SplitPane`](javafxdoc:SplitPane) control. Other parent nodes are more complex visual nodes, such as [`TextField`](javafxdoc:TextField), [`TextArea`](javafxdoc:TextArea), and [`Button`](javafxdoc:Button). +These nodes have managed subparts. For example, [`Button`](javafxdoc:Button) includes a labeled text part and optional graphic. This graphic can be any node type but is typically an image or icon. + +Recall that leaf nodes have no child nodes. Examples include [`Shape`](javafxdoc:Shape) (such as [`Rectangle`](javafxdoc:Rectangle), [`Ellipse`](javafxdoc:Ellipse), [`Line`](javafxdoc:Line), [`Path`](javafxdoc:Path), and [`Text`](javafxdoc:Text)) and [`ImageView`](javafxdoc:ImageView), a node for rendering an image. + +Just a word of warning: you should be using a plain text editor to create and save this file. Using a word processor will not work. +  +## A Simple Shape Example + +The picture below shows a simple JavaFX application called `MyShapes` that displays an ellipse and a text element centered in an application window. The appearance of this window varies depending on the underlying platform. +When you resize the window, the visible elements will remain centered in the resized space. Even though this is a simple program, there’s much to learn here about JavaFX rendering, layout features, and nodes. + +[![MyShapes application](/assets/images/javafx/myshapes-application.png)](/assets/images/javafx/myshapes-application.png) + +The source code for this application is in the `MyShapes` program. Class `MyShapes` is the main class and extends [`Application`](javafxdoc:Application). The JavaFX runtime system instantiates `MyShapes` as well as the primary Stage, which it passes to the overridden `start()` method. The runtime system invokes the `start()` method for you. + +```java +package org.modernclient; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Ellipse; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; +public class MyShapes extends Application { + @Override + public void start(Stage stage) throws Exception { + // Create an Ellipse and set fill color + Ellipse ellipse = new Ellipse(110, 70); + ellipse.setFill(Color.LIGHTBLUE); + // Create a Text shape with font and size + Text text = new Text("My Shapes"); + text.setFont(new Font("Arial Bold", 24)); + StackPane stackPane = new StackPane(); + stackPane.getChildren().addAll(ellipse, text); + Scene scene = new Scene(stackPane, 350, 230, + Color.LIGHTYELLOW); + stage.setTitle("MyShapes with JavaFX"); + stage.setScene(scene); + stage.show(); + } + public static void main(String[] args) { + launch(args); + } +} +``` + +Note the import statements that reference packages in `javafx.application`, `javafx.scene`, and `javafx.stage`. + +--- +**NOTE**: Be sure to specify the correct package for any import statements. Some JavaFX classes, such as Rectangle, have the same class name as their AWT or Swing counterparts. All JavaFX classes are part of package `javafx`. + +--- + +This program creates several nodes and adds them to a [`StackPane`](javafxdoc:StackPane) layout container. The program also creates the scene, configures the stage, and shows the stage. Let’s look at these steps in detail. + +First, we create an [`Ellipse`](javafxdoc:Ellipse) shape, providing a width and height in pixels. Since [`Ellipse`](javafxdoc:Ellipse) extends [`Shape`](javafxdoc:Shape), we can also configure any [`Shape`](javafxdoc:Shape) property. This includes fill, which lets you specify an interior paint value. +  +## Color + +A [`Shape`](javafxdoc:Shape)’s fill property can be a JavaFX color, a linear gradient, a radial gradient, or an image. Let’s briefly discuss color. You can specify colors in JavaFX several ways. +Here, we set the [`Ellipse`](javafxdoc:Ellipse) fill property to `Color.LIGHTBLUE`. + +```java +// Create an Ellipse and set fill color +Ellipse ellipse = new Ellipse(110, 70); +ellipse.setFill(Color.LIGHTBLUE); +``` + +There are currently 147 predefined colors in the JavaFX Color class, named alphabetically from `ALICEBLUE` to `YELLOWGREEN`. However, you can also specify [`Color`](javafxdoc:Color) using web RGB values with either hexadecimal notation or decimal numbers. +You can optionally provide an alpha value for transparency. Fully opaque is 1 and fully transparent is 0. A transparency of .5, for example, shows the color but lets the background color show through as well. +Here are a few examples that set a shape’s fill with [`Color`](javafxdoc:Color): + +```java +ellipse.setFill(Color.LIGHTBLUE); // Light blue, fully opaque +ellipse.setFill(Color.web("#ADD8E6")); // Light blue, fully opaque +ellipse.setFill(Color.web("#ADD8E680")); // Light blue, .5 opaque +ellipse.setFill(Color.web("0xADD8E6")); // Light blue, fully opaque +ellipse.setFill(Color.web("0xADD8E680")); // Light blue, .5 opaque +ellipse.setFill(Color.rgb(173, 216, 230)); // Light blue, fully opaque +ellipse.setFill(Color.rgb(173, 216, 230, .5)); // Light blue, .5 opaque +``` + +Notably, you can interpolate a color’s values, and that is how JavaFX constructs gradients. We’ll show you how to create a linear gradient shortly. +  +## Text + +We next create a Text object. Text is also a [`Shape`](javafxdoc:Shape) with additional properties, such as font, text alignment, text, and wrapping width. The constructor provides the text and the `setFont()` method sets its font. + +```java +// Create a Text shape with font and size +Text text = new Text("My Shapes"); +text.setFont(new Font("Arial Bold", 24)); +``` +  +## The JavaFX Coordinate System + +Note that we created the ellipse and text nodes, but they are not yet in our scene graph. Before we add them to the scene, we must put these nodes in some kind of layout container. Layout controls are extremely important in managing your scene graph. +These controls not only arrange components for you but also respond to events such as resizing, the addition or removal of elements, and any changes to the sizes of one or more nodes in the scene graph. + +To show you just how important layout controls are, let’s replace the [`StackPane`](javafxdoc:StackPane) from the original example with a [`Group`](javafxdoc:Group) and specify the placement manually. +[`Group`](javafxdoc:Group) is a parent node that manages its children but does not provide any layout capability. Here we create a group and add the ellipse and text elements with the constructor. We then specify group as the scene’s root node: + +```java +Group group = new Group(ellipse, text); +... +Scene scene = new Scene(group, 350, 230, Color.LIGHTYELLOW); +``` + +Group uses default alignment settings for its children and places everything at the origin (0,0), the upper-left corner of the scene. For Text, the default placement is the bottom-left edge of the text element. +In this case, the only visible portions will be the letters that extend below the bottom edge (the lower case `y` and `p` letters of `MyShapes`). The ellipse will be centered at the group origin (0,0), and therefore only the lower-right quadrant will be visible. +This arrangement is clearly not what we want. To fix this, let’s manually center the shapes in the 350 × 230 scene, as follows: + +```java +Group group = new Group(ellipse, text); +// Manually placing components is tedious and error-prone +ellipse.setCenterX(175); +ellipse.setCenterY(115); +text.setX(175-(text.getLayoutBounds().getWidth()/2)); +text.setY(115+(text.getLayoutBounds().getHeight()/2)); +. . . +Scene scene = new Scene(group, 350, 230, Color.LIGHTYELLOW); +``` + +Now the shapes will be nicely centered in the scene. But this is still not ideal. The shapes will remain stuck in the scene at these coordinates when the window resizes (unless you write code that detects and reacts to window resizing). +And you don’t want to do that. Instead, use JavaFX layout controls! diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/02_layout_controls.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/02_layout_controls.md new file mode 100644 index 0000000..451c74c --- /dev/null +++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/02_layout_controls.md @@ -0,0 +1,160 @@ +--- +id: javafx.fundamentals.layout +title: JavaFX Layout Controls +slug: learn/javafx/layout +type: tutorial-group +group: rich-client-apps +category: javafx +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Introduction {intro} +- StackPane {stackpane} +- AnchorPane {anchorpane} +- GridPane {gridpane} +- FlowPane and TilePane {flow-tile-pane} +- BorderPane {borderpane} +- SplitPane {splitpane} +- HBox, VBox, and ButtonBar {box-button} +- Make a Scene {make-scene} +description: "Let's take a tour of some common JavaFX layout controls." +last_update: 2023-09-12 +author: ["GailC.Anderson", "PaulAnderson"] +byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.' +--- + +  +## Introduction +To manage the nodes of a scene, you use one or more of these controls. Each control is designed for a particular layout configuration. Furthermore, you can nest layout controls to manage groups of nodes and specify how the layout should react to events, such as resizing or changes to the managed nodes. +You can specify alignment settings as well as margin controls and padding. + +There are several ways to add nodes to layout containers. You can add child nodes with the layout container’s constructor. You can also use method `getChildren().add()` for a single node and method `getChildren().addAll()` for multiple nodes. In addition, some layout controls have specialized methods for adding nodes. +Let’s look at a few commonly used layout controls now to show you how JavaFX can compose a scene for you. +  +## StackPane + +A convenient and easy layout container is [`StackPane`](javafxdoc:StackPane). This layout control stacks its children from back to front in the order that you add nodes. Note that we add the ellipse first so that it appears behind the text node. In the opposite order, the ellipse would obscure the text element. + +By default, [`StackPane`](javafxdoc:StackPane) centers all of its children. You can provide a different alignment for the children or apply an alignment to a specific node in the [`StackPane`](javafxdoc:StackPane). For example, + +```java +// align the text only +stackPane.setAlignment(text, Pos.BOTTOM_CENTER); +``` + +centers the text node along the bottom edge of the StackPane. Now when you resize the window, the ellipse remains centered, and the text remains anchored to the bottom edge of the window. To specify the alignment of all managed nodes to the bottom edge, use + +```java +// align all managed nodes +stackPane.setAlignment(Pos.BOTTOM_CENTER); +``` + +Although both the ellipse and the text appear at the bottom of the window, they won’t be centered relative to each other. Why not? +  +## AnchorPane + +[`AnchorPane`](javafxdoc:AnchorPane) manages its children according to configured anchor points, even when a container resizes. You specify an offset from the pane’s edge for a component. Here, we add a Label to an AnchorPane and anchor it to the lower-left side of the pane with a 10-pixel offset: +```java +AnchorPane anchorPane = new AnchorPane(); +Label label = new Label("My Label"); +anchorPane.getChildren().add(label); +AnchorPane.setLeftAnchor(label, 10.0); +AnchorPane.setBottomAnchor(label, 10.0); +``` + +[`AnchorPane`](javafxdoc:AnchorPane) is typically used as a top-level layout manager for controlling margins, even when the window is resized. +  +## GridPane + +[`GridPane`](javafxdoc:GridPane) lets you place child nodes in a flexibly sized two-dimensional grid. Components can span rows and/or columns, but the row size is consistent for all components in a given row. Similarly, the column’s width is consistent for a given column. +[`GridPane`](javafxdoc:GridPane) has specialized methods that add nodes to a particular cell designated by a column and row number. Optional arguments let you specify column and row span values. +For example, the first label here is placed in the cell corresponding to column 0 and row 0. The second label goes into the cell corresponding to column 1 and row 0, and it spans two columns (the second and third columns). We must also provide a row span value (here it is set to 1): + +```java +GridPane gridPane = new GridPane(); +gridPane.add(new Label("Label1"), 0, 0); +gridPane.add(new Label("Label2 is very long"), 1, 0, 2, 1); +``` + +[`GridPane`](javafxdoc:GridPane) is useful for laying out components in forms that accommodate columns or rows of various sizes. [`GridPane`](javafxdoc:GridPane) also allows nodes to span either multiple columns or rows. +We use [`GridPane`](javafxdoc:GridPane) in our master-detail UI example (see _Putting It All Together_ section of this series). +  +## FlowPane and TilePane + +[`FlowPane`](javafxdoc:FlowPane) manages its children in either a horizontal or vertical flow. The default orientation is horizontal. You can specify the flow direction with the constructor or use method `setOrientation()`. Here, we specify a vertical orientation with the constructor: + +```java +FlowPane flowpane = new FlowPane(Orientation.VERTICAL); +``` +[`FlowPane`](javafxdoc:FlowPane) wraps child nodes according to a configurable boundary. If you resize a pane that contains a [`FlowPane`](javafxdoc:FlowPane), the layout will adjust the flow as needed. The size of the cells depends on the size of the nodes, and it will not be a uniform grid unless all the nodes are the same size. This layout is convenient for nodes whose sizes can vary, such as `ImageView` nodes or shapes. `TilePane` is similar to [`FlowPane`](javafxdoc:FlowPane), except `TilePane` uses equal-sized cells. +  +## BorderPane + +[`BorderPane`](javafxdoc:BorderPane) is convenient for desktop applications with discreet sections, including a top toolbar (Top), a bottom status bar (Bottom), a center work area (Center), and two side areas (Right and Left). +Any of the five sections can be empty. Here is an example of a [`BorderPane`](javafxdoc:BorderPane) with a rectangle in the center and a label at the top: + +```java +BorderPane borderPane = new BorderPane(); +Label colorLabel = new Label("Color: Lightblue"); +colorLabel.setFont(new Font("Verdana", 18)); +borderPane.setTop(colorLabel); +Rectangle rectangle = new Rectangle(100, 50, Color.LIGHTBLUE); +borderPane.setCenter(rectangle); +BorderPane.setAlignment(colorLabel, Pos.CENTER); +BorderPane.setMargin(colorLabel, new Insets(20,10,5,10)); +``` +Note that [`BorderPane`](javafxdoc:BorderPane) uses a center alignment by default for the center area and a left alignment for the top. To keep the top area label centered, we configure its alignment with `Pos.CENTER`. We also set margins around the label with BorderPane static method `setMargin()`. The `Insets` constructor takes four values corresponding to the top, right, bottom, and left edges. Similar alignment and margin configurations apply to other layout components, too. +  +## SplitPane + +[`SplitPane`](javafxdoc:SplitPane) divides the layout space into multiple horizontally or vertically configured areas. The divider is movable, and you typically use other layout controls in each of [`SplitPane`](javafxdoc:SplitPane)’s areas. +We use [`SplitPane`](javafxdoc:SplitPane) in our master-detail UI example (checkout _Putting It All Together_ part of these series). +  +## HBox, VBox, and ButtonBar + +The [`HBox`](javafxdoc:HBox) and [`VBox`](javafxdoc:VBox) layout controls provide single horizontal or vertical placements for child nodes. You can nest [`HBox`](javafxdoc:HBox) nodes inside a [`VBox`](javafxdoc:VBox) for a grid-like effect or nest `VBox` nodes inside an `HBox` component. +[`ButtonBar`](javafxdoc:ButtonBar) is convenient for placing a row of buttons of equal size in a horizontal container. +  +## Make a Scene + +Returning to `MyShapes`, the Scene holds the scene graph, defined by its root node. + +```java +package org.modernclient; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Ellipse; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; +public class MyShapes extends Application { + @Override + public void start(Stage stage) throws Exception { + // Create an Ellipse and set fill color + Ellipse ellipse = new Ellipse(110, 70); + ellipse.setFill(Color.LIGHTBLUE); + // Create a Text shape with font and size + Text text = new Text("My Shapes"); + text.setFont(new Font("Arial Bold", 24)); + StackPane stackPane = new StackPane(); + stackPane.getChildren().addAll(ellipse, text); + Scene scene = new Scene(stackPane, 350, 230, + Color.LIGHTYELLOW); + stage.setTitle("MyShapes with JavaFX"); + stage.setScene(scene); + stage.show(); + } + public static void main(String[] args) { + launch(args); + } +} + +``` +First, we construct the [`Scene`](javafxdoc:Scene) and provide `stackPane` as the root node. We then specify its width and height in pixels and supply an optional fill argument for the background (`Color.LIGHTYELLOW`). +What’s left is to configure the Stage. We provide a title, set the scene, and show the stage. The JavaFX runtime renders our scene. +Below is a hierarchical view of the scene graph for our `MyShapes` application. The root node is the [`StackPane`](javafxdoc:StackPane), which contains its two child nodes, `Ellipse` and `Text`. + +[![MyShapes scene graph](/assets/images/javafx/myshapes-scene-graph.png)](/assets/images/javafx/myshapes-scene-graph.png) diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/03_effects_gradients.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/03_effects_gradients.md new file mode 100644 index 0000000..3077fd6 --- /dev/null +++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/03_effects_gradients.md @@ -0,0 +1,147 @@ +--- +id: javafx.fundamentals.effects +title: Effects, Gradients and Animations +slug: learn/javafx/effects +type: tutorial-group +group: rich-client-apps +category: javafx +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Enhancing the MyShapes Application {enhance-app} +- Linear Gradient {linear-gradient} +- DropShadow {drop-shadow} +- Reflection {reflection} +- Configuring Actions {config-actions} +- Animation {animation} +description: "Learn how to apply effects, gradients, animations to nodes in your scene graph. " +last_update: 2023-09-12 +author: ["GailC.Anderson", "PaulAnderson"] +byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.' +--- + +  +## Enhancing the MyShapes Application + +One of the advantages of JavaFX over older UI toolkits is the ease in which you can apply effects, gradients, and animation to nodes in your scene graph. We will return to the concept of scene graph nodes repeatedly, since that’s how the JavaFX runtime efficiently renders the visual parts of your application. +Let’s apply some modifications to `MyShapes` now to show you some of these features. Because JavaFX is able to interpolate colors, you can use colors to define gradients. Gradients give depth to a shape and can be either radial or linear. Let’s show you a linear gradient. +  +## Linear Gradient + +Linear gradients require two or more colors, called Stops. A gradient stop consists of a color and an offset between 0 and 1. This offset specifies where to place the color along the gradient. The gradient calculates the proportional shading from one color stop to the next. +In our example, we’ll use three color stops: `Color.DODGERBLUE`, `Color.LIGHTBLUE`, and `Color.GREEN`. The first stop will have offset 0, the second offset .5, and the third offset 1.0, as follows: + +```java +Stop[] stops = new Stop[] { new Stop(0, Color.DODGERBLUE), + new Stop(0.5, Color.LIGHTBLUE), + new Stop(1.0, Color.LIGHTGREEN)}; +``` + +The [`LinearGradient`](javafxdoc:LinearGradient) constructor specifies the x-axis range followed by the y-axis range. The following linear gradient has a constant x-axis but varies its y-axis. This is called a vertical gradient. + +```java +// startX=0, startY=0, endX=0, endY=1 +LinearGradient gradient = new LinearGradient(0, 0, 0, 1, true,CycleMethod.NO_CYCLE, stops); +``` + +Boolean true indicates the gradient stretches through the shape (where 0 and 1 are proportional to the shape), and `NO_CYCLE` means the pattern does not repeat. Boolean false indicates the gradient’s x and y values are instead relative to the local coordinate system of the parent. +To make a horizontal gradient, specify a range for the x-axis and make the y-axis constant, as follows: + +```java +// startX=0, startY=0, endX=1, endY=0 +LinearGradient gradient = new LinearGradient(0, 0, 1, 0, true,CycleMethod.NO_CYCLE, stops); +``` +Other combinations let you specify diagonal gradients or reverse gradients, where colors appear in the opposite order. +  +## DropShadow +Next, let’s add a drop shadow effect to the ellipse. You specify the color of the drop shadow, as well as a radius and x and y offsets. The larger the radius, the larger the shadow. +The offsets represent the shadow placement relative to the outer edge of the shape. Here, we specify a radius of 30 pixels with an offset of 10 pixels to the right and below the shape: + +```java +ellipse.setEffect(new DropShadow(30, 10, 10, Color.GRAY)); +``` +These offsets simulate a light source emanating from the upper left of the scene. When the offsets are 0, the shadow surrounds the entire shape, as if the light source were shining directly above the scene. +  +## Reflection + +A reflection effect mirrors a component and fades to transparent, depending on how you configure its top and bottom opacities, fraction, and offset. Let’s add a reflection effect to our Text node. We’ll use `.8` for the fraction, so that the reflection will be eight-tenths of the reflected component. The offset specifies how far below the bottom edge the reflection starts in pixels. +We specify 1 pixel (the default is 0). The reflection starts at fully opaque (top opacity) and transitions to fully transparent (bottom opacity) unless you modify the top and bottom opacity values: +```java +Reflection r = new Reflection(); +r.setFraction(.8); +r.setTopOffset(1.0); +text.setEffect(r); +``` + +You can observe below the enhanced MyShapes program running in a window. You see the linear gradient fill applied to the ellipse, a drop shadow on the ellipse, and the reflection effect applied to the text. +[![Enhanced MyShapes application (MyShapes2)](/assets/images/javafx/enahanced-myshapes-application.png)](/assets/images/javafx/enahanced-myshapes-application.png) +  +## Configuring Actions + +Now it’s time to make our application do something. JavaFX defines various types of standard input events with the mouse, gestures, touch, or keys. These input event types each have specific handlers that process them. + +Let’s keep things simple for now. We’ll show you how to write an event handler to process a single mouse click event. We’ll create the handler and attach it to a node in our scene graph. +The program’s behavior will vary depending on which node acquires the handler. We can configure the mouse click handler on the text, ellipse, or stack pane node. +Here’s the code to add an action event handler to the text node: + +```java +text.setOnMouseClicked(mouseEvent -> { + System.out.println(mouseEvent.getSource().getClass() + " clicked."); +}); +``` +When the user clicks inside the text, the program displays the line `class javafx.scene.text.Text` clicked. + +If the user clicks in the background area (the stack pane) or inside the ellipse, nothing happens. If we attach the same listener to the ellipse instead of the text, we see the line +`class javafx.scene.shape.Ellipse` clicked. + +Note that because the text object appears in front of the ellipse in the stack pane, clicking the text object does not invoke the event handler. Even though these scene graph nodes appear on top of each other, they are separate nodes in the hierarchy. +That is, one isn’t inside the other; rather, they are both distinct leaf nodes managed by the stack pane. In this case, if you want both nodes to respond to a mouse click, you would attach the mouse event handler to both nodes. Or you could attach just one event handler to the stack pane node. Then, a mouse click anywhere inside the window triggers the handler with the following output line: +`class javafx.scene.layout.StackPane` clicked. +Let’s do something a bit more exciting and apply an animation to the `MyShape`s program. +  +## Animation +JavaFX makes animation very easy when you use the built-in transition APIs. Each JavaFX Transition type controls one or more Node (or Shape) properties. For example, the `FadeTransition` controls a node’s opacity, varying the property over time. To fade something out gradually, you change its opacity from fully opaque (1) to completely transparent (0). The `TranslateTransition` moves a node by modifying its translateX and translateY properties (or translateZ if you’re working in 3D). + +You can play multiple transitions in parallel with a [`ParallelTransition`](javafxdoc:ParallelTransition) or sequentially with a [`SequentialTransition`](javafxdoc:SequentialTransition). To control timing between two sequential transitions, use [`PauseTransition`](javafxdoc:PauseTransition) or configure a delay before a transition begins with [`Transition`](javafxdoc:Transition) method `setDelay()`. You can also define an action when a [`Transition`](javafxdoc:Transition) completes using the [`Transition`](javafxdoc:Transition) action event handler property `onFinished`. + +Transitions begin with method `play()` or `playFromStart()`. Method `play()` starts the transition at its current time; method `playFromStart()` always begins at time 0. Other methods include `stop()` and `pause()`. You can query a transition’s status with `getStatus()`, which returns one of the [`Animation.Status`](javafxdoc:Animation.Status) enum values: `RUNNING`, `PAUSED`, or `STOPPED`. + +All transitions support the common properties `duration, autoReverse, cycleCount, onFinished, currentTime`, and either node or shape (for Shape-specific transitions). + +Let’s define a [`RotateTransition`](javafxdoc:RotateTransition) now for our `MyShapes` program. The rotation begins when a user clicks inside the window. + +```java +public class MyShapes extends Application { + @Override + public void start(Stage stage) throws Exception { + . . . + // Define RotateTransition + RotateTransition rotate = new RotateTransition( + Duration.millis(2500), stackPane); + rotate.setToAngle(360); + rotate.setFromAngle(0); + rotate.setInterpolator(Interpolator.LINEAR); + // configure mouse click handler + stackPane.setOnMouseClicked(mouseEvent -> { + if (rotate.getStatus().equals(Animation.Status.RUNNING)) { + rotate.pause(); + } else { + rotate.play(); + } + }); + . . . + } +} +``` + + +The [`RotateTransition`](javafxdoc:RotateTransition) constructor specifies a duration of 2500 milliseconds and applies the transition to the [`StackPane`](javafxdoc:StackPane) node. The rotation animation begins at angle 0 and proceeds linearly to angle 360, providing one full rotation. The animation starts when the user clicks anywhere inside the [`StackPane`](javafxdoc:StackPane) layout control. + +There are a few interesting things to notice in this example. First, because we define the transition on the [`StackPane`](javafxdoc:StackPane) node, the rotation applies to all of the [`StackPane`](javafxdoc:StackPane)’s children. This means that not only will the [`Ellipse`](javafxdoc:Ellipse) and [`Text`](javafxdoc:Text) shapes rotate, but the drop shadow and reflection effects rotate, too. + +Second, the event handler checks the transition status. If the animation is in progress (running), the event handler pauses the transition. If it’s not running, it starts it up with `play()`. Because `play()` starts at the transition’s current time, a `pause()` followed by `play()` resumes the transition where it was paused. + +Below you can see the program running during the rotate transition. + +[![MyShapes application with RotateTransition (MyShapes2)](/assets/images/javafx/rotate-transition-myshapes-application.png)](/assets/images/javafx/rotate-transition-myshapes-application.png) diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/04_javafx_properties.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/04_javafx_properties.md new file mode 100644 index 0000000..c9f1d62 --- /dev/null +++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/04_javafx_properties.md @@ -0,0 +1,154 @@ +--- +id: javafx.fundamentals.properties +title: JavaFX Properties +slug: learn/javafx/properties +type: tutorial-group +group: rich-client-apps +category: javafx +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Introduction {intro} +- Invalidation Listeners {invalidation-listeners} +- Change Listeners {change-listener} +- Binding {binding} +- Bidirectional Binding {unidir-binding} +- Unidirectional Binding {bidir-binding} +- Fluent API and Bindings API {fluent-api} +description: "Control nodes by manipulating their properties. " +last_update: 2023-09-12 +author: ["GailC.Anderson", "PaulAnderson"] +byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.' +--- + +  +## Introduction + +JavaFX property listeners that apply to object properties (not collections) come in two flavors: invalidation listeners and change listeners. Invalidation listeners fire when a property’s value is no longer valid. +For this example and the ones that follow, we’ll discuss the MyShapesProperties program, which is based on the previous `MyShapes` application. In this new program, we’ve added a second [`Text`](javafxdoc:Text) object placed in a [`VBox`](javafxdoc:VBox) layout control below the rotating [`StackPane`](javafxdoc:StackPane). +Below you can see the updated scene graph with the top-level [`VBox`](javafxdoc:VBox). + +[![MyShapesProperties scene graph](/assets/images/javafx/myshapes-properties.png)](/assets/images/javafx/myshapes-properties.png) +  +## Invalidation Listeners + +Invalidation listeners have a single method that you override with lambda expressions. Let’s show you the non-lambda expression first, so you can see the full method definition. +When you click the [`StackPane`](javafxdoc:StackPane), the mouse click handler rotates the [`StackPane`](javafxdoc:StackPane) control as before. The second [`Text`](javafxdoc:Text) object displays the status of the [`RotationTransition`](javafxdoc:RotationTransition) animation, which is managed by the read-only status property. +You’ll see either RUNNING, PAUSED, or STOPPED. The figure below shows the animation paused. + +[![MyShapesProperties application with an invalidation listener](/assets/images/javafx/myshapes-properties-invalidation.png)](/assets/images/javafx/myshapes-properties-invalidation.png) + +The invalidation listener includes an observable object that lets you access the property. Because the observable is nongeneric, you must apply an appropriate type cast to access the property value. +Here’s one way to access the value of the animation’s status property in a listener attached to that property. Note that we attach the listener with the property getter method `statusProperty()`: + +```java +rotate.statusProperty().addListener(new InvalidationListener() { + @Override + public void invalidated(Observable observable) { + text2.setText("Animation status: " + + ((ObservableObjectValue)observable).getValue()); + } +}); +``` + +Here we implement the same listener with a lambda expression: + +```java +rotate.statusProperty().addListener(observable -> { + text2.setText("Animation status: " + + ((ObservableObjectValue)observable).getValue()); +}); +``` + +Since we access just the status property value, we can bypass the observable with method `getStatus()`, which returns an enum. This avoids the casting expression: + +```java +rotate.statusProperty().addListener(observable -> { + text2.setText("Animation status: " + rotate.getStatus()); +}); +``` +  +## Change Listeners + +When you need access to the previous value of an observable as well as its current value, use a change listener. Change listeners provide the observable and the new and old values. +Change listeners can be more expensive, since they must keep track of more information. Here’s the non-lambda version of a change listener that displays both the old and new values. +Note that you don’t have to cast these parameters, since change listeners are generic: + +```java +rotate.statusProperty().addListener( + new ChangeListener() { + @Override + public void changed(ObservableValue observableValue, + Animation.Status oldValue, Animation.Status newValue) { + text2.setText("Was " + oldValue + ", Now " + newValue); + } +}); +``` +Here’s the version with a more compact lambda expression: + +```java +rotate.statusProperty().addListener( + (observableValue, oldValue, newValue) -> { + text2.setText("Was " + oldValue + ", Now " + newValue); +}); +``` + +Below you can see the `MyShapesProperties` running with a change listener attached to the animation’s status property. Now we can display both the previous and current values. + +[![MyShapesProperties application with a change listener](/assets/images/javafx/myshapes-properties-change.png)](/assets/images/javafx/myshapes-properties-change.png) +  +## Binding + +JavaFX binding is a flexible, API-rich mechanism that lets you avoid writing listeners in many situations. You use binding to link the value of a JavaFX property to one or more other JavaFX properties. +Property bindings can be unidirectional or bidirectional. When properties are the same type, the unidirectional `bind()` method may be all you need. +However, when properties have different types or you want to compute a value based on more than one property, then you’ll need the fluent and bindings APIs. +You can also create your own binding methods with custom binding. +  +## Unidirectional Binding + +The simplest form of binding links the value of one property to the value of another. Here, we bind text2’s rotate property to `stackPane`’s rotate property: + +```java +text2.rotateProperty().bind(stackPane.rotateProperty()); +``` + +This means any changes to `stackPane`’s rotation will immediately update text2’s rotate property. When this binding is set in the `MyShapesProperties` program, any clicks inside the [`StackPane`](javafxdoc:StackPane) initiate a rotate transition. +This makes both the [`StackPane`](javafxdoc:StackPane) and text2 components rotate together. The [`StackPane`](javafxdoc:StackPane) rotates because we start the `RotateTransition` defined for that node. The text2 node rotates because of the bind expression. + +Note that when you bind a property, you cannot explicitly set its value unless you unbind the property first. +  +## Bidirectional Binding + +Bidirectional binding provides a two-way relationship between two properties. When one property updates, the other also updates. Here’s an example with two text properties: + +```java +text2.textProperty().bindBidirectional(text.textProperty()); +``` +Both text controls initially display "My Shapes". When the user clicks inside the `stackPane` and the `stackPane` rotates, both text properties will now contain the animation status because of the change listener. + +Bidirectional binding is not completely symmetrical; the initial value of both properties takes on the value of the property passed in the call to `bindBidirectional()`. Unlike `bind()`, you can explicitly set either property when using bidirectional binding. +  +## Fluent API and Bindings API + +The fluent and bindings APIs help you construct bind expressions when more than one property needs to participate in a binding or when it’s necessary to perform some sort of calculation or conversion. +For example, the following bind expression displays the rotation angle of the [`StackPane`](javafxdoc:StackPane) as it rotates from 0 to 360 degrees. The text property is a `String`, and the rotate property is a double. +The binding method `asString()` converts the double to [`String`](javadoc:String), formatting the number with a single digit to the right of the decimal point: + +```java +text2.textProperty().bind(stackPane.rotateProperty().asString("%.1f")); +``` + +For a more complex example, let’s update `text2`’s stroke property (its color) depending on whether the animation is running or not. Here we construct a binding with [`When`](javafxdoc:When) based on a ternary expression. +This sets the stroke color to green when the animation is running and to red when the animation is stopped or paused: + +```java +text2.strokeProperty().bind(new When(rotate.statusProperty() + .isEqualTo(Animation.Status.RUNNING)) + .then(Color.GREEN).otherwise(Color.RED)); +``` + +The `text2` text property is set in the change listener that is attached to the animation status property we showed earlier. +You can see below the application `MyShapesProperties` with the complex bind expression attached to the `text2 strokeProperty`. Since the animation is running, the stroke property is set to `Color.GREEN`. + +[![MyShapesProperties application with the fluent and bindings APIs](/assets/images/javafx/myshapes-properties-fluent.png)](/assets/images/javafx/myshapes-properties-fluent.png) diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/05_fxml.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/05_fxml.md new file mode 100644 index 0000000..c58e4a1 --- /dev/null +++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/05_fxml.md @@ -0,0 +1,247 @@ +--- +id: javafx.fundamentals.fxml +title: Using FXML +slug: learn/javafx/fxml +type: tutorial-group +group: rich-client-apps +category: javafx +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Declare Scene Graph Nodes with FXML {intro} +- Controller Class {controller-class} +- JavaFX Application Class {javafx-app-class} +- Adding CSS {add-css} +- Using Scene Builder {scene-builder} +description: "Control nodes by manipulating their properties. " +last_update: 2023-09-12 +author: ["GailC.Anderson", "PaulAnderson"] +byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.' +--- + +  +## Declare Scene Graph Nodes with FXML + +You’ve seen how JavaFX APIs create scene graph nodes and configure them for you. The `MyShapes` and `MyShapesProperties` programs use only JavaFX code to build and configure these objects. An alternative approach is to declare scene graph nodes with FXML, a markup notation based on XML. FXML lets you describe and configure your scene graph in a declarative format. This approach has several advantages: +* FXML markup structure is hierarchical, so it reflects the structure of your scene graph. +* FXML describes your view and supports a Model-View-Controller (MVC) architecture, providing better structure for larger applications. +* FXML reduces the JavaFX code you have to write to create and configure scene graph nodes. +* You can design your UI with Scene Builder. This drag-and-drop tool is a stand-alone application that provides a visual rendering of your scene. And Scene Builder generates the FXML markup for you. +* You can also edit your FXML markup with text and IDE editors. + +FXML affects the structure of your program. The main application class now invokes an [`FXMLLoader`](javafxdoc:FXMLLoader). This loader parses your FXML markup, creates JavaFX objects, and inserts the scene graph into the scene at the root node. You can have multiple FXML files, and typically each one has a corresponding JavaFX controller class. This controller class may include event handlers or other statements that dynamically update the scene. +The controller also includes business logic that manages a specific view. + +Let’s return to our `MyShapes` example (now called `MyShapesFXML`) and use an FXML file for the view and CSS for styling. Below you can see the files in our program, arranged for use with build tools or IDEs. + +[![MyShapesFXML with FXML and CSS](/assets/images/javafx/myshapes-fxml-css.png)](/assets/images/javafx/myshapes-fxml-css.png) + +The JavaFX source code appears under the java subdirectory. The resources subdirectory contains the FXML and CSS files (here `Scene.fxml` and `Styles.css`). + +This program includes a rotating [`StackPane`](javafxdoc:StackPane), [`VBox`](javafxdoc:VBox) control, and second [`Text`](javafxdoc:Text) object. `Scene.fxml` describes our scene graph: a top-level [`VBox`](javafxdoc:VBox) that includes a [`StackPane`](javafxdoc:StackPane) and [`Text`](javafxdoc:Text) element. The [`StackPane`](javafxdoc:StackPane) includes the [`Ellipse`](javafxdoc:Ellipse) and [`Text`](javafxdoc:Text) shapes. + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` +The top-level container includes the name of the JavaFX controller class with attribute fx:controller. The [`VBox`](javafxdoc:VBox) specifies its alignment, preferred sizes, and spacing followed by its children: the [`StackPane`](javafxdoc:StackPane) and [`Text`](javafxdoc:StackPane). +Here, we configure the [`StackPane`](javafxdoc:StackPane) with preferred sizing. A special attribute `fx:id` specifies a variable name corresponding to this node. +In the JavaFX controller class, you’ll now see this variable name annotated with `@FXML` for the [`StackPane`](javafxdoc:StackPane). This is how you access objects in the controller class that are declared in FXML files. + +In addition, [`StackPane`](javafxdoc:StackPane) specifies an `onMouseClicked` event handler called `#handleMouseClick`. This event handler is also annotated with `@FXML` in the JavaFX controller class. + +Here, the [`StackPane`](javafxdoc:StackPane) children, [`Ellipse`](javafxdoc:Ellipse) and [`Text`](javafxdoc:Text), are declared inside the Children FXML node. Neither has associated `fx:id` attributes, since the controller class does not need to access these objects. You also see the linear gradient, drop shadow, and reflection effect configurations. + +Note that the [`Text`](javafxdoc:Text) object with `fx:id text2` appears after the [`StackPane`](javafxdoc:StackPane) definition. This makes the second [`Text`](javafxdoc:Text) object appear under the [`StackPane`](javafxdoc:StackPane) in the [`VBox`](javafxdoc:VBox). We also specify an `fx:id` attribute to access this node from the JavaFX controller. +  +## Controller Class + +Let’s show you the controller class now. You’ll notice the code is more compact, since object instantiations and configuration code are no longer done with Java statements. All that is now specified in the FXML markup. + +```java +package org.modernclient; +import javafx.animation.Animation; +import javafx.animation.Interpolator; +import javafx.animation.RotateTransition; +import javafx.beans.binding.When; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import javafx.util.Duration; +import java.net.URL; +import java.util.ResourceBundle; +public class FXMLController implements Initializable { + @FXML + private StackPane stackPane; + @FXML + private Text text2; + private RotateTransition rotate; + @Override + public void initialize(URL url, ResourceBundle rb) { + rotate = new RotateTransition(Duration.millis(2500), stackPane); + rotate.setToAngle(360); + rotate.setFromAngle(0); + rotate.setInterpolator(Interpolator.LINEAR); + rotate.statusProperty().addListener( + (observableValue, oldValue, newValue) -> { + text2.setText("Was " + oldValue + ", Now " + newValue); + }); + text2.strokeProperty().bind(new When(rotate.statusProperty() + .isEqualTo(Animation.Status.RUNNING)) + .then(Color.GREEN).otherwise(Color.RED)); + } + @FXML + private void handleMouseClick(MouseEvent mouseEvent) { + if (rotate.getStatus().equals(Animation.Status.RUNNING)) { + rotate.pause(); + } else { + rotate.play(); + } + } +} +``` + +The controller class implements [`Initializable`](javafxdoc:Initializable) and overrides method `initialize()`, which is invoked for you at runtime. Importantly, the private class fields `stackPane` and `text2` are annotated with `@FXML`. +The `@FXML` annotation associates variable names in the controller class to the objects described in the FXML file. There is no code in the controller class that creates these objects because the [`FXMLLoader`](javafxdoc:FXMLLoader) does that for you. + +The `initialize()` method does three things here. First, it creates and configures the [`RotateTransition`](javafxdoc:RotateTransition) and applies it to the `stackPane` node. Second, it adds a change listener to the transition’s status property. +And third, a bind expression for the `text2` stroke property specifies its color based on the rotate transition’s status. + +The `@FXML` annotation with `handleMouseClick()` indicates that the FXML file configures the event handler. This mouse click event handler starts and stops the rotate transition’s animation. +  +## JavaFX Application Class + +The main application class, `MyShapesFXML`, now becomes very simple. Its job is to invoke the [`FXMLLoader`](javafxdoc:FXMLLoader), which parses the FXML (`Scene.fxml`), builds the scene graph, and returns the scene graph root. All you have to do is build the scene object and configure the stage as before, as shown below. + +```java +package org.modernclient; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +public class MyShapesFXML extends Application { + @Override + public void start(Stage stage) throws Exception { + Parent root = FXMLLoader.load(getClass() + .getResource("/fxml/Scene.fxml")); + Scene scene = new Scene(root, Color.LIGHTYELLOW); + scene.getStylesheets().add(getClass() + .getResource("/styles/Styles.css").toExternalForm()); + stage.setTitle("MyShapesApp with JavaFX"); + stage.setScene(scene); + stage.show(); + } + public static void main(String[] args) { + launch(args); + } +} +``` +  +## Adding CSS + +Now let’s show you how to incorporate your own styles with CSS. One advantage of JavaFX is its ability to style nodes with CSS. +JavaFX comes bundled with a default stylesheet, `Modena.css`. You can augment these default styles or replace them with new ones. Our example CSS file found in file Styles.css is a single style class (`mytext`) that sets its font style to italic: + +```css +.mytext { + -fx-font-style: italic; +} +``` + +To use this stylesheet, you must first load the file, either in the application’s `start()` method or in the FXML file. Once the file is added to the available stylesheets, you can apply the style classes to a node. To apply individually defined style classes to a specific node, for instance, use: + +```java +text2.getStyleClass().add("mytext"); +``` + +Here, `mytext` is the style class and `text2` is the second [`Text`](javafxdoc:Text) object in our program. +Alternatively, you can specify the stylesheet in the FXML file. The advantage of this approach is that styles are now available inside Scene Builder. Here is the modified `Scene.fxml` file that loads this customized CSS file and applies the customized CSS style class to the `text2 Text` node: + +```xml +. . . + + + + + . . . code removed . . . + + + + + + + + +``` +  +## Using Scene Builder + +Scene Builder was originally developed at Oracle and is now open sourced. It is available for download from Gluon here: https://gluonhq.com/products/scene-builder/. +Scene Builder is a stand-alone drag-and-drop tool for creating JavaFX UIs. You can see below the main Scene Builder window with file `Scene.fxml` from the `MyShapesFXML` program. + +[![FXML file with Scene Builder for MyShapesFXML](/assets/images/javafx/scene-builder.png)](/assets/images/javafx/scene-builder.png) + +The upper-left window shows the JavaFX component library. This library includes containers, controls, shapes, 3D, and more. From this window, you select components and drop them onto your scene in the middle visual view or onto the `Document` window shown in the lower-left area. + +The `Document` window shows the scene graph hierarchy. You can select components and move them within the tree. The right window is an `Inspector` window that lets you configure each component, including its properties, layout settings, and code. In this figure, the [`StackPane`](javafxdoc:StackPane) is selected in the `Document` hierarchy window and appears in the center visual view. +In the `Inspector` window, the `OnMouseClicked` property is set to `#handleMouseClick`, which is the name of the corresponding method in the JavaFX controller class. + +Scene Builder is particularly helpful when building real-world form-based UIs. You can visualize your scene hierarchy and easily configure layout and alignment settings. + diff --git a/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/06_all_together.md b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/06_all_together.md new file mode 100644 index 0000000..3ebd253 --- /dev/null +++ b/app/pages/learn/01_tutorial/07_rich_client_apps/01_javafx/06_all_together.md @@ -0,0 +1,452 @@ +--- +id: javafx.fundamentals.all +title: Putting all together +slug: learn/javafx/all +type: tutorial-group +group: rich-client-apps +category: javafx +layout: learn/tutorial-group.html +subheader_select: tutorials +main_css_id: learn +toc: +- Overview {overview} +- Master-Detail UI {master-detail-ui} +- The Model {model} +- Observable Lists {observable-lists} +- Implementing ListView Selection {list-view-selection} +- Using Multiple Selection {multiple-selection} +- ListView and Sort {list-view-sort} +- Person UI Application Actions {app-actions} +- Person UI with Records {app-records} +- Key Point Summary {summary} +description: "Control nodes by manipulating their properties. " +last_update: 2023-09-12 +author: ["GailC.Anderson", "PaulAnderson"] +byline: 'and is from The Definitive Guide to Modern Java Clients with JavaFX 17 graciously contributed by Apress.' +--- + +  +## Overview + +It’s time to build a more interesting JavaFX application now, one that implements a master-detail view. As we show you this application, we’ll explain several JavaFX features that help you control the UI and keep your data and the application consistent. + +First, we use Scene Builder to construct and configure the UI. Our example includes a `Person` model class and an underlying [`ObservableList`](javafxdoc:ObservableList) that holds data. The program lets users make changes, but we don’t persist any data. +JavaFX has [ [`ObservableList`](javafxdoc:ObservableList)](javafxdoc:ObservableList)s that manage collections of data, and you can write listeners and bind expressions that respond to any data changes. The program uses a combination of event handlers and bind expressions to keep the application state consistent. +  +## Master-Detail UI + +For the UI, we use a JavaFX ListView control in the left window (the master view) and a Form on the right (the detail view). In Scene Builder, we select an [`AnchorPane`](javafxdoc:AnchorPane) as the top-level component and the scene graph root. +A [`SplitPane`](javafxdoc:SplitPane) layout pane divides the application view into two parts, and each part has [`AnchorPane`](javafxdoc:AnchorPane) as its main container. + +[![Person UI application](/assets/images/javafx/person-ui-app.png)](/assets/images/javafx/person-ui-app.png) + +The [`ListView`](javafxdoc:ListView) control lets you perform selections for a `Person` object. Here, the first `Person` is selected, and the details of that `Person` appear in the form control on the right. +The form control has the following layout: +* The form contains a [`GridPane`](javafxdoc:GridPane) (two columns by four rows) that holds [`TextField`](javafxdoc:TextField)s for the firstname and lastname fields of `Person`. +* A [`TextArea`](javafxdoc:TextArea) holds the notes field for `Person`. Labels in the first column mark each of these controls. +* The bottom row of the [`GridPane`](javafxdoc:GridPane) consists of a [`ButtonBar`](javafxdoc:ButtonBar) that spans both columns and aligns on the right side by default. The [`ButtonBar`](javafxdoc:ButtonBar) sizes all of its buttons to the width of the widest button label so the buttons have a uniform size. +* The buttons let you perform New (create a `Person` and add that `Person` to the list), Update (edit a selected `Person`), and Delete (remove a selected `Person` from the list). +* Bind expressions query the state of the application and enable or disable the buttons. + +The hierarchical view of our scene graph for the Person UI application looks like following: + +[![Person UI scene graph hierarchy](/assets/images/javafx/person-ui-scene-graph.png)](/assets/images/javafx/person-ui-scene-graph.png) + +The file structure of the application is listed below: + +[![Person UI application file structure](/assets/images/javafx/person-ui-file-struct.png)](/assets/images/javafx/person-ui-file-struct.png) + +`Person.java` contains the `Person` model code, and `SampleData.java` provides the data to initialize the application. `FXMLController.java` is the JavaFX controller class, and `PersonUI.java` holds the main application class. Under resources, the FXML file `Scene.fxml` describes the UI. +  +## The Model + +The `Person` class is the "model: we use for this application. + +```java +package org.modernclient.model; +import javafx.beans.Observable; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.util.Callback; +import java.util.Objects; +public class Person { + private final StringProperty firstname = new SimpleStringProperty( + this, "firstname", ""); + private final StringProperty lastname = new SimpleStringProperty( + this, "lastname", ""); + private final StringProperty notes = new SimpleStringProperty( + this, "notes", "sample notes"); + public Person() { + } + public Person(String firstname, String lastname, String notes) { + this.firstname.set(firstname); + this.lastname.set(lastname); + this.notes.set(notes); + } + public String getFirstname() { + return firstname.get(); + } + public StringProperty firstnameProperty() { + return firstname; + } + public void setFirstname(String firstname) { + this.firstname.set(firstname); + } + public String getLastname() { + return lastname.get(); + } + public StringProperty lastnameProperty() { + return lastname; + } + public void setLastname(String lastname) { + this.lastname.set(lastname); + } + public String getNotes() { + return notes.get(); + } + public StringProperty notesProperty() { + return notes; + } + public void setNotes(String notes) { + this.notes.set(notes); + } + @Override + public String toString() { + return firstname.get() + " " + lastname.get(); + } + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Person person = (Person) obj; + return Objects.equals(firstname, person.firstname) && + Objects.equals(lastname, person.lastname) && + Objects.equals(notes, person.notes); + } + @Override + public int hashCode() { + return Objects.hash(firstname, lastname, notes); + } +} +``` +  +## Observable Lists + +When working with JavaFX collections, you’ll typically use [`ObservableList`](javafxdoc:ObservableList)s that detect list changes with listeners. Furthermore, the JavaFX controls that display lists of data expect observable lists. +These controls automatically update the UI in response to list modifications. We’ll explain some of these intricacies as we walk you through our example program. +  +## Implementing ListView Selection + +A [`ListView`](javafxdoc:ListView) control displays items in an observable list and lets you select one or possibly multiple items. To display a selected `Person` in the form fields in the right view, you use a change listener for the `selectedItemProperty`. +This change listener is invoked each time the user either selects a different item from the [`ListView`](javafxdoc:ListView) or deselects the selected item. You can use the mouse for selecting, as well as the arrow keys, Home (for the first item), and End (for the last item). +On a Mac, use Fn + Left Arrow for Home and Fn + Right Arrow for End. For deselecting (either Command-click for a Mac or Control-click on Linux or Windows), the new value is null, and we clear all the form control fields. +Below you can observe the [`ListView`](javafxdoc:ListView) selection change listener. + +```java +listView.getSelectionModel().selectedItemProperty().addListener( + personChangeListener = (observable, oldValue, newValue) -> { + // newValue can be null if nothing is selected + selectedPerson = newValue; + modifiedProperty.set(false); + if (newValue != null) { + // Populate controls with selected Person + firstnameTextField.setText(selectedPerson.getFirstname()); + lastnameTextField.setText(selectedPerson.getLastname()); + notesTextArea.setText(selectedPerson.getNotes()); + } else { + firstnameTextField.setText(""); + lastnameTextField.setText(""); + notesTextArea.setText(""); + } + }); +``` + +Boolean property `modifiedProperty` tracks whether the user has changed any of the three text controls in the form. We reset this flag after each [`ListView`](javafxdoc:ListView) selection and use this property in a bind expression to control the Update button’s disable property. +  +## Using Multiple Selection + +By default, a [`ListView`](javafxdoc:ListView) control implements single selection so at most one item can be selected. [`ListView`](javafxdoc:ListView) also provides multiple selection, which you enable by configuring the selection mode, as follows: + +```java +listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); +``` + +With this setting, each time the user adds another item to the selection with CTRL-Shift or CTRL-Command, the `selectedItemProperty` listener is invoked with the new selection. +The `getSelectedItems()` method returns all of the currently selected items, and the newValue argument is the most recently selected value. For example, the following change listener collects multiple selected items and prints them: + +```java +listView.getSelectionModel().selectedItemProperty().addListener( + personChangeListener = (observable, oldValue, newValue) -> { + ObservableList selectedItems = listView.getSelectionModel().getSelectedItems(); + // Do something with selectedItems + // System.out.println(selectedItems); +}); +``` +Our Person UI application uses single selection mode for the ListView. +  +## ListView and Sort + +Suppose you want to sort the list of names by last name and then first name. JavaFX has several ways to sort lists. Since we need to keep names sorted, we’ll wrap the underlying `observableArrayList` in a [`SortedList`](javafxdoc:SortedList). +To keep the list sorted in ListView, we invoke [`ListView`](javafxdoc:ListView)’s setItems() method with the sorted list. A comparator specifies the ordering. +First, we compare each person’s last name for sorting and then the first names if necessary. To set the sorting, the `setComparator()` method uses an anonymous class or, more succinctly, a lambda expression: + +```java +// Use a sorted list; sort by lastname; then by firstname +SortedList sortedList = new SortedList(personList); +sortedList.setComparator((p1, p2) -> { + int result = p1.getLastname().compareToIgnoreCase(p2.getLastname()); + if (result == 0) { + result = p1.getFirstname().compareToIgnoreCase(p2.getFirstname()); + } + return result; +}); +listView.setItems(sortedList); +``` + +Note that the comparator arguments p1 and p2 are inferred as `Person` types since [`SortedList`](javafxdoc:SortedList) is generic. +  +## Person UI Application Actions + +Our Person UI application implements three actions: Delete (remove the selected `Person` object from the underlying list), New (create a `Person` object and add it to the underlying list), and Update (make changes to the selected `Person` object and update the underlying list). +Let’s go over each action in detail, with an eye toward learning more about the JavaFX features that help you build this type of application. + +### Delete a Person + +The controller class includes an action event handler for the Delete button. Here’s the FXML snippet that defines the Delete button: + +```xml +