diff --git a/.github/workflows/check-new-jdk.lock.yml b/.github/workflows/check-new-jdk.lock.yml deleted file mode 100644 index b40ee5f..0000000 --- a/.github/workflows/check-new-jdk.lock.yml +++ /dev/null @@ -1,1130 +0,0 @@ -# -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ -# | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ -# \_| |_/\__, |\___|_| |_|\__|_|\___| -# __/ | -# _ _ |___/ -# | | | | / _| | -# | | | | ___ _ __ _ __| |_| | _____ ____ -# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| -# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ -# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ -# -# This file was automatically generated by gh-aw (v0.50.4). DO NOT EDIT. -# -# To update this file, edit the corresponding .md file and run: -# gh aw compile -# Not all edits will cause changes to this file. -# -# For more information: https://github.github.com/gh-aw/introduction/overview/ -# -# Checks for new OpenJDK releases and proposes new java.evolved snippets covering newly finalized language features and APIs. -# -# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"b2476905fe4edf0c55a76a2238afc4ea0eef32b3eedd8974de20e92bdf27bd04","compiler_version":"v0.50.4"} - -name: "Check for New OpenJDK Release and Propose New Snippets" -"on": - schedule: - - cron: "0 12 15-21 3,9 5" - workflow_dispatch: - -permissions: {} - -concurrency: - group: "gh-aw-${{ github.workflow }}" - -run-name: "Check for New OpenJDK Release and Propose New Snippets" - -jobs: - activation: - runs-on: ubuntu-slim - permissions: - contents: read - outputs: - comment_id: "" - comment_repo: "" - steps: - - name: Setup Scripts - uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4 - with: - destination: /opt/gh-aw/actions - - name: Validate context variables - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/validate_context_variables.cjs'); - await main(); - - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - sparse-checkout: | - .github - .agents - fetch-depth: 1 - persist-credentials: false - - name: Check workflow file timestamps - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_WORKFLOW_FILE: "check-new-jdk.lock.yml" - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs'); - await main(); - - name: Create prompt with built-in context - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} - GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - run: | - bash /opt/gh-aw/actions/create_prompt_first.sh - { - cat << 'GH_AW_PROMPT_EOF' - - GH_AW_PROMPT_EOF - cat "/opt/gh-aw/prompts/xpia.md" - cat "/opt/gh-aw/prompts/temp_folder_prompt.md" - cat "/opt/gh-aw/prompts/markdown.md" - cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_EOF' - - Tools: create_pull_request, missing_tool, missing_data - GH_AW_PROMPT_EOF - cat "/opt/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_EOF' - - - The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} - - **actor**: __GH_AW_GITHUB_ACTOR__ - {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} - - **repository**: __GH_AW_GITHUB_REPOSITORY__ - {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} - - **workspace**: __GH_AW_GITHUB_WORKSPACE__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ - {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} - - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ - {{/if}} - - - GH_AW_PROMPT_EOF - cat << 'GH_AW_PROMPT_EOF' - - GH_AW_PROMPT_EOF - cat << 'GH_AW_PROMPT_EOF' - {{#runtime-import check-new-jdk.md}} - GH_AW_PROMPT_EOF - } > "$GH_AW_PROMPT" - - name: Interpolate variables and render templates - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs'); - await main(); - - name: Substitute placeholders - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} - GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - - const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); - - // Call the substitution function - return await substitutePlaceholders({ - file: process.env.GH_AW_PROMPT, - substitutions: { - GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, - GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, - GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, - GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE - } - }); - - name: Validate prompt placeholders - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh - - name: Print prompt - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - run: bash /opt/gh-aw/actions/print_prompt_summary.sh - - name: Upload prompt artifact - if: success() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: prompt - path: /tmp/gh-aw/aw-prompts/prompt.txt - retention-days: 1 - - agent: - needs: activation - runs-on: ubuntu-latest - permissions: - contents: read - issues: read - pull-requests: read - concurrency: - group: "gh-aw-copilot-${{ github.workflow }}" - env: - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - GH_AW_ASSETS_ALLOWED_EXTS: "" - GH_AW_ASSETS_BRANCH: "" - GH_AW_ASSETS_MAX_SIZE_KB: 0 - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json - GH_AW_WORKFLOW_ID_SANITIZED: checknewjdk - outputs: - checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} - detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} - detection_success: ${{ steps.detection_conclusion.outputs.success }} - has_patch: ${{ steps.collect_output.outputs.has_patch }} - model: ${{ steps.generate_aw_info.outputs.model }} - output: ${{ steps.collect_output.outputs.output }} - output_types: ${{ steps.collect_output.outputs.output_types }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} - steps: - - name: Setup Scripts - uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4 - with: - destination: /opt/gh-aw/actions - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - name: Create gh-aw temp directory - run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh - - name: Configure Git credentials - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - - name: Checkout PR branch - id: checkout-pr - if: | - github.event.pull_request - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); - await main(); - - name: Generate agentic run info - id: generate_aw_info - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const fs = require('fs'); - - const awInfo = { - engine_id: "copilot", - engine_name: "GitHub Copilot CLI", - model: process.env.GH_AW_MODEL_AGENT_COPILOT || "", - version: "", - agent_version: "0.0.417", - cli_version: "v0.50.4", - workflow_name: "Check for New OpenJDK Release and Propose New Snippets", - experimental: false, - supports_tools_allowlist: true, - run_id: context.runId, - run_number: context.runNumber, - run_attempt: process.env.GITHUB_RUN_ATTEMPT, - repository: context.repo.owner + '/' + context.repo.repo, - ref: context.ref, - sha: context.sha, - actor: context.actor, - event_name: context.eventName, - staged: false, - allowed_domains: ["defaults"], - firewall_enabled: true, - awf_version: "v0.23.0", - awmg_version: "v0.1.5", - steps: { - firewall: "squid" - }, - created_at: new Date().toISOString() - }; - - // Write to /tmp/gh-aw directory to avoid inclusion in PR - const tmpPath = '/tmp/gh-aw/aw_info.json'; - fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); - console.log('Generated aw_info.json at:', tmpPath); - console.log(JSON.stringify(awInfo, null, 2)); - - // Set model as output for reuse in other steps/jobs - core.setOutput('model', awInfo.model); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - - name: Install GitHub Copilot CLI - run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.417 - - name: Install awf binary - run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0 - - name: Determine automatic lockdown mode for GitHub MCP Server - id: determine-automatic-lockdown - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - with: - script: | - const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); - await determineAutomaticLockdown(github, context, core); - - name: Download container images - run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.5 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine - - name: Write Safe Outputs Config - run: | - mkdir -p /opt/gh-aw/safeoutputs - mkdir -p /tmp/gh-aw/safeoutputs - mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_pull_request":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_EOF - cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' - [ - { - "description": "Create a new GitHub pull request to propose code changes. Use this after making file edits to submit them for review and merging. The PR will be created from the current branch with your committed changes. For code review comments on an existing PR, use create_pull_request_review_comment instead. CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[new-jdk] \". Labels [enhancement new-jdk-release] will be automatically added.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "body": { - "description": "Detailed PR description in Markdown. Include what changes were made, why, testing notes, and any breaking changes. Do NOT repeat the title as a heading.", - "type": "string" - }, - "branch": { - "description": "Source branch name containing the changes. If omitted, uses the current working branch.", - "type": "string" - }, - "draft": { - "description": "Whether to create the PR as a draft. Draft PRs cannot be merged until marked as ready for review. Use mark_pull_request_as_ready_for_review to convert a draft PR. Default: true.", - "type": "boolean" - }, - "labels": { - "description": "Labels to categorize the PR (e.g., 'enhancement', 'bugfix'). Labels must exist in the repository.", - "items": { - "type": "string" - }, - "type": "array" - }, - "title": { - "description": "Concise PR title describing the changes. Follow repository conventions (e.g., conventional commits). The title appears as the main heading.", - "type": "string" - } - }, - "required": [ - "title", - "body" - ], - "type": "object" - }, - "name": "create_pull_request" - }, - { - "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "alternatives": { - "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", - "type": "string" - }, - "reason": { - "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", - "type": "string" - }, - "tool": { - "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", - "type": "string" - } - }, - "required": [ - "reason" - ], - "type": "object" - }, - "name": "missing_tool" - }, - { - "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "message": { - "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", - "type": "string" - } - }, - "required": [ - "message" - ], - "type": "object" - }, - "name": "noop" - }, - { - "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "alternatives": { - "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", - "type": "string" - }, - "context": { - "description": "Additional context about the missing data or where it should come from (max 256 characters).", - "type": "string" - }, - "data_type": { - "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", - "type": "string" - }, - "reason": { - "description": "Explanation of why this data is needed to complete the task (max 256 characters).", - "type": "string" - } - }, - "required": [], - "type": "object" - }, - "name": "missing_data" - } - ] - GH_AW_SAFE_OUTPUTS_TOOLS_EOF - cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' - { - "create_pull_request": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "branch": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "draft": { - "type": "boolean" - }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 - } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - } - } - } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_EOF - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash /opt/gh-aw/actions/start_safe_outputs_server.sh - - - name: Start MCP Gateway - id: start-mcp-gateway - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} - GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -eo pipefail - mkdir -p /tmp/gh-aw/mcp-config - - # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="80" - export MCP_GATEWAY_DOMAIN="host.docker.internal" - MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${MCP_GATEWAY_API_KEY}" - export MCP_GATEWAY_API_KEY - export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" - mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" - export DEBUG="*" - - export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.5' - - mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh - { - "mcpServers": { - "github": { - "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v0.31.0", - "env": { - "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", - "GITHUB_READ_ONLY": "1", - "GITHUB_TOOLSETS": "pull_requests,issues,repos" - } - }, - "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" - } - } - }, - "gateway": { - "port": $MCP_GATEWAY_PORT, - "domain": "${MCP_GATEWAY_DOMAIN}", - "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" - } - } - GH_AW_MCP_CONFIG_EOF - - name: Generate workflow overview - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); - await generateWorkflowOverview(core); - - name: Download prompt artifact - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 - with: - name: prompt - path: /tmp/gh-aw/aw-prompts - - name: Clean git credentials - run: bash /opt/gh-aw/actions/clean_git_credentials.sh - - name: Execute GitHub Copilot CLI - id: agentic_execution - # Copilot CLI tool arguments (sorted): - timeout-minutes: 30 - run: | - set -o pipefail - # shellcheck disable=SC1003 - sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log - env: - COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json - GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} - GITHUB_WORKSPACE: ${{ github.workspace }} - XDG_CONFIG_HOME: /home/runner - - name: Configure Git credentials - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - - name: Copy Copilot session state files to logs - if: always() - continue-on-error: true - run: | - # Copy Copilot session state files to logs folder for artifact collection - # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them - SESSION_STATE_DIR="$HOME/.copilot/session-state" - LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" - - if [ -d "$SESSION_STATE_DIR" ]; then - echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" - mkdir -p "$LOGS_DIR" - cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true - echo "Session state files copied successfully" - else - echo "No session-state directory found at $SESSION_STATE_DIR" - fi - - name: Stop MCP Gateway - if: always() - continue-on-error: true - env: - MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} - run: | - bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" - - name: Redact secrets in logs - if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs'); - await main(); - env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Upload Safe Outputs - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: safe-output - path: ${{ env.GH_AW_SAFE_OUTPUTS }} - if-no-files-found: warn - - name: Ingest agent output - id: collect_output - if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_API_URL: ${{ github.api_url }} - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs'); - await main(); - - name: Upload sanitized agent output - if: always() && env.GH_AW_AGENT_OUTPUT - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: agent-output - path: ${{ env.GH_AW_AGENT_OUTPUT }} - if-no-files-found: warn - - name: Upload engine output files - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: agent_outputs - path: | - /tmp/gh-aw/sandbox/agent/logs/ - /tmp/gh-aw/redacted-urls.log - if-no-files-found: ignore - - name: Parse agent logs for step summary - if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs'); - await main(); - - name: Parse MCP Gateway logs for step summary - if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs'); - await main(); - - name: Print firewall logs - if: always() - continue-on-error: true - env: - AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs - run: | - # Fix permissions on firewall logs so they can be uploaded as artifacts - # AWF runs with sudo, creating files owned by root - sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true - # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) - if command -v awf &> /dev/null; then - awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" - else - echo 'AWF binary not installed, skipping firewall log summary' - fi - - name: Upload agent artifacts - if: always() - continue-on-error: true - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: agent-artifacts - path: | - /tmp/gh-aw/aw-prompts/prompt.txt - /tmp/gh-aw/aw_info.json - /tmp/gh-aw/mcp-logs/ - /tmp/gh-aw/sandbox/firewall/logs/ - /tmp/gh-aw/agent-stdio.log - /tmp/gh-aw/agent/ - /tmp/gh-aw/aw-*.patch - if-no-files-found: ignore - # --- Threat Detection (inline) --- - - name: Check if detection needed - id: detection_guard - if: always() - env: - OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }} - HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} - run: | - if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then - echo "run_detection=true" >> "$GITHUB_OUTPUT" - echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" - else - echo "run_detection=false" >> "$GITHUB_OUTPUT" - echo "Detection skipped: no agent outputs or patches to analyze" - fi - - name: Clear MCP configuration for detection - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - rm -f /tmp/gh-aw/mcp-config/mcp-servers.json - rm -f /home/runner/.copilot/mcp-config.json - rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - - name: Prepare threat detection files - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - mkdir -p /tmp/gh-aw/threat-detection/aw-prompts - cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true - cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true - for f in /tmp/gh-aw/aw-*.patch; do - [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true - done - echo "Prepared threat detection files:" - ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true - - name: Setup threat detection - if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - WORKFLOW_NAME: "Check for New OpenJDK Release and Propose New Snippets" - WORKFLOW_DESCRIPTION: "Checks for new OpenJDK releases and proposes new java.evolved snippets covering newly finalized language features and APIs." - HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs'); - await main(); - - name: Ensure threat-detection directory and log - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - mkdir -p /tmp/gh-aw/threat-detection - touch /tmp/gh-aw/threat-detection/detection.log - - name: Execute GitHub Copilot CLI - if: always() && steps.detection_guard.outputs.run_detection == 'true' - id: detection_agentic_execution - # Copilot CLI tool arguments (sorted): - # --allow-tool shell(cat) - # --allow-tool shell(grep) - # --allow-tool shell(head) - # --allow-tool shell(jq) - # --allow-tool shell(ls) - # --allow-tool shell(tail) - # --allow-tool shell(wc) - timeout-minutes: 20 - run: | - set -o pipefail - # shellcheck disable=SC1003 - sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log - env: - COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} - GITHUB_WORKSPACE: ${{ github.workspace }} - XDG_CONFIG_HOME: /home/runner - - name: Parse threat detection results - id: parse_detection_results - if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); - - name: Upload threat detection log - if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: threat-detection.log - path: /tmp/gh-aw/threat-detection/detection.log - if-no-files-found: ignore - - name: Set detection conclusion - id: detection_conclusion - if: always() - env: - RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} - DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }} - run: | - if [[ "$RUN_DETECTION" != "true" ]]; then - echo "conclusion=skipped" >> "$GITHUB_OUTPUT" - echo "success=true" >> "$GITHUB_OUTPUT" - echo "Detection was not needed, marking as skipped" - elif [[ "$DETECTION_SUCCESS" == "true" ]]; then - echo "conclusion=success" >> "$GITHUB_OUTPUT" - echo "success=true" >> "$GITHUB_OUTPUT" - echo "Detection passed successfully" - else - echo "conclusion=failure" >> "$GITHUB_OUTPUT" - echo "success=false" >> "$GITHUB_OUTPUT" - echo "Detection found issues" - fi - - conclusion: - needs: - - activation - - agent - - safe_outputs - if: (always()) && (needs.agent.result != 'skipped') - runs-on: ubuntu-slim - permissions: - contents: write - issues: write - pull-requests: write - outputs: - noop_message: ${{ steps.noop.outputs.noop_message }} - tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} - total_count: ${{ steps.missing_tool.outputs.total_count }} - steps: - - name: Setup Scripts - uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4 - with: - destination: /opt/gh-aw/actions - - name: Download agent output artifact - continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 - with: - name: agent-output - path: /tmp/gh-aw/safeoutputs/ - - name: Setup agent output environment variable - run: | - mkdir -p /tmp/gh-aw/safeoutputs/ - find "/tmp/gh-aw/safeoutputs/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - - name: Process No-Op Messages - id: noop - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Check for New OpenJDK Release and Propose New Snippets" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/noop.cjs'); - await main(); - - name: Record Missing Tool - id: missing_tool - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Check for New OpenJDK Release and Propose New Snippets" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/missing_tool.cjs'); - await main(); - - name: Handle Agent Failure - id: handle_agent_failure - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Check for New OpenJDK Release and Propose New Snippets" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_WORKFLOW_ID: "check-new-jdk" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }} - GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} - GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} - GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} - GH_AW_GROUP_REPORTS: "false" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); - await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Check for New OpenJDK Release and Propose New Snippets" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs'); - await main(); - - name: Handle Create Pull Request Error - id: handle_create_pr_error - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Check for New OpenJDK Release and Propose New Snippets" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/handle_create_pr_error.cjs'); - await main(); - - safe_outputs: - needs: - - activation - - agent - if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') - runs-on: ubuntu-slim - permissions: - contents: write - issues: write - pull-requests: write - timeout-minutes: 15 - env: - GH_AW_ENGINE_ID: "copilot" - GH_AW_WORKFLOW_ID: "check-new-jdk" - GH_AW_WORKFLOW_NAME: "Check for New OpenJDK Release and Propose New Snippets" - outputs: - code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} - code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} - create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} - create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} - process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} - steps: - - name: Setup Scripts - uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4 - with: - destination: /opt/gh-aw/actions - - name: Download agent output artifact - continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 - with: - name: agent-output - path: /tmp/gh-aw/safeoutputs/ - - name: Setup agent output environment variable - run: | - mkdir -p /tmp/gh-aw/safeoutputs/ - find "/tmp/gh-aw/safeoutputs/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - - name: Download patch artifact - continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 - with: - name: agent-artifacts - path: /tmp/gh-aw/ - - name: Checkout repository - if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'create_pull_request')) - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ github.base_ref || github.ref_name }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - - name: Configure Git credentials - if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'create_pull_request')) - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - - name: Process Safe Outputs - id: process_safe_outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"base_branch\":\"${{ github.base_ref || github.ref_name }}\",\"labels\":[\"enhancement\",\"new-jdk-release\"],\"max\":1,\"max_patch_size\":1024,\"title_prefix\":\"[new-jdk] \"},\"missing_data\":{},\"missing_tool\":{}}" - GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs'); - await main(); - - name: Upload safe output items manifest - if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - with: - name: safe-output-items - path: /tmp/safe-output-items.jsonl - if-no-files-found: warn - diff --git a/.github/workflows/check-new-jdk.md b/.github/workflows/check-new-jdk.md deleted file mode 100644 index 5bf4c71..0000000 --- a/.github/workflows/check-new-jdk.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -on: - schedule: - - cron: '0 12 15-21 3,9 5' # Third Friday of March and September at noon UTC - workflow_dispatch: -description: > - Checks for new OpenJDK releases and proposes new java.evolved snippets - covering newly finalized language features and APIs. -strict: false -permissions: - contents: read - pull-requests: read - issues: read -network: - allowed: - - defaults -tools: - web-fetch: - edit: - bash: true - github: - toolsets: [pull_requests, issues, repos] -safe-outputs: - create-pull-request: - title-prefix: "[new-jdk] " - labels: [enhancement, new-jdk-release] -timeout-minutes: 30 ---- - -# Check for New OpenJDK Release and Propose New Snippets - -You are a Java expert maintaining the **java.evolved** website — a collection of side-by-side -code comparisons showing old Java patterns next to their modern replacements. - -## Your Task - -1. **Check for new OpenJDK releases.** - - Fetch the OpenJDK project page at `https://openjdk.org/projects/jdk/` and identify the - latest GA (General Availability) JDK release. - - Compare it against the site's current coverage. Read the existing JSON snippet files in - the category subfolders (e.g., `language/*.json`, `concurrency/*.json`) to see which - JDK versions are already covered. - - If the latest GA release is already fully covered, stop and report "No new JDK release to cover." - -2. **Research new features.** - - Go to `https://openjdk.org/projects/jdk/{version}/` for the new release. - - Identify all JEPs that are **finalized** (not preview, not incubator, not experimental). - - Focus on **language features** and **API additions** that a typical Java developer would use - in application code. Skip internal/VM-only JEPs (GC changes, ports, JFR internals, etc.) - unless they have a clear developer-facing usage pattern. - - Also note any features that graduated from preview to final in this release. - -3. **Create new snippet JSON files.** - - Each snippet is an individual JSON file at `{category}/{slug}.json`. - - Use an existing JSON file (e.g., `language/type-inference-with-var.json`) as a reference - for the schema. Each snippet needs: - - `id`: next sequential integer - - `slug`: kebab-case URL slug (must match the filename without `.json`) - - `title`: human-readable title - - `category`: one of language, collections, strings, streams, concurrency, io, errors, datetime, security, tooling (must match the parent folder) - - `difficulty`: beginner, intermediate, or advanced - - `jdkVersion`: the JDK version where this became final (non-preview) - - `oldLabel` / `modernLabel`: e.g., "Java 8" / "Java 26+" - - `oldApproach` / `modernApproach`: short description of each approach - - `oldCode` / `modernCode`: complete, compilable code snippets (concise, max ~12 lines each) - - `summary`: one-sentence summary - - `explanation`: 2-3 sentence explanation of why the modern approach is better - - `whyModernWins`: array of exactly 3 objects with `icon`, `title`, `desc` - - `support`: version info string, e.g., "Finalized in JDK 26 (JEP NNN, Month Year)." - - `prev`: `category/slug` of the previous pattern, or `null` if first - - `next`: `category/slug` of the next pattern, or `null` if last - - `related`: array of exactly 3 `category/slug` strings for related patterns - -4. **Update existing files.** - - Update the `next` field of the last existing snippet's JSON file to point to the first - new snippet, and set the new snippet's `prev` accordingly. Chain all new snippets together. - - Add a preview card for each new snippet to `site/index.html` inside the `#tipsGrid` div. - - The snippet count in `site/index.html` uses `{{snippetCount}}` placeholders — it is updated - automatically by the generator. - - Do **NOT** edit `site/data/snippets.json` or any HTML files in `site/` category subfolders — these are - generated by `html-generators/generate.java` and must not be modified directly. - -5. **Verify the build.** - - Run `java -jar html-generators/generate.jar` to regenerate all HTML pages and `site/data/snippets.json`. - - Confirm the new pages were generated successfully. - -6. **Create a pull request.** - - The PR title should be: `[new-jdk] Add snippets for JDK {version} features` - - The PR body should list each new snippet with its title and a one-line summary. - - Mention which JEPs are covered and link to the OpenJDK release page. - -## Important Rules - -- Only include features that are **final** (non-preview) in the new JDK release. -- Label preview features as preview if you choose to include them, with "(Preview)" in the modernLabel. -- All content goes in `content/{category}/{slug}.json` files — never edit generated HTML or `site/data/snippets.json`. -- Run `java -jar html-generators/generate.jar` after making changes to verify the build. -- Do not modify existing snippet JSON files unless a feature graduated from preview to final. -- If a previously preview feature is now final, update its `modernLabel` and `support` text - to remove the "(Preview)" label in its JSON file. diff --git a/.github/workflows/social-post.yml b/.github/workflows/social-post.yml new file mode 100644 index 0000000..4471eeb --- /dev/null +++ b/.github/workflows/social-post.yml @@ -0,0 +1,44 @@ +name: Weekly Social Post + +on: + schedule: + - cron: '0 14 * * 1' # Every Monday at 14:00 UTC (10 AM ET) + workflow_dispatch: # Manual trigger + +permissions: + contents: write + +concurrency: + group: social-post + cancel-in-progress: false + +jobs: + post: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-java@v5 + with: + distribution: 'temurin' + java-version: '25' + + - uses: jbangdev/setup-jbang@main + + - name: Post to Twitter + env: + TWITTER_CONSUMER_KEY: ${{ secrets.TWITTER_APP_CONSUMER_KEY }} + TWITTER_CONSUMER_KEY_SECRET: ${{ secrets.TWITTER_APP_SECRET_KEY }} + TWITTER_ACCESS_TOKEN: ${{ secrets.TWITTER_ACCESS_TOKEN }} + TWITTER_ACCESS_TOKEN_SECRET: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }} + run: jbang html-generators/socialpost.java + + - name: Commit updated state + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add social/state.yaml social/queue.txt social/tweets.yaml + git diff --cached --quiet && exit 0 + git commit -m "chore: update social post state [skip ci]" + git pull --rebase + git push diff --git a/content/collections/copying-collections-immutably.yaml b/content/collections/copying-collections-immutably.yaml index 3dd73f2..851216f 100644 --- a/content/collections/copying-collections-immutably.yaml +++ b/content/collections/copying-collections-immutably.yaml @@ -29,8 +29,8 @@ whyModernWins: title: "One call" desc: "No manual ArrayList construction + wrapping." - icon: "🛡️" - title: "Defensive copy" - desc: "Changes to the original don't affect the copy." + title: "Any Collection" + desc: "Accepts any Collection as input—no intermediate ArrayList conversion needed." support: state: "available" description: "Widely available since JDK 10 (March 2018)" diff --git a/content/collections/sequenced-collections.yaml b/content/collections/sequenced-collections.yaml index 03b4026..09d2169 100644 --- a/content/collections/sequenced-collections.yaml +++ b/content/collections/sequenced-collections.yaml @@ -11,9 +11,9 @@ oldApproach: "Index Arithmetic" modernApproach: "getFirst/getLast" oldCode: |- // Get last element - var last = list.get(list.size() - 1); + Object last = list.get(list.size() - 1); // Get first - var first = list.get(0); + Object first = list.get(0); // Reverse iteration: manual modernCode: |- var last = list.getLast(); diff --git a/content/collections/stream-toarray-typed.yaml b/content/collections/stream-toarray-typed.yaml index 8c291f7..7fb7897 100644 --- a/content/collections/stream-toarray-typed.yaml +++ b/content/collections/stream-toarray-typed.yaml @@ -7,22 +7,26 @@ difficulty: "beginner" jdkVersion: "8" oldLabel: "Pre-Streams" modernLabel: "Java 8+" -oldApproach: "Manual Array Copy" +oldApproach: "Manual Filter + Copy" modernApproach: "toArray(generator)" oldCode: |- List list = getNames(); - String[] arr = new String[list.size()]; - for (int i = 0; i < list.size(); i++) { - arr[i] = list.get(i); + List filtered = new ArrayList<>(); + for (String n : list) { + if (n.length() > 3) { + filtered.add(n); + } } + String[] arr = filtered.toArray(new String[0]); modernCode: |- String[] arr = getNames().stream() .filter(n -> n.length() > 3) .toArray(String[]::new); -summary: "Convert streams to typed arrays with a method reference." -explanation: "The toArray(IntFunction) method creates a properly typed array from\ - \ a stream. The generator (String[]::new) tells the stream what type of array to\ - \ create." +summary: "Filter a collection and collect the results to a typed array using a single stream expression." +explanation: "When you need to filter a collection before converting it to a typed array,\ + \ streams let you chain the operations without an intermediate list. The toArray(IntFunction)\ + \ generator (String[]::new) creates the correctly typed array directly at the end\ + \ of the pipeline, eliminating the manual loop and temporary ArrayList." whyModernWins: - icon: "🎯" title: "Type-safe" @@ -32,7 +36,7 @@ whyModernWins: desc: "Works at the end of any stream pipeline." - icon: "📏" title: "Concise" - desc: "One expression replaces the manual loop." + desc: "No intermediate list — one expression replaces the manual loop and copy." support: state: "available" description: "Widely available since JDK 8 (March 2014)" diff --git a/content/language/pattern-matching-instanceof.yaml b/content/language/pattern-matching-instanceof.yaml index d4306b7..34291c3 100644 --- a/content/language/pattern-matching-instanceof.yaml +++ b/content/language/pattern-matching-instanceof.yaml @@ -12,11 +12,13 @@ modernApproach: "Pattern Variable" oldCode: |- if (obj instanceof String) { String s = (String) obj; - System.out.println(s.length()); + int length = s.length(); + // do something with 'length' } modernCode: |- if (obj instanceof String s) { - IO.println(s.length()); + int length = s.length(); + // do something with 'length' } summary: "Combine type check and cast in one step with pattern matching." explanation: "Pattern matching for instanceof eliminates the redundant cast after\ diff --git a/html-generators/generatesocialqueue.java b/html-generators/generatesocialqueue.java new file mode 100644 index 0000000..35a3ed7 --- /dev/null +++ b/html-generators/generatesocialqueue.java @@ -0,0 +1,231 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25 +//DEPS com.fasterxml.jackson.core:jackson-databind:2.18.3 +//DEPS com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.3 + +import module java.base; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; + +/** + * Generate social media queue and pre-drafted tweets from content YAML files. + * + * Produces: + * social/queue.txt — shuffled posting order (one category/slug per line) + * social/tweets.yaml — pre-drafted tweet text for each pattern + * + * Re-run behavior: + * - New patterns are appended to the end of the existing queue + * - Deleted/renamed patterns are pruned + * - Existing order and tweet edits are preserved + * - Use --reshuffle to force a full reshuffle + */ + +static final String CONTENT_DIR = "content"; +static final String SOCIAL_DIR = "social"; +static final String QUEUE_FILE = SOCIAL_DIR + "/queue.txt"; +static final String TWEETS_FILE = SOCIAL_DIR + "/tweets.yaml"; +static final String STATE_FILE = SOCIAL_DIR + "/state.yaml"; +static final String BASE_URL = "https://javaevolved.github.io"; +static final int MAX_TWEET_LENGTH = 280; + +static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); +static final ObjectMapper YAML_WRITER = new ObjectMapper( + new YAMLFactory() + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) +); + +record PatternInfo(String category, String slug, String title, String summary, + String oldApproach, String modernApproach, String jdkVersion) { + String key() { return category + "/" + slug; } +} + +void main(String... args) throws Exception { + boolean reshuffle = List.of(args).contains("--reshuffle"); + + // 1. Scan all content files + var allPatterns = scanContentFiles(); + System.out.println("Found " + allPatterns.size() + " patterns in content/"); + + // 2. Load existing queue and tweets (if any) + var existingQueue = loadExistingQueue(); + var existingTweets = loadExistingTweets(); + + // 3. Determine new queue order + List queue; + if (reshuffle || existingQueue.isEmpty()) { + // Full shuffle + var keys = new ArrayList<>(allPatterns.keySet()); + Collections.shuffle(keys); + queue = keys; + System.out.println(reshuffle ? "Reshuffled all patterns" : "Generated new queue"); + } else { + // Preserve existing order, prune deleted, append new + queue = new ArrayList<>(); + for (var key : existingQueue) { + if (allPatterns.containsKey(key)) queue.add(key); + else System.out.println(" Pruned (removed): " + key); + } + var existingSet = new LinkedHashSet<>(queue); + var newKeys = new ArrayList(); + for (var key : allPatterns.keySet()) { + if (!existingSet.contains(key)) newKeys.add(key); + } + if (!newKeys.isEmpty()) { + Collections.shuffle(newKeys); + queue.addAll(newKeys); + System.out.println(" Appended " + newKeys.size() + " new patterns: " + newKeys); + } + } + + // 4. Generate tweet drafts + var tweets = new LinkedHashMap(); + int truncated = 0; + for (var key : queue) { + // Preserve manually edited tweets + if (!reshuffle && existingTweets.containsKey(key)) { + tweets.put(key, existingTweets.get(key)); + } else { + var p = allPatterns.get(key); + var tweet = buildTweet(p); + tweets.put(key, tweet); + if (tweet.length() > MAX_TWEET_LENGTH) { + // Retry with truncated summary + tweet = buildTweetTruncated(p); + tweets.put(key, tweet); + truncated++; + } + } + } + + // 5. Validate lengths + int overLength = 0; + for (var entry : tweets.entrySet()) { + int len = entry.getValue().length(); + if (len > MAX_TWEET_LENGTH) { + System.err.println(" WARNING: " + entry.getKey() + " tweet is " + len + " chars (max " + MAX_TWEET_LENGTH + ")"); + overLength++; + } + } + + // 6. Write queue file + Files.createDirectories(Path.of(SOCIAL_DIR)); + Files.writeString(Path.of(QUEUE_FILE), String.join("\n", queue) + "\n"); + System.out.println("Wrote " + QUEUE_FILE + " (" + queue.size() + " entries)"); + + // 7. Write tweets file + YAML_WRITER.writerWithDefaultPrettyPrinter().writeValue(Path.of(TWEETS_FILE).toFile(), tweets); + System.out.println("Wrote " + TWEETS_FILE + " (" + tweets.size() + " entries)"); + + // 8. Create state file if it doesn't exist + if (!Files.exists(Path.of(STATE_FILE))) { + var state = new LinkedHashMap(); + state.put("currentIndex", 1); + state.put("lastPostedKey", null); + state.put("lastTweetId", null); + state.put("lastPostedAt", null); + YAML_WRITER.writerWithDefaultPrettyPrinter().writeValue(Path.of(STATE_FILE).toFile(), state); + System.out.println("Created " + STATE_FILE); + } + + if (truncated > 0) System.out.println(truncated + " tweets were truncated to fit 280 chars"); + if (overLength > 0) System.err.println("WARNING: " + overLength + " tweets still exceed 280 chars — edit manually in " + TWEETS_FILE); + System.out.println("Done!"); +} + +Map scanContentFiles() throws Exception { + var patterns = new LinkedHashMap(); + var contentDir = Path.of(CONTENT_DIR); + + try (var categories = Files.list(contentDir)) { + for (var catDir : categories.filter(Files::isDirectory).sorted().toList()) { + var category = catDir.getFileName().toString(); + try (var files = Files.list(catDir)) { + for (var file : files.filter(f -> isContentFile(f)).sorted().toList()) { + var node = YAML_MAPPER.readTree(file.toFile()); + var slug = node.path("slug").asText(); + var info = new PatternInfo( + category, slug, + node.path("title").asText(), + node.path("summary").asText(), + node.path("oldApproach").asText(), + node.path("modernApproach").asText(), + node.path("jdkVersion").asText() + ); + patterns.put(info.key(), info); + } + } + } + } + return patterns; +} + +boolean isContentFile(Path p) { + var name = p.getFileName().toString(); + return name.endsWith(".yaml") || name.endsWith(".yml") || name.endsWith(".json"); +} + +List loadExistingQueue() throws Exception { + var path = Path.of(QUEUE_FILE); + if (!Files.exists(path)) return List.of(); + return Files.readAllLines(path).stream() + .map(String::strip) + .filter(s -> !s.isEmpty()) + .toList(); +} + +@SuppressWarnings("unchecked") +Map loadExistingTweets() throws Exception { + var path = Path.of(TWEETS_FILE); + if (!Files.exists(path)) return Map.of(); + return YAML_MAPPER.readValue(path.toFile(), LinkedHashMap.class); +} + +String buildTweet(PatternInfo p) { + return """ + ☕ %s + + %s + + %s → %s (JDK %s+) + + 🔗 %s/%s/%s.html + + #Java #JavaEvolved""".formatted( + p.title(), p.summary(), + p.oldApproach(), p.modernApproach(), p.jdkVersion(), + BASE_URL, p.category(), p.slug() + ).stripIndent().strip(); +} + +String buildTweetTruncated(PatternInfo p) { + // Calculate budget: total minus everything except summary + var template = """ + ☕ %s + + %s + + %s → %s (JDK %s+) + + 🔗 %s/%s/%s.html + + #Java #JavaEvolved""".stripIndent().strip(); + + var withoutSummary = template.formatted( + p.title(), "", + p.oldApproach(), p.modernApproach(), p.jdkVersion(), + BASE_URL, p.category(), p.slug() + ); + int budget = MAX_TWEET_LENGTH - withoutSummary.length(); + var summary = p.summary(); + if (summary.length() > budget && budget > 3) { + summary = summary.substring(0, budget - 1) + "…"; + } + return template.formatted( + p.title(), summary, + p.oldApproach(), p.modernApproach(), p.jdkVersion(), + BASE_URL, p.category(), p.slug() + ); +} diff --git a/html-generators/locales.properties b/html-generators/locales.properties index c56101d..422c4dd 100644 --- a/html-generators/locales.properties +++ b/html-generators/locales.properties @@ -11,4 +11,5 @@ ko=🇰🇷 한국어 bn=🇧🇩 বাংলা it=🇮🇹 Italiano pl=🇵🇱 Polski -tr=🇹🇷 Türkçe \ No newline at end of file +tr=🇹🇷 Türkçe +ru=🇷🇺 Русский \ No newline at end of file diff --git a/html-generators/socialpost.java b/html-generators/socialpost.java new file mode 100644 index 0000000..282008e --- /dev/null +++ b/html-generators/socialpost.java @@ -0,0 +1,206 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25 +//DEPS com.fasterxml.jackson.core:jackson-databind:2.18.3 +//DEPS com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.18.3 + +import module java.base; +import java.net.http.*; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; + +/** + * Post the next tweet from the social queue to Twitter/X. + * + * Reads state from social/state.yaml, posts via Twitter API v2, + * and updates state only after confirmed API success. + * + * Required environment variables: + * TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_KEY_SECRET, + * TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_TOKEN_SECRET + * + * Options: + * --dry-run Print the tweet without posting + */ + +static final String SOCIAL_DIR = "social"; +static final String QUEUE_FILE = SOCIAL_DIR + "/queue.txt"; +static final String TWEETS_FILE = SOCIAL_DIR + "/tweets.yaml"; +static final String STATE_FILE = SOCIAL_DIR + "/state.yaml"; +static final String TWITTER_API_URL = "https://api.twitter.com/2/tweets"; + +static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()); +static final ObjectMapper YAML_WRITER = new ObjectMapper( + new YAMLFactory() + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) +); +static final ObjectMapper JSON_MAPPER = new ObjectMapper(); + +void main(String... args) throws Exception { + boolean dryRun = List.of(args).contains("--dry-run"); + + // 1. Load queue, tweets, state + var queue = loadQueue(); + var tweets = loadTweets(); + var state = loadState(); + + int currentIndex = ((Number) state.get("currentIndex")).intValue(); + System.out.println("Queue has " + queue.size() + " entries, current index: " + currentIndex); + + // 2. Check if queue is exhausted + if (currentIndex > queue.size()) { + System.out.println("Queue exhausted — reshuffle needed."); + System.out.println("Run: jbang html-generators/generatesocialqueue.java --reshuffle"); + System.exit(1); + } + + // 3. Get the current pattern key and tweet text + var key = queue.get(currentIndex - 1); // 1-based index + var tweetText = tweets.get(key); + + if (tweetText == null) { + System.err.println("ERROR: No tweet text found for key: " + key); + System.err.println("Regenerate tweets: jbang html-generators/generatesocialqueue.java"); + System.exit(1); + } + + System.out.println("Pattern: " + key); + System.out.println("Tweet (" + tweetText.length() + " chars):"); + System.out.println("---"); + System.out.println(tweetText); + System.out.println("---"); + + if (dryRun) { + System.out.println("DRY RUN — not posting."); + return; + } + + // 4. Read Twitter credentials from environment + var consumerKey = requireEnv("TWITTER_CONSUMER_KEY"); + var consumerSecret = requireEnv("TWITTER_CONSUMER_KEY_SECRET"); + var accessToken = requireEnv("TWITTER_ACCESS_TOKEN"); + var accessTokenSecret = requireEnv("TWITTER_ACCESS_TOKEN_SECRET"); + + // 5. Post to Twitter + var tweetId = postTweet(tweetText, consumerKey, consumerSecret, accessToken, accessTokenSecret); + System.out.println("Posted! Tweet ID: " + tweetId); + + // 6. Update state only after success + state.put("currentIndex", currentIndex + 1); + state.put("lastPostedKey", key); + state.put("lastTweetId", tweetId); + state.put("lastPostedAt", java.time.Instant.now().toString()); + YAML_WRITER.writerWithDefaultPrettyPrinter().writeValue(Path.of(STATE_FILE).toFile(), state); + System.out.println("State updated: index now " + (currentIndex + 1)); +} + +// --- Twitter API v2 with OAuth 1.0a --- + +String postTweet(String text, String consumerKey, String consumerSecret, + String token, String tokenSecret) throws Exception { + var method = "POST"; + var url = TWITTER_API_URL; + + // OAuth parameters + var oauthParams = new TreeMap(); + oauthParams.put("oauth_consumer_key", consumerKey); + oauthParams.put("oauth_nonce", generateNonce()); + oauthParams.put("oauth_signature_method", "HMAC-SHA1"); + oauthParams.put("oauth_timestamp", String.valueOf(Instant.now().getEpochSecond())); + oauthParams.put("oauth_token", token); + oauthParams.put("oauth_version", "1.0"); + + // Build signature base string (no body params for JSON content type) + var paramString = oauthParams.entrySet().stream() + .map(e -> percentEncode(e.getKey()) + "=" + percentEncode(e.getValue())) + .collect(Collectors.joining("&")); + + var baseString = method + "&" + percentEncode(url) + "&" + percentEncode(paramString); + var signingKey = percentEncode(consumerSecret) + "&" + percentEncode(tokenSecret); + + var signature = hmacSha1(signingKey, baseString); + oauthParams.put("oauth_signature", signature); + + // Build Authorization header + var authHeader = "OAuth " + oauthParams.entrySet().stream() + .map(e -> percentEncode(e.getKey()) + "=\"" + percentEncode(e.getValue()) + "\"") + .collect(Collectors.joining(", ")); + + // Build JSON body + var bodyMap = Map.of("text", text); + var body = JSON_MAPPER.writeValueAsString(bodyMap); + + // Send request + var client = HttpClient.newHttpClient(); + var request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("Authorization", authHeader) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .build(); + + var response = client.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != 201) { + System.err.println("Twitter API error (HTTP " + response.statusCode() + "):"); + System.err.println(response.body()); + System.exit(1); + } + + var responseNode = JSON_MAPPER.readTree(response.body()); + return responseNode.path("data").path("id").asText(); +} + +String generateNonce() { + var bytes = new byte[32]; + new SecureRandom().nextBytes(bytes); + return HexFormat.of().formatHex(bytes); +} + +String hmacSha1(String key, String data) throws Exception { + var mac = javax.crypto.Mac.getInstance("HmacSHA1"); + mac.init(new javax.crypto.spec.SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA1")); + var raw = mac.doFinal(data.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(raw); +} + +String percentEncode(String value) { + return URLEncoder.encode(value, StandardCharsets.UTF_8) + .replace("+", "%20") + .replace("*", "%2A") + .replace("%7E", "~"); +} + +// --- File helpers --- + +List loadQueue() throws Exception { + var lines = Files.readAllLines(Path.of(QUEUE_FILE)).stream() + .map(String::strip) + .filter(s -> !s.isEmpty()) + .toList(); + if (lines.isEmpty()) { + System.err.println("ERROR: " + QUEUE_FILE + " is empty. Run the queue generator first."); + System.exit(1); + } + return lines; +} + +@SuppressWarnings("unchecked") +Map loadTweets() throws Exception { + return YAML_MAPPER.readValue(Path.of(TWEETS_FILE).toFile(), LinkedHashMap.class); +} + +@SuppressWarnings("unchecked") +Map loadState() throws Exception { + return YAML_MAPPER.readValue(Path.of(STATE_FILE).toFile(), LinkedHashMap.class); +} + +String requireEnv(String name) { + var value = System.getenv(name); + if (value == null || value.isBlank()) { + System.err.println("ERROR: Missing environment variable: " + name); + System.exit(1); + } + return value; +} diff --git a/secrets.md b/secrets.md new file mode 100644 index 0000000..cc9f938 --- /dev/null +++ b/secrets.md @@ -0,0 +1,32 @@ +# Repository Secrets + +This document lists the GitHub repository secrets configured in **Settings → Secrets and variables → Actions**. + +> **Note:** Secret values are never stored in the repository. They are managed exclusively through GitHub's encrypted secrets. See [GitHub docs](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) for details. + +## Secrets + +| Secret | Purpose | +|--------|---------| +| `COPILOT_GITHUB_TOKEN` | GitHub token used by Copilot for automated workflows | +| `TWITTER_APP_CONSUMER_KEY` | X (Twitter) API v2 OAuth 1.0a consumer key | +| `TWITTER_APP_SECRET_KEY` | X (Twitter) API v2 OAuth 1.0a consumer secret | +| `TWITTER_ACCESS_TOKEN` | X (Twitter) API v2 user access token | +| `TWITTER_ACCESS_TOKEN_SECRET` | X (Twitter) API v2 user access token secret | +| `TWITTER_APP_BEARER_TOKEN` | X (Twitter) API v2 OAuth 2.0 App-Only bearer token | +| `TWITTER_CLIENT_ID` | X (Twitter) OAuth 2.0 client ID | +| `TWITTER_CLIENT_SECRET` | X (Twitter) OAuth 2.0 client secret | + +## Usage + +### `COPILOT_GITHUB_TOKEN` + +Used by GitHub Copilot integrations. Not currently referenced in any workflow file. + +### Twitter / X Secrets + +Used for automated weekly social media posting of Java pattern updates to X (Twitter) via the X API v2. + +- **Workflow:** [`.github/workflows/social-post.yml`](.github/workflows/social-post.yml) — runs every Monday at 14:00 UTC +- **Post script:** [`html-generators/socialpost.java`](html-generators/socialpost.java) +- **Spec:** [`specs/social-posts-spec.md`](specs/social-posts-spec.md) diff --git a/social/queue.txt b/social/queue.txt new file mode 100644 index 0000000..ced2434 --- /dev/null +++ b/social/queue.txt @@ -0,0 +1,113 @@ +language/guarded-patterns +datetime/math-clamp +streams/optional-ifpresentorelse +strings/string-repeat +language/unnamed-variables +security/strong-random +io/writing-files +language/pattern-matching-instanceof +language/static-methods-in-interfaces +language/module-import-declarations +strings/string-strip +tooling/multi-file-source +datetime/hex-format +language/sealed-classes +strings/string-isblank +enterprise/servlet-vs-jaxrs +enterprise/jdbc-vs-jooq +collections/reverse-list-iteration +enterprise/jpa-vs-jakarta-data +tooling/jfr-profiling +concurrency/stable-values +enterprise/jdbc-resultset-vs-jpa-criteria +language/type-inference-with-var +streams/predicate-not +concurrency/process-api +datetime/java-time-basics +language/pattern-matching-switch +language/static-members-in-inner-classes +language/record-patterns +errors/helpful-npe +enterprise/spring-null-safety-jspecify +concurrency/executor-try-with-resources +streams/stream-tolist +strings/string-lines +collections/map-entry-factory +concurrency/scoped-values +language/markdown-javadoc-comments +concurrency/completablefuture-chaining +enterprise/spring-api-versioning +io/http-client +streams/stream-mapmulti +strings/string-formatted +datetime/duration-and-period +errors/optional-orelsethrow +io/path-of +collections/unmodifiable-collectors +enterprise/manual-transaction-vs-declarative +security/tls-default +enterprise/jndi-lookup-vs-cdi-injection +errors/null-in-switch +datetime/instant-precision +language/primitive-types-in-patterns +language/text-blocks-for-multiline-strings +io/file-memory-mapping +tooling/junit6-with-jspecify +streams/virtual-thread-executor +language/switch-expressions +errors/record-based-errors +streams/collectors-flatmapping +language/diamond-operator +language/compact-canonical-constructor +tooling/aot-class-preloading +io/deserialization-filters +enterprise/spring-xml-config-vs-annotations +security/key-derivation-functions +concurrency/virtual-threads +collections/immutable-list-creation +enterprise/jsf-managed-bean-vs-cdi-named +errors/multi-catch +io/try-with-resources-effectively-final +concurrency/lock-free-lazy-init +collections/stream-toarray-typed +tooling/jshell-prototyping +io/inputstream-transferto +collections/sequenced-collections +security/random-generator +language/compact-source-files +collections/immutable-set-creation +concurrency/structured-concurrency +tooling/built-in-http-server +errors/optional-chaining +language/flexible-constructor-bodies +io/reading-files +streams/stream-takewhile-dropwhile +streams/stream-of-nullable +collections/copying-collections-immutably +enterprise/singleton-ejb-vs-cdi-application-scoped +security/pem-encoding +language/default-interface-methods +io/io-class-console-io +language/records-for-data-classes +language/private-interface-methods +streams/stream-gatherers +strings/string-chars-stream +enterprise/soap-vs-jakarta-rest +enterprise/ejb-timer-vs-jakarta-scheduler +concurrency/concurrent-http-virtual +io/files-mismatch +collections/immutable-map-creation +tooling/single-file-execution +enterprise/mdb-vs-reactive-messaging +tooling/compact-object-headers +datetime/date-formatting +enterprise/ejb-vs-cdi +language/call-c-from-java +streams/optional-or +enterprise/jdbc-vs-jpa +errors/require-nonnull-else +streams/stream-iterate-predicate +strings/string-indent-transform +language/exhaustive-switch +collections/collectors-teeing +concurrency/thread-sleep-duration diff --git a/social/state.yaml b/social/state.yaml new file mode 100644 index 0000000..2267446 --- /dev/null +++ b/social/state.yaml @@ -0,0 +1,4 @@ +currentIndex: 5 +lastPostedKey: strings/string-repeat +lastTweetId: 2051331770377744701 +lastPostedAt: 2026-05-04T16:03:00.763027369Z diff --git a/social/tweets.yaml b/social/tweets.yaml new file mode 100644 index 0000000..7eacc4b --- /dev/null +++ b/social/tweets.yaml @@ -0,0 +1,1130 @@ +language/guarded-patterns: |- + ☕ Guarded patterns with when + + Add conditions to pattern cases using when guards. + + Nested if → when Clause (JDK 21+) + + 🔗 https://javaevolved.github.io/language/guarded-patterns.html + + #Java #JavaEvolved +datetime/math-clamp: |- + ☕ Math.clamp() + + Clamp a value between bounds with a single clear call. + + Nested min/max → Math.clamp() (JDK 21+) + + 🔗 https://javaevolved.github.io/datetime/math-clamp.html + + #Java #JavaEvolved +streams/optional-ifpresentorelse: |- + ☕ Optional.ifPresentOrElse() + + Handle both present and empty cases of Optional in one call. + + if/else on Optional → ifPresentOrElse() (JDK 9+) + + 🔗 https://javaevolved.github.io/streams/optional-ifpresentorelse.html + + #Java #JavaEvolved +strings/string-repeat: |- + ☕ String.repeat() + + Repeat a string n times without a loop. + + StringBuilder Loop → repeat() (JDK 11+) + + 🔗 https://javaevolved.github.io/strings/string-repeat.html + + #Java #JavaEvolved +language/unnamed-variables: |- + ☕ Unnamed variables with _ + + Use _ to signal intent when a variable is intentionally unused. + + Unused Variable → _ Placeholder (JDK 22+) + + 🔗 https://javaevolved.github.io/language/unnamed-variables.html + + #Java #JavaEvolved +security/strong-random: |- + ☕ Strong random generation + + Get the platform's strongest SecureRandom implementation. + + new SecureRandom() → getInstanceStrong() (JDK 9+) + + 🔗 https://javaevolved.github.io/security/strong-random.html + + #Java #JavaEvolved +io/writing-files: |- + ☕ Writing files + + Write a String to a file with one line. + + FileWriter + BufferedWriter → Files.writeString() (JDK 11+) + + 🔗 https://javaevolved.github.io/io/writing-files.html + + #Java #JavaEvolved +language/pattern-matching-instanceof: |- + ☕ Pattern matching for instanceof + + Combine type check and cast in one step with pattern matching. + + instanceof + Cast → Pattern Variable (JDK 16+) + + 🔗 https://javaevolved.github.io/language/pattern-matching-instanceof.html + + #Java #JavaEvolved +language/static-methods-in-interfaces: |- + ☕ Static methods in interfaces + + Add static utility methods directly to interfaces instead of separate utility classes. + + Utility classes → Interface static methods (JDK 8+) + + 🔗 https://javaevolved.github.io/language/static-methods-in-interfaces.html + + #Java #JavaEvolved +language/module-import-declarations: |- + ☕ Module import declarations + + Import all exported packages of a module with a single declaration. + + Many Imports → import module (JDK 25+) + + 🔗 https://javaevolved.github.io/language/module-import-declarations.html + + #Java #JavaEvolved +strings/string-strip: |- + ☕ String.strip() vs trim() + + Use Unicode-aware stripping with strip(), stripLeading(), stripTrailing(). + + trim() → strip() (JDK 11+) + + 🔗 https://javaevolved.github.io/strings/string-strip.html + + #Java #JavaEvolved +tooling/multi-file-source: |- + ☕ Multi-file source launcher + + Launch multi-file programs without an explicit compile step. + + Compile All First → Source Launcher (JDK 22+) + + 🔗 https://javaevolved.github.io/tooling/multi-file-source.html + + #Java #JavaEvolved +datetime/hex-format: |- + ☕ HexFormat + + Convert between hex strings and byte arrays with HexFormat. + + Manual Hex Conversion → HexFormat (JDK 17+) + + 🔗 https://javaevolved.github.io/datetime/hex-format.html + + #Java #JavaEvolved +language/sealed-classes: |- + ☕ Sealed classes for type hierarchies + + Restrict which classes can extend a type — enabling exhaustive switches. + + Open Hierarchy → sealed permits (JDK 17+) + + 🔗 https://javaevolved.github.io/language/sealed-classes.html + + #Java #JavaEvolved +strings/string-isblank: |- + ☕ String.isBlank() + + Check for blank strings with a single method call. + + trim().isEmpty() → isBlank() (JDK 11+) + + 🔗 https://javaevolved.github.io/strings/string-isblank.html + + #Java #JavaEvolved +enterprise/servlet-vs-jaxrs: |- + ☕ Servlet versus JAX-RS + + Replace verbose HttpServlet boilerplate with declarative JAX-RS resource classes. + + HttpServlet → JAX-RS Resource (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/servlet-vs-jaxrs.html + + #Java #JavaEvolved +enterprise/jdbc-vs-jooq: |- + ☕ JDBC versus jOOQ + + Replace raw JDBC string-based SQL with jOOQ's type-safe, fluent SQL DSL. + + Raw JDBC → jOOQ SQL DSL (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/jdbc-vs-jooq.html + + #Java #JavaEvolved +collections/reverse-list-iteration: |- + ☕ Reverse list iteration + + Iterate over a list in reverse order with a clean for-each loop. + + Manual ListIterator → reversed() (JDK 21+) + + 🔗 https://javaevolved.github.io/collections/reverse-list-iteration.html + + #Java #JavaEvolved +enterprise/jpa-vs-jakarta-data: |- + ☕ JPA versus Jakarta Data + + Declare a repository interface and let Jakarta Data generate the DAO implementation automatically. + + JPA EntityManager → Jakarta Data Repository (JDK 21+) + + 🔗 https://javaevolved.github.io/enterprise/jpa-vs-jakarta-data.html + + #Java #JavaEvolved +tooling/jfr-profiling: |- + ☕ JFR for profiling + + Profile any Java app with the built-in Flight Recorder — no external tools. + + External Profiler → Java Flight Recorder (JDK 9+) + + 🔗 https://javaevolved.github.io/tooling/jfr-profiling.html + + #Java #JavaEvolved +concurrency/stable-values: |- + ☕ Stable values + + Thread-safe lazy initialization without volatile or synchronized. + + Double-Checked Locking → StableValue (JDK 25+) + + 🔗 https://javaevolved.github.io/concurrency/stable-values.html + + #Java #JavaEvolved +enterprise/jdbc-resultset-vs-jpa-criteria: |- + ☕ JDBC ResultSet Mapping vs JPA Criteria API + + Replace manual JDBC ResultSet mapping with JPA's type-safe Criteria API for dynamic que… + + JDBC ResultSet → JPA Criteria API (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/jdbc-resultset-vs-jpa-criteria.html + + #Java #JavaEvolved +language/type-inference-with-var: |- + ☕ Type inference with var + + Use var for local variable type inference — less noise, same safety. + + Explicit Types → var keyword (JDK 10+) + + 🔗 https://javaevolved.github.io/language/type-inference-with-var.html + + #Java #JavaEvolved +streams/predicate-not: |- + ☕ Predicate.not() for negation + + Use Predicate.not() to negate method references cleanly instead of writing lambda wrappers. + + Lambda negation → Predicate.not() (JDK 11+) + + 🔗 https://javaevolved.github.io/streams/predicate-not.html + + #Java #JavaEvolved +concurrency/process-api: |- + ☕ Modern Process API + + Inspect and manage OS processes with ProcessHandle. + + Runtime.exec() → ProcessHandle (JDK 9+) + + 🔗 https://javaevolved.github.io/concurrency/process-api.html + + #Java #JavaEvolved +datetime/java-time-basics: |- + ☕ java.time API basics + + Use immutable, clear date/time types instead of Date and Calendar. + + Date + Calendar → java.time.* (JDK 8+) + + 🔗 https://javaevolved.github.io/datetime/java-time-basics.html + + #Java #JavaEvolved +language/pattern-matching-switch: |- + ☕ Pattern matching in switch + + Replace if-else instanceof chains with clean switch type patterns. + + if-else Chain → Type Patterns (JDK 21+) + + 🔗 https://javaevolved.github.io/language/pattern-matching-switch.html + + #Java #JavaEvolved +language/static-members-in-inner-classes: |- + ☕ Static members in inner classes + + Define static members in inner classes without requiring static nested… + + Must use static nested class → Static members in inner classes (JDK 16+) + + 🔗 https://javaevolved.github.io/language/static-members-in-inner-classes.html + + #Java #JavaEvolved +language/record-patterns: |- + ☕ Record patterns (destructuring) + + Destructure records directly in patterns — extract fields in one step. + + Manual Access → Destructuring (JDK 21+) + + 🔗 https://javaevolved.github.io/language/record-patterns.html + + #Java #JavaEvolved +errors/helpful-npe: |- + ☕ Helpful NullPointerExceptions + + JVM automatically tells you exactly which variable was null. + + Cryptic NPE → Detailed NPE (JDK 14+) + + 🔗 https://javaevolved.github.io/errors/helpful-npe.html + + #Java #JavaEvolved +enterprise/spring-null-safety-jspecify: |- + ☕ Spring Null Safety with JSpecify + + Spring 7 adopts JSpecify annotations, making non-null the default and reducing annota… + + Spring @NonNull/@Nullable → JSpecify @NullMarked (JDK 17+) + + 🔗 https://javaevolved.github.io/enterprise/spring-null-safety-jspecify.html + + #Java #JavaEvolved +concurrency/executor-try-with-resources: |- + ☕ ExecutorService auto-close + + Use try-with-resources for automatic executor shutdown. + + Manual Shutdown → try-with-resources (JDK 19+) + + 🔗 https://javaevolved.github.io/concurrency/executor-try-with-resources.html + + #Java #JavaEvolved +streams/stream-tolist: |- + ☕ Stream.toList() + + Terminal toList() replaces the verbose collect(Collectors.toList()). + + Collectors.toList() → .toList() (JDK 16+) + + 🔗 https://javaevolved.github.io/streams/stream-tolist.html + + #Java #JavaEvolved +strings/string-lines: |- + ☕ String.lines() for line splitting + + Use String.lines() to split text into a stream of lines without regex overhead. + + split("\\n") → lines() (JDK 11+) + + 🔗 https://javaevolved.github.io/strings/string-lines.html + + #Java #JavaEvolved +collections/map-entry-factory: |- + ☕ Map.entry() factory + + Create map entries with a clean factory method. + + SimpleEntry → Map.entry() (JDK 9+) + + 🔗 https://javaevolved.github.io/collections/map-entry-factory.html + + #Java #JavaEvolved +concurrency/scoped-values: |- + ☕ Scoped values + + Share data across call stacks safely without ThreadLocal pitfalls. + + ThreadLocal → ScopedValue (JDK 25+) + + 🔗 https://javaevolved.github.io/concurrency/scoped-values.html + + #Java #JavaEvolved +language/markdown-javadoc-comments: |- + ☕ Markdown in Javadoc comments + + Write Javadoc comments in Markdown instead of HTML for better readability. + + HTML-based Javadoc → Markdown Javadoc (JDK 23+) + + 🔗 https://javaevolved.github.io/language/markdown-javadoc-comments.html + + #Java #JavaEvolved +concurrency/completablefuture-chaining: |- + ☕ CompletableFuture chaining + + Chain async operations without blocking, using CompletableFuture. + + Blocking Future.get() → CompletableFuture (JDK 8+) + + 🔗 https://javaevolved.github.io/concurrency/completablefuture-chaining.html + + #Java #JavaEvolved +enterprise/spring-api-versioning: |- + ☕ Spring Framework 7 API Versioning + + Replace duplicated version-prefixed controllers with Spring Framework 7's native API ver… + + Manual URL Path Versioning → Native API Versioning (JDK 17+) + + 🔗 https://javaevolved.github.io/enterprise/spring-api-versioning.html + + #Java #JavaEvolved +io/http-client: |- + ☕ Modern HTTP client + + Use the built-in HttpClient for clean, modern HTTP requests. + + HttpURLConnection → HttpClient (JDK 11+) + + 🔗 https://javaevolved.github.io/io/http-client.html + + #Java #JavaEvolved +streams/stream-mapmulti: |- + ☕ Stream.mapMulti() + + Emit zero or more elements per input without creating intermediate streams. + + flatMap + List → mapMulti() (JDK 16+) + + 🔗 https://javaevolved.github.io/streams/stream-mapmulti.html + + #Java #JavaEvolved +strings/string-formatted: |- + ☕ String.formatted() + + Call formatted() on the template string itself. + + String.format() → formatted() (JDK 15+) + + 🔗 https://javaevolved.github.io/strings/string-formatted.html + + #Java #JavaEvolved +datetime/duration-and-period: |- + ☕ Duration and Period + + Calculate time differences with type-safe Duration and Period. + + Millisecond Math → Duration / Period (JDK 8+) + + 🔗 https://javaevolved.github.io/datetime/duration-and-period.html + + #Java #JavaEvolved +errors/optional-orelsethrow: |- + ☕ Optional.orElseThrow() without supplier + + Use Optional.orElseThrow() as a clearer, intent-revealing alternative to get(). + + get() or orElseThrow(supplier) → orElseThrow() (JDK 10+) + + 🔗 https://javaevolved.github.io/errors/optional-orelsethrow.html + + #Java #JavaEvolved +io/path-of: |- + ☕ Path.of() factory + + Use Path.of() — the modern factory method on the Path interface. + + Paths.get() → Path.of() (JDK 11+) + + 🔗 https://javaevolved.github.io/io/path-of.html + + #Java #JavaEvolved +collections/unmodifiable-collectors: |- + ☕ Unmodifiable collectors + + Collect directly to an unmodifiable list with stream.toList(). + + collectingAndThen → stream.toList() (JDK 16+) + + 🔗 https://javaevolved.github.io/collections/unmodifiable-collectors.html + + #Java #JavaEvolved +enterprise/manual-transaction-vs-declarative: |- + ☕ Manual JPA Transaction vs Declarative @Transactional + + Replace verbose begin/commit/rollback blocks with a single @Transactiona… + + Manual Transaction → @Transactional (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/manual-transaction-vs-declarative.html + + #Java #JavaEvolved +security/tls-default: |- + ☕ TLS 1.3 by default + + TLS 1.3 is enabled by default — no explicit protocol configuration needed. + + Manual TLS Config → TLS 1.3 Default (JDK 11+) + + 🔗 https://javaevolved.github.io/security/tls-default.html + + #Java #JavaEvolved +enterprise/jndi-lookup-vs-cdi-injection: |- + ☕ JNDI Lookup vs CDI Injection + + Replace fragile JNDI string lookups with type-safe CDI injection for container-managed resources. + + JNDI Lookup → CDI @Inject (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/jndi-lookup-vs-cdi-injection.html + + #Java #JavaEvolved +errors/null-in-switch: |- + ☕ Null case in switch + + Handle null directly as a switch case — no separate guard needed. + + Guard Before Switch → case null (JDK 21+) + + 🔗 https://javaevolved.github.io/errors/null-in-switch.html + + #Java #JavaEvolved +datetime/instant-precision: |- + ☕ Instant with nanosecond precision + + Get timestamps with microsecond or nanosecond precision. + + Milliseconds → Nanoseconds (JDK 9+) + + 🔗 https://javaevolved.github.io/datetime/instant-precision.html + + #Java #JavaEvolved +language/primitive-types-in-patterns: |- + ☕ Primitive types in patterns + + Pattern matching now works with primitive types, not just objects. + + Manual Range Checks → Primitive Patterns (JDK 25+) + + 🔗 https://javaevolved.github.io/language/primitive-types-in-patterns.html + + #Java #JavaEvolved +language/text-blocks-for-multiline-strings: |- + ☕ Text blocks for multiline strings + + Write multiline strings naturally with triple-quote text blocks. + + String Concatenation → Text Blocks (JDK 15+) + + 🔗 https://javaevolved.github.io/language/text-blocks-for-multiline-strings.html + + #Java #JavaEvolved +io/file-memory-mapping: |- + ☕ File memory mapping + + Map files larger than 2GB with deterministic cleanup using MemorySegment. + + MappedByteBuffer → MemorySegment with Arena (JDK 22+) + + 🔗 https://javaevolved.github.io/io/file-memory-mapping.html + + #Java #JavaEvolved +tooling/junit6-with-jspecify: |- + ☕ JUnit 6 with JSpecify null safety + + JUnit 6 adopts JSpecify @NullMarked, making null contracts explicit across its assertion API. + + Unannotated API → @NullMarked API (JDK 17+) + + 🔗 https://javaevolved.github.io/tooling/junit6-with-jspecify.html + + #Java #JavaEvolved +streams/virtual-thread-executor: |- + ☕ Virtual thread executor + + Use virtual thread executors for unlimited lightweight concurrency. + + Fixed Thread Pool → Virtual Thread Executor (JDK 21+) + + 🔗 https://javaevolved.github.io/streams/virtual-thread-executor.html + + #Java #JavaEvolved +language/switch-expressions: |- + ☕ Switch expressions + + Switch as an expression that returns a value — no break, no fall-through. + + Switch Statement → Switch Expression (JDK 14+) + + 🔗 https://javaevolved.github.io/language/switch-expressions.html + + #Java #JavaEvolved +errors/record-based-errors: |- + ☕ Record-based error responses + + Use records for concise, immutable error response types. + + Map or Verbose Class → Error Records (JDK 16+) + + 🔗 https://javaevolved.github.io/errors/record-based-errors.html + + #Java #JavaEvolved +streams/collectors-flatmapping: |- + ☕ Collectors.flatMapping() + + Use flatMapping() to flatten inside a grouping collector. + + Nested flatMap → flatMapping() (JDK 9+) + + 🔗 https://javaevolved.github.io/streams/collectors-flatmapping.html + + #Java #JavaEvolved +language/diamond-operator: |- + ☕ Diamond with anonymous classes + + Diamond operator now works with anonymous classes too. + + Repeat Type Args → Diamond <> (JDK 9+) + + 🔗 https://javaevolved.github.io/language/diamond-operator.html + + #Java #JavaEvolved +language/compact-canonical-constructor: |- + ☕ Compact canonical constructor + + Validate and normalize record fields without repeating parameter lists. + + Explicit constructor validation → Compact constructor (JDK 16+) + + 🔗 https://javaevolved.github.io/language/compact-canonical-constructor.html + + #Java #JavaEvolved +tooling/aot-class-preloading: |- + ☕ AOT class preloading + + Cache class loading and compilation for instant startup. + + Cold Start Every Time → AOT Cache (JDK 25+) + + 🔗 https://javaevolved.github.io/tooling/aot-class-preloading.html + + #Java #JavaEvolved +io/deserialization-filters: |- + ☕ Deserialization filters + + Restrict which classes can be deserialized to prevent attacks. + + Accept Everything → ObjectInputFilter (JDK 9+) + + 🔗 https://javaevolved.github.io/io/deserialization-filters.html + + #Java #JavaEvolved +enterprise/spring-xml-config-vs-annotations: |- + ☕ Spring XML Bean Config vs Annotation-Driven + + Replace verbose Spring XML bean definitions with concise annotation-dri… + + XML Bean Definitions → Annotation-Driven Beans (JDK 17+) + + 🔗 https://javaevolved.github.io/enterprise/spring-xml-config-vs-annotations.html + + #Java #JavaEvolved +security/key-derivation-functions: |- + ☕ Key Derivation Functions + + Derive cryptographic keys using the standard KDF API. + + Manual PBKDF2 → KDF API (JDK 25+) + + 🔗 https://javaevolved.github.io/security/key-derivation-functions.html + + #Java #JavaEvolved +concurrency/virtual-threads: |- + ☕ Virtual threads + + Create millions of lightweight virtual threads instead of heavy OS threads. + + Platform Threads → Virtual Threads (JDK 21+) + + 🔗 https://javaevolved.github.io/concurrency/virtual-threads.html + + #Java #JavaEvolved +collections/immutable-list-creation: |- + ☕ Immutable list creation + + Create immutable lists in one clean expression. + + Verbose Wrapping → List.of() (JDK 9+) + + 🔗 https://javaevolved.github.io/collections/immutable-list-creation.html + + #Java #JavaEvolved +enterprise/jsf-managed-bean-vs-cdi-named: |- + ☕ JSF Managed Bean vs CDI Named Bean + + Replace deprecated JSF @ManagedBean with CDI @Named for a unified dependency injection model. + + @ManagedBean → @Named + CDI (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/jsf-managed-bean-vs-cdi-named.html + + #Java #JavaEvolved +errors/multi-catch: |- + ☕ Multi-catch exception handling + + Catch multiple exception types in a single catch block. + + Separate Catch Blocks → Multi-catch (JDK 7+) + + 🔗 https://javaevolved.github.io/errors/multi-catch.html + + #Java #JavaEvolved +io/try-with-resources-effectively-final: |- + ☕ Try-with-resources improvement + + Use existing effectively-final variables directly in try-with-resources. + + Re-declare Variable → Effectively Final (JDK 9+) + + 🔗 https://javaevolved.github.io/io/try-with-resources-effectively-final.html + + #Java #JavaEvolved +concurrency/lock-free-lazy-init: |- + ☕ Lock-free lazy initialization + + Replace double-checked locking with StableValue for lazy singletons. + + synchronized + volatile → StableValue (JDK 25+) + + 🔗 https://javaevolved.github.io/concurrency/lock-free-lazy-init.html + + #Java #JavaEvolved +collections/stream-toarray-typed: |- + ☕ Typed stream toArray + + Filter a collection and collect the results to a typed array using a single stream expression. + + Manual Filter + Copy → toArray(generator) (JDK 8+) + + 🔗 https://javaevolved.github.io/collections/stream-toarray-typed.html + + #Java #JavaEvolved +tooling/jshell-prototyping: |- + ☕ JShell for prototyping + + Try Java expressions interactively without creating files. + + Create File + Compile + Run → jshell REPL (JDK 9+) + + 🔗 https://javaevolved.github.io/tooling/jshell-prototyping.html + + #Java #JavaEvolved +io/inputstream-transferto: |- + ☕ InputStream.transferTo() + + Copy an InputStream to an OutputStream in one call. + + Manual Copy Loop → transferTo() (JDK 9+) + + 🔗 https://javaevolved.github.io/io/inputstream-transferto.html + + #Java #JavaEvolved +collections/sequenced-collections: |- + ☕ Sequenced collections + + Access first/last elements and reverse views with clean API methods. + + Index Arithmetic → getFirst/getLast (JDK 21+) + + 🔗 https://javaevolved.github.io/collections/sequenced-collections.html + + #Java #JavaEvolved +security/random-generator: |- + ☕ RandomGenerator interface + + Use the RandomGenerator interface to choose random number algorithms by name without coupling t… + + new Random() / ThreadLocalRandom → RandomGenerator factory (JDK 17+) + + 🔗 https://javaevolved.github.io/security/random-generator.html + + #Java #JavaEvolved +language/compact-source-files: |- + ☕ Compact source files + + Write a complete program without class declaration or public static void main. + + Main Class Ceremony → void main() (JDK 25+) + + 🔗 https://javaevolved.github.io/language/compact-source-files.html + + #Java #JavaEvolved +collections/immutable-set-creation: |- + ☕ Immutable set creation + + Create immutable sets with a single factory call. + + Verbose Wrapping → Set.of() (JDK 9+) + + 🔗 https://javaevolved.github.io/collections/immutable-set-creation.html + + #Java #JavaEvolved +concurrency/structured-concurrency: |- + ☕ Structured concurrency + + Manage concurrent task lifetimes as a single unit of work. + + Manual Thread Lifecycle → StructuredTaskScope (JDK 25+) + + 🔗 https://javaevolved.github.io/concurrency/structured-concurrency.html + + #Java #JavaEvolved +tooling/built-in-http-server: |- + ☕ Built-in HTTP server + + Java 18 includes a built-in minimal HTTP server for prototyping and file serving. + + External Server / Framework → jwebserver CLI (JDK 18+) + + 🔗 https://javaevolved.github.io/tooling/built-in-http-server.html + + #Java #JavaEvolved +errors/optional-chaining: |- + ☕ Optional chaining + + Replace nested null checks with an Optional pipeline. + + Nested Null Checks → Optional Pipeline (JDK 9+) + + 🔗 https://javaevolved.github.io/errors/optional-chaining.html + + #Java #JavaEvolved +language/flexible-constructor-bodies: |- + ☕ Flexible constructor bodies + + Validate and compute values before calling super() or this(). + + Validate After super() → Code Before super() (JDK 25+) + + 🔗 https://javaevolved.github.io/language/flexible-constructor-bodies.html + + #Java #JavaEvolved +io/reading-files: |- + ☕ Reading files + + Read an entire file into a String with one line. + + BufferedReader → Files.readString() (JDK 11+) + + 🔗 https://javaevolved.github.io/io/reading-files.html + + #Java #JavaEvolved +streams/stream-takewhile-dropwhile: |- + ☕ Stream takeWhile / dropWhile + + Take or drop elements from a stream based on a predicate. + + Manual Loop → takeWhile/dropWhile (JDK 9+) + + 🔗 https://javaevolved.github.io/streams/stream-takewhile-dropwhile.html + + #Java #JavaEvolved +streams/stream-of-nullable: |- + ☕ Stream.ofNullable() + + Create a zero-or-one element stream from a nullable value. + + Null Check → ofNullable() (JDK 9+) + + 🔗 https://javaevolved.github.io/streams/stream-of-nullable.html + + #Java #JavaEvolved +collections/copying-collections-immutably: |- + ☕ Copying collections immutably + + Create an immutable copy of any collection in one call. + + Manual Copy + Wrap → List.copyOf() (JDK 10+) + + 🔗 https://javaevolved.github.io/collections/copying-collections-immutably.html + + #Java #JavaEvolved +enterprise/singleton-ejb-vs-cdi-application-scoped: |- + ☕ Singleton EJB vs CDI @ApplicationScoped + + Replace Singleton EJBs with CDI @ApplicationScoped beans for simpler shared… + + @Singleton EJB → @ApplicationScoped CDI (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/singleton-ejb-vs-cdi-application-scoped.html + + #Java #JavaEvolved +security/pem-encoding: |- + ☕ PEM encoding/decoding + + Encode and decode PEM-formatted cryptographic objects natively. + + Manual Base64 + Headers → PEM API (JDK 25+) + + 🔗 https://javaevolved.github.io/security/pem-encoding.html + + #Java #JavaEvolved +language/default-interface-methods: |- + ☕ Default interface methods + + Add method implementations directly in interfaces, enabling multiple inherita… + + Abstract classes for shared behavior → Default methods on interfaces (JDK 8+) + + 🔗 https://javaevolved.github.io/language/default-interface-methods.html + + #Java #JavaEvolved +io/io-class-console-io: |- + ☕ IO class for console I/O + + The new IO class provides simple, concise methods for console input and output. + + System.out / Scanner → IO class (JDK 25+) + + 🔗 https://javaevolved.github.io/io/io-class-console-io.html + + #Java #JavaEvolved +language/records-for-data-classes: |- + ☕ Records for data classes + + One line replaces 30+ lines of boilerplate for immutable data carriers. + + Verbose POJO → record (JDK 16+) + + 🔗 https://javaevolved.github.io/language/records-for-data-classes.html + + #Java #JavaEvolved +language/private-interface-methods: |- + ☕ Private interface methods + + Extract shared logic in interfaces using private methods. + + Duplicated Logic → Private Methods (JDK 9+) + + 🔗 https://javaevolved.github.io/language/private-interface-methods.html + + #Java #JavaEvolved +streams/stream-gatherers: |- + ☕ Stream gatherers + + Use gatherers for custom intermediate stream operations. + + Custom Collector → gather() (JDK 24+) + + 🔗 https://javaevolved.github.io/streams/stream-gatherers.html + + #Java #JavaEvolved +strings/string-chars-stream: |- + ☕ String chars as stream + + Process string characters as a stream pipeline. + + Manual Loop → chars() Stream (JDK 9+) + + 🔗 https://javaevolved.github.io/strings/string-chars-stream.html + + #Java #JavaEvolved +enterprise/soap-vs-jakarta-rest: |- + ☕ SOAP Web Services vs Jakarta REST + + Replace heavyweight SOAP/WSDL endpoints with clean Jakarta REST resources returning JSON. + + JAX-WS / SOAP → Jakarta REST / JSON (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/soap-vs-jakarta-rest.html + + #Java #JavaEvolved +enterprise/ejb-timer-vs-jakarta-scheduler: |- + ☕ EJB Timer vs Jakarta Scheduler + + Replace heavyweight EJB timers with Jakarta Concurrency's ManagedScheduledExecutor… + + EJB TimerService → ManagedScheduledExecutorService (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/ejb-timer-vs-jakarta-scheduler.html + + #Java #JavaEvolved +concurrency/concurrent-http-virtual: |- + ☕ Concurrent HTTP with virtual threads + + Fetch many URLs concurrently with virtual threads and HttpClient. + + Thread Pool + URLConnection → Virtual Threads + HttpClient (JDK 21+) + + 🔗 https://javaevolved.github.io/concurrency/concurrent-http-virtual.html + + #Java #JavaEvolved +io/files-mismatch: |- + ☕ Files.mismatch() + + Compare two files efficiently without loading them into memory. + + Manual Byte Compare → Files.mismatch() (JDK 12+) + + 🔗 https://javaevolved.github.io/io/files-mismatch.html + + #Java #JavaEvolved +collections/immutable-map-creation: |- + ☕ Immutable map creation + + Create immutable maps inline without a builder. + + Map Builder Pattern → Map.of() (JDK 9+) + + 🔗 https://javaevolved.github.io/collections/immutable-map-creation.html + + #Java #JavaEvolved +tooling/single-file-execution: |- + ☕ Single-file execution + + Run single-file Java programs directly without javac. + + Two-Step Compile → Direct Launch (JDK 11+) + + 🔗 https://javaevolved.github.io/tooling/single-file-execution.html + + #Java #JavaEvolved +enterprise/mdb-vs-reactive-messaging: |- + ☕ Message-Driven Bean vs Reactive Messaging + + Replace JMS Message-Driven Beans with MicroProfile Reactive Messaging for simpler even… + + Message-Driven Bean → Reactive Messaging (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/mdb-vs-reactive-messaging.html + + #Java #JavaEvolved +tooling/compact-object-headers: |- + ☕ Compact object headers + + Cut object header size in half for better memory density and cache usage. + + 128-bit Headers → 64-bit Headers (JDK 25+) + + 🔗 https://javaevolved.github.io/tooling/compact-object-headers.html + + #Java #JavaEvolved +datetime/date-formatting: |- + ☕ Date formatting + + Format dates with thread-safe, immutable DateTimeFormatter. + + SimpleDateFormat → DateTimeFormatter (JDK 8+) + + 🔗 https://javaevolved.github.io/datetime/date-formatting.html + + #Java #JavaEvolved +enterprise/ejb-vs-cdi: |- + ☕ EJB versus CDI + + Replace heavyweight EJBs with lightweight CDI beans for dependency injection and transactions. + + EJB → CDI Bean (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/ejb-vs-cdi.html + + #Java #JavaEvolved +language/call-c-from-java: |- + ☕ Calling out to C code from Java + + FFM lets Java call C libraries directly, without JNI boilerplate or C-side Java kn… + + JNI (Java Native Interface) → FFM (Foreign Function & Memory API) (JDK 22+) + + 🔗 https://javaevolved.github.io/language/call-c-from-java.html + + #Java #JavaEvolved +streams/optional-or: |- + ☕ Optional.or() fallback + + Chain Optional fallbacks without nested checks. + + Nested Fallback → .or() chain (JDK 9+) + + 🔗 https://javaevolved.github.io/streams/optional-or.html + + #Java #JavaEvolved +enterprise/jdbc-vs-jpa: |- + ☕ JDBC versus JPA + + Replace verbose JDBC boilerplate with JPA's object-relational mapping and EntityManager. + + JDBC → JPA EntityManager (JDK 11+) + + 🔗 https://javaevolved.github.io/enterprise/jdbc-vs-jpa.html + + #Java #JavaEvolved +errors/require-nonnull-else: |- + ☕ Objects.requireNonNullElse() + + Get a non-null value with a clear default, no ternary needed. + + Ternary Null Check → requireNonNullElse() (JDK 9+) + + 🔗 https://javaevolved.github.io/errors/require-nonnull-else.html + + #Java #JavaEvolved +streams/stream-iterate-predicate: |- + ☕ Stream.iterate() with predicate + + Use a predicate to stop iteration — like a for-loop in stream form. + + iterate + limit → iterate(seed, pred, op) (JDK 9+) + + 🔗 https://javaevolved.github.io/streams/stream-iterate-predicate.html + + #Java #JavaEvolved +strings/string-indent-transform: |- + ☕ String.indent() and transform() + + Indent text and chain string transformations fluently. + + Manual Indentation → indent() / transform() (JDK 12+) + + 🔗 https://javaevolved.github.io/strings/string-indent-transform.html + + #Java #JavaEvolved +language/exhaustive-switch: |- + ☕ Exhaustive switch without default + + Compiler verifies all sealed subtypes are covered — no default needed. + + Mandatory default → Sealed Exhaustiveness (JDK 21+) + + 🔗 https://javaevolved.github.io/language/exhaustive-switch.html + + #Java #JavaEvolved +collections/collectors-teeing: |- + ☕ Collectors.teeing() + + Compute two aggregations in a single stream pass. + + Two Passes → teeing() (JDK 12+) + + 🔗 https://javaevolved.github.io/collections/collectors-teeing.html + + #Java #JavaEvolved +concurrency/thread-sleep-duration: |- + ☕ Thread.sleep with Duration + + Use Duration for self-documenting time values. + + Milliseconds → Duration (JDK 19+) + + 🔗 https://javaevolved.github.io/concurrency/thread-sleep-duration.html + + #Java #JavaEvolved diff --git a/specs/social-posts-spec.md b/specs/social-posts-spec.md index 4fe7234..ee272ad 100644 --- a/specs/social-posts-spec.md +++ b/specs/social-posts-spec.md @@ -1,20 +1,23 @@ # Automated Weekly Social Posts — Specification ## Problem -Post one pattern per week to X/Twitter and Bluesky, covering all 112 patterns (~2 years of content). Fully automated via GitHub Actions with no manual steps. +Post one pattern per week to X/Twitter, covering all 113+ patterns (~2.2 years of content). Fully automated via GitHub Actions with no manual steps. ## Approach Use a **GitHub Actions scheduled workflow** that: -1. Reads a pre-shuffled queue file (`content/social-queue.txt`) listing all pattern keys -2. Each week, picks the next unposted pattern, posts to X and Bluesky -3. Commits the updated queue pointer back to the repo to track progress -4. When all 112 are exhausted, reshuffles and starts over +1. Reads a pre-shuffled queue file (`social/queue.txt`) listing all pattern keys +2. Each Monday, picks the next unposted pattern, posts to X/Twitter +3. Commits the updated state back to the repo to track progress +4. When all patterns are exhausted, reshuffles and starts over ### Why a queue file? - Deterministic: you can review/reorder upcoming posts - Resumable: survives workflow failures, repo changes - Auditable: git history shows what was posted when +### Pre-drafted tweets +All tweet copy is pre-generated into `social/tweets.yaml` so it can be reviewed and edited before posting. The queue generator builds tweets from content YAML fields and validates they fit within 280 characters. + ## Post Format ``` ☕ {title} @@ -30,41 +33,50 @@ Use a **GitHub Actions scheduled workflow** that: ## Implementation -### 1. Social Queue Generator -**File:** `html-generators/generate-social-queue.java` +### 1. Queue & Tweet Generator +**File:** `html-generators/generatesocialqueue.java` + +JBang script that reads all content files, shuffles them, and writes: +- `social/queue.txt` — one `category/slug` per line (posting order) +- `social/tweets.yaml` — pre-drafted tweet text for each pattern, keyed by `category/slug` +- `social/state.yaml` — posting state (`currentIndex`, `lastPostedKey`, `lastTweetId`, `lastPostedAt`) -JBang script that reads all content files, shuffles them, and writes `content/social-queue.txt` (one `category/slug` per line). +On re-run: detects new patterns (appends to end), prunes deleted patterns, preserves existing order and manual tweet edits. Use `--reshuffle` to force a full reshuffle. -### 2. Social Post Script -**File:** `html-generators/social-post.sh` +### 2. Post Script +**File:** `html-generators/socialpost.java` -Shell script that: -- Reads the next unposted line from `content/social-queue.txt` -- Loads the pattern's JSON/YAML to build the post text -- Posts to X/Twitter via API v2 (OAuth 1.0a) -- Posts to Bluesky via AT Protocol API -- Updates the pointer file (`content/social-queue-pointer.txt`) +JBang script that: +- Reads state from `social/state.yaml` +- Looks up the pre-drafted tweet text from `social/tweets.yaml` +- Posts to X/Twitter via API v2 (OAuth 1.0a with HMAC-SHA1 signing) +- Updates state only after confirmed API success +- Supports `--dry-run` to preview without posting ### 3. GitHub Actions Workflow **File:** `.github/workflows/social-post.yml` -- Schedule: weekly cron (e.g., Tuesday 15:00 UTC) -- Runs the post script -- Commits updated queue state back to repo +- Schedule: every Monday at 14:00 UTC (10 AM ET) +- Manual dispatch support (`workflow_dispatch`) +- Concurrency group prevents double-posts +- Commits updated state back to repo ## Required GitHub Secrets | Secret | Purpose | |--------|---------| -| `TWITTER_API_KEY` | X API v2 OAuth 1.0a consumer key | -| `TWITTER_API_SECRET` | X API v2 OAuth 1.0a consumer secret | +| `TWITTER_CONSUMER_KEY` | X API v2 OAuth 1.0a consumer key | +| `TWITTER_CONSUMER_KEY_SECRET` | X API v2 OAuth 1.0a consumer secret | | `TWITTER_ACCESS_TOKEN` | X API v2 user access token | -| `TWITTER_ACCESS_SECRET` | X API v2 user access secret | -| `BLUESKY_HANDLE` | Bluesky handle (e.g., javaevolved.bsky.social) | -| `BLUESKY_APP_PASSWORD` | Bluesky app password | +| `TWITTER_ACCESS_TOKEN_SECRET` | X API v2 user access token secret | ## Design Decisions -- **Text-only posts** with URL — platforms unfurl the OG card automatically from `og:image` meta tags +- **Twitter/X only** — Bluesky support can be added later +- **Text-only posts** with URL — platform unfurls the OG card automatically from `og:image` meta tags +- **Pre-drafted tweets** — generated into `social/tweets.yaml` for review; editable before posting - **Random order** via pre-shuffled queue for variety across categories -- **Reshuffles** when all 112 patterns are exhausted -- **Post script uses `curl`** for both APIs (no extra dependencies) -- **Queue state** tracked via a pointer file containing the current line number +- **Reshuffles** when all patterns are exhausted +- **JBang/Java for posting** — consistent with the rest of the project; safer for OAuth 1.0a signing than shell +- **State tracked** via `social/state.yaml` with `currentIndex`, `lastPostedKey`, `lastTweetId`, `lastPostedAt` +- **Social files in `social/`** (not `content/`) to avoid triggering site deploys +- **New patterns** appended to end of queue on re-run; deleted patterns pruned +- **Tweet length validation** — generator truncates summaries to fit 280 chars diff --git a/translations/content/ar/collections/copying-collections-immutably.yaml b/translations/content/ar/collections/copying-collections-immutably.yaml index a85704f..78391c7 100644 --- a/translations/content/ar/collections/copying-collections-immutably.yaml +++ b/translations/content/ar/collections/copying-collections-immutably.yaml @@ -11,7 +11,7 @@ whyModernWins: title: "استدعاء واحد" desc: "لا حاجة لإنشاء ArrayList يدوياً ثم تغليفه." - icon: "🛡️" - title: "نسخة دفاعية" - desc: "التغييرات في المصدر لا تؤثر على النسخة." + title: "أي مجموعة" + desc: "يقبل أي Collection كمدخل، دون الحاجة إلى تحويل ArrayList وسيط." support: description: "متاح على نطاق واسع منذ JDK 10 (مارس 2018)" diff --git a/translations/content/ar/collections/stream-toarray-typed.yaml b/translations/content/ar/collections/stream-toarray-typed.yaml index 73abc19..6bf32c8 100644 --- a/translations/content/ar/collections/stream-toarray-typed.yaml +++ b/translations/content/ar/collections/stream-toarray-typed.yaml @@ -1,5 +1,5 @@ title: "تحويل Stream إلى مصفوفة محدّدة النوع" -oldApproach: "نسخ المصفوفة يدوياً" +oldApproach: "تصفية يدوية + نسخ" modernApproach: "toArray(generator)" summary: "حوّل Stream إلى مصفوفة محدّدة النوع باستخدام مرجع دالة." explanation: "تُنشئ الدالة toArray(IntFunction) مصفوفةً محدّدة النوع من Stream. يُخبر المولّد (String[]::new) التدفقَ بنوع المصفوفة المراد إنشاؤها." diff --git a/translations/content/bn/collections/copying-collections-immutably.yaml b/translations/content/bn/collections/copying-collections-immutably.yaml index e991756..9598cc9 100644 --- a/translations/content/bn/collections/copying-collections-immutably.yaml +++ b/translations/content/bn/collections/copying-collections-immutably.yaml @@ -12,7 +12,7 @@ whyModernWins: title: এক কলে কাজ শেষ desc: ম্যানুয়াল ArrayList তৈরি বা র‍্যাপ করার দরকার নেই। - icon: "🛡️" - title: ডিফেন্সিভ কপি - desc: মূল কালেকশনে পরিবর্তন আনলেও কপি করা কালেকশনে তার কোনো প্রভাব পড়ে না। + title: যেকোনো Collection + desc: যেকোনো Collection ইনপুট হিসেবে গ্রহণ করে—মধ্যবর্তী ArrayList রূপান্তরের দরকার নেই। support: description: JDK 10 (মার্চ 2018) থেকে ব্যাপকভাবে উপলব্ধ \ No newline at end of file diff --git a/translations/content/bn/collections/stream-toarray-typed.yaml b/translations/content/bn/collections/stream-toarray-typed.yaml index 86a2cc3..c55d5b0 100644 --- a/translations/content/bn/collections/stream-toarray-typed.yaml +++ b/translations/content/bn/collections/stream-toarray-typed.yaml @@ -1,6 +1,6 @@ --- title: টাইপড স্ট্রিমকে অ্যারেতে রূপান্তর (Typed Stream toArray) -oldApproach: ম্যানুয়াল অ্যারে কপি +oldApproach: ম্যানুয়াল ফিল্টার + কপি modernApproach: toArray(generator) summary: একটি মেথড রেফারেন্স (method reference) ব্যবহার করে স্ট্রিমকে টাইপড অ্যারেতে রূপান্তর করুন। explanation: toArray(IntFunction) মেথডটি একটি স্ট্রিম থেকে সঠিক টাইপের অ্যারে তৈরি করে। এখানে জেনারেটর (যেমন String[]::new) স্ট্রিমকে বলে দেয় কোন ধরনের অ্যারে তৈরি করতে হবে। diff --git a/translations/content/de/collections/copying-collections-immutably.yaml b/translations/content/de/collections/copying-collections-immutably.yaml index 0ed476c..2734dfa 100644 --- a/translations/content/de/collections/copying-collections-immutably.yaml +++ b/translations/content/de/collections/copying-collections-immutably.yaml @@ -13,7 +13,7 @@ whyModernWins: title: Ein einziger Aufruf desc: Kein manuelles ArrayList-Erstellen und Verpacken. - icon: "🛡️" - title: Defensive Kopie - desc: Änderungen am Original wirken sich nicht auf die Kopie aus. + title: Jede Collection + desc: Nimmt jede Collection als Eingabe entgegen – keine ArrayList-Zwischenkonvertierung nötig. support: description: Weitgehend verfügbar seit JDK 10 (März 2018) diff --git a/translations/content/de/collections/stream-toarray-typed.yaml b/translations/content/de/collections/stream-toarray-typed.yaml index a0a0bc8..e4c2f46 100644 --- a/translations/content/de/collections/stream-toarray-typed.yaml +++ b/translations/content/de/collections/stream-toarray-typed.yaml @@ -1,5 +1,5 @@ title: Typisiertes Stream-toArray -oldApproach: Manuelle Array-Kopie +oldApproach: Manuelle Filterung + Kopie modernApproach: "toArray(generator)" summary: Streams mit einer Methodenreferenz in typisierte Arrays umwandeln. explanation: "Die Methode toArray(IntFunction) erstellt ein korrekt typisiertes Array\ diff --git a/translations/content/es/collections/copying-collections-immutably.yaml b/translations/content/es/collections/copying-collections-immutably.yaml index 910e832..a5094b4 100644 --- a/translations/content/es/collections/copying-collections-immutably.yaml +++ b/translations/content/es/collections/copying-collections-immutably.yaml @@ -14,7 +14,7 @@ whyModernWins: title: "Una sola llamada" desc: "Sin construcción manual de ArrayList + envoltorio." - icon: "🛡️" - title: "Copia defensiva" - desc: "Los cambios en el original no afectan a la copia." + title: "Cualquier Collection" + desc: "Acepta cualquier Collection como entrada, sin conversión intermedia a ArrayList." support: description: "Ampliamente disponible desde JDK 10 (marzo 2018)" diff --git a/translations/content/es/collections/stream-toarray-typed.yaml b/translations/content/es/collections/stream-toarray-typed.yaml index 086834a..5fa1312 100644 --- a/translations/content/es/collections/stream-toarray-typed.yaml +++ b/translations/content/es/collections/stream-toarray-typed.yaml @@ -1,6 +1,6 @@ --- title: "toArray tipado en streams" -oldApproach: "Copia manual de array" +oldApproach: "Filtro manual + copia" modernApproach: "toArray(generator)" summary: "Convierte streams en arrays tipados con una referencia a método." explanation: "El método toArray(IntFunction) crea un array correctamente tipado a\ diff --git a/translations/content/fr/collections/copying-collections-immutably.yaml b/translations/content/fr/collections/copying-collections-immutably.yaml index 4e9cf74..1021d2c 100644 --- a/translations/content/fr/collections/copying-collections-immutably.yaml +++ b/translations/content/fr/collections/copying-collections-immutably.yaml @@ -12,7 +12,7 @@ whyModernWins: title: "Un seul appel" desc: "Sans construction manuelle d'ArrayList + wrapper." - icon: "🛡️" - title: "Copie défensive" - desc: "Les modifications de l'original n'affectent pas la copie." + title: "N'importe quelle Collection" + desc: "Accepte n'importe quelle Collection en entrée—sans conversion intermédiaire en ArrayList." support: description: "Disponible depuis JDK 10 (mars 2018)" diff --git a/translations/content/fr/collections/stream-toarray-typed.yaml b/translations/content/fr/collections/stream-toarray-typed.yaml index 4340844..58e1c3c 100644 --- a/translations/content/fr/collections/stream-toarray-typed.yaml +++ b/translations/content/fr/collections/stream-toarray-typed.yaml @@ -1,6 +1,6 @@ --- title: "toArray typé dans les streams" -oldApproach: "Copie manuelle de tableau" +oldApproach: "Filtrage manuel + copie" modernApproach: "toArray(generator)" summary: "Convertit des streams en tableaux typés avec une référence de méthode." explanation: "La méthode toArray(IntFunction) crée un tableau correctement typé à partir d'un stream. Le générateur (String[]::new) indique au stream quel type de tableau créer." diff --git a/translations/content/it/collections/copying-collections-immutably.yaml b/translations/content/it/collections/copying-collections-immutably.yaml index 46eb60a..e7b3eb6 100644 --- a/translations/content/it/collections/copying-collections-immutably.yaml +++ b/translations/content/it/collections/copying-collections-immutably.yaml @@ -12,7 +12,7 @@ whyModernWins: title: Una chiamata desc: "Nessuna costruzione manuale di ArrayList + wrapping." - icon: 🛡️ - title: Copia difensiva - desc: "Le modifiche all'originale non influenzano la copia." + title: Qualsiasi Collection + desc: "Accetta qualsiasi Collection come input—senza conversione intermedia in ArrayList." support: description: Ampiamente disponibile dal JDK 10 (marzo 2018) diff --git a/translations/content/it/collections/stream-toarray-typed.yaml b/translations/content/it/collections/stream-toarray-typed.yaml index cdbff94..0376b75 100644 --- a/translations/content/it/collections/stream-toarray-typed.yaml +++ b/translations/content/it/collections/stream-toarray-typed.yaml @@ -1,6 +1,6 @@ --- title: Stream toArray tipizzato -oldApproach: Copia manuale dell'array +oldApproach: Filtraggio manuale + copia modernApproach: toArray(generatore) summary: "Converti gli stream in array tipizzati con un riferimento a metodo." explanation: "Il metodo toArray(IntFunction) crea un array correttamente tipizzato da uno stream. Il generatore (String[]::new) indica allo stream quale tipo di array creare." diff --git a/translations/content/ja/collections/copying-collections-immutably.yaml b/translations/content/ja/collections/copying-collections-immutably.yaml index 9373f50..467192b 100644 --- a/translations/content/ja/collections/copying-collections-immutably.yaml +++ b/translations/content/ja/collections/copying-collections-immutably.yaml @@ -11,7 +11,7 @@ whyModernWins: title: 1回の呼び出し desc: "ArrayListの手動構築+ラッピングは不要です。" - icon: "🛡️" - title: 防御的コピー - desc: "元のコレクションへの変更はコピーに影響しません。" + title: あらゆるCollectionに対応 + desc: "ArrayListへの中間変換なしに、あらゆるCollectionを入力として受け付けます。" support: description: "JDK 10(2018年3月)以降、広く利用可能" diff --git a/translations/content/ja/collections/stream-toarray-typed.yaml b/translations/content/ja/collections/stream-toarray-typed.yaml index b59b8b7..27b19bd 100644 --- a/translations/content/ja/collections/stream-toarray-typed.yaml +++ b/translations/content/ja/collections/stream-toarray-typed.yaml @@ -1,5 +1,5 @@ title: 型付きストリームのtoArray -oldApproach: 手動配列コピー +oldApproach: 手動フィルタリング + コピー modernApproach: toArray(ジェネレータ) summary: "メソッド参照を使ってストリームを型付き配列に変換する。" explanation: "toArray(IntFunction)メソッドはストリームから適切に型付けされた配列を作成します。ジェネレータ(String[]::new)は、ストリームが作成する配列の型を指定します。" diff --git a/translations/content/ko/collections/copying-collections-immutably.yaml b/translations/content/ko/collections/copying-collections-immutably.yaml index b3ffa2a..f821065 100644 --- a/translations/content/ko/collections/copying-collections-immutably.yaml +++ b/translations/content/ko/collections/copying-collections-immutably.yaml @@ -11,7 +11,7 @@ whyModernWins: title: "한 번의 호출" desc: "ArrayList 수동 생성과 래핑이 필요 없습니다." - icon: "🛡️" - title: "방어적 복사" - desc: "원본을 변경해도 복사본에 영향을 주지 않습니다." + title: "모든 Collection 지원" + desc: "ArrayList로 중간 변환 없이 모든 Collection을 입력으로 받습니다." support: description: "JDK 10 (2018년 3월) 이후 널리 사용 가능" diff --git a/translations/content/ko/collections/stream-toarray-typed.yaml b/translations/content/ko/collections/stream-toarray-typed.yaml index bd5db27..4c714d6 100644 --- a/translations/content/ko/collections/stream-toarray-typed.yaml +++ b/translations/content/ko/collections/stream-toarray-typed.yaml @@ -1,5 +1,5 @@ title: "타입이 지정된 스트림 toArray" -oldApproach: "수동 배열 복사" +oldApproach: "수동 필터 + 복사" modernApproach: "toArray(생성자)" summary: "메서드 참조로 스트림을 타입이 지정된 배열로 변환합니다." explanation: "toArray(IntFunction) 메서드는 스트림에서 올바르게 타입이 지정된 배열을 만듭니다. 생성자(String[]::new)는 스트림에 생성할 배열 타입을 알려줍니다." diff --git a/translations/content/pl/collections/copying-collections-immutably.yaml b/translations/content/pl/collections/copying-collections-immutably.yaml index 869b400..1e470db 100644 --- a/translations/content/pl/collections/copying-collections-immutably.yaml +++ b/translations/content/pl/collections/copying-collections-immutably.yaml @@ -11,7 +11,7 @@ whyModernWins: title: Jedno wywołanie desc: Bez ręcznej konstrukcji ArrayList i opakowywania. - icon: 🛡️ - title: Kopia defensywna - desc: Zmiany w oryginale nie wpływają na kopię. + title: Dowolna kolekcja + desc: Przyjmuje dowolną kolekcję jako wejście—bez pośredniej konwersji do ArrayList. support: description: Szeroko dostępne od JDK 10 (marzec 2018) diff --git a/translations/content/pl/collections/stream-toarray-typed.yaml b/translations/content/pl/collections/stream-toarray-typed.yaml index 6361733..0a3e627 100644 --- a/translations/content/pl/collections/stream-toarray-typed.yaml +++ b/translations/content/pl/collections/stream-toarray-typed.yaml @@ -1,5 +1,5 @@ title: Typowana konwersja strumienia do tablicy -oldApproach: Ręczne kopiowanie tablicy +oldApproach: Ręczne filtrowanie + kopiowanie modernApproach: toArray(generator) summary: Konwertuj strumienie do typowanych tablic za pomocą referencji do metody. explanation: Metoda toArray(IntFunction) tworzy poprawnie typowaną tablicę ze strumienia. Generator (String[]::new) mówi strumieniowi, jakiego typu tablicę utworzyć. diff --git a/translations/content/pt-BR/collections/copying-collections-immutably.yaml b/translations/content/pt-BR/collections/copying-collections-immutably.yaml index 599629f..c80e203 100644 --- a/translations/content/pt-BR/collections/copying-collections-immutably.yaml +++ b/translations/content/pt-BR/collections/copying-collections-immutably.yaml @@ -13,7 +13,7 @@ whyModernWins: title: Uma chamada desc: Sem construção manual de ArrayList + encapsulamento. - icon: "🛡️" - title: Cópia defensiva - desc: Alterações na coleção original não afetam a cópia. + title: Qualquer Collection + desc: Aceita qualquer Collection como entrada—sem conversão intermediária para ArrayList. support: description: Amplamente disponível desde o JDK 10 (março de 2018) diff --git a/translations/content/pt-BR/collections/stream-toarray-typed.yaml b/translations/content/pt-BR/collections/stream-toarray-typed.yaml index fb6d5a9..0b9212f 100644 --- a/translations/content/pt-BR/collections/stream-toarray-typed.yaml +++ b/translations/content/pt-BR/collections/stream-toarray-typed.yaml @@ -1,5 +1,5 @@ title: toArray tipado com streams -oldApproach: Cópia manual de array +oldApproach: Filtro manual + cópia modernApproach: toArray(generator) summary: Converta streams em arrays tipados com uma referência de método. explanation: "O método toArray(IntFunction) cria um array com tipo correto a partir\ diff --git a/translations/content/ru/collections/collectors-teeing.yaml b/translations/content/ru/collections/collectors-teeing.yaml new file mode 100644 index 0000000..b468e9d --- /dev/null +++ b/translations/content/ru/collections/collectors-teeing.yaml @@ -0,0 +1,19 @@ +title: "Collectors.teeing()" +oldApproach: Два прохода +modernApproach: "teeing()" +summary: Вычисление двух агрегаций за один проход по потоку. +explanation: "Collectors.teeing() направляет каждый элемент в два дочерних Collector\ + \ и объединяет результаты. Это позволяет избежать двукратного обхода данных или\ + \ использования изменяемого аккумулятора." +whyModernWins: +- icon: "⚡" + title: Один проход + desc: Обработать поток один раз вместо двух. +- icon: "🧩" + title: Комбинируемый + desc: Соединить два произвольных Collector с функцией слияния. +- icon: "🔒" + title: Неизменяемый результат + desc: Сразу объединить в Record или Value-Object. +support: + description: Широко доступно начиная с JDK 12 (март 2019) diff --git a/translations/content/ru/collections/copying-collections-immutably.yaml b/translations/content/ru/collections/copying-collections-immutably.yaml new file mode 100644 index 0000000..e64f8e4 --- /dev/null +++ b/translations/content/ru/collections/copying-collections-immutably.yaml @@ -0,0 +1,19 @@ +title: Неизменяемое копирование коллекций +oldApproach: Ручное копирование + обёртка +modernApproach: "List.copyOf()" +summary: Создание неизменяемой копии любой коллекции одним вызовом. +explanation: "List.copyOf(), Set.copyOf() и Map.copyOf() создают неизменяемые снимки\ + \ существующих коллекций. Если источник уже является неизменяемой коллекцией,\ + \ копия не создаётся." +whyModernWins: +- icon: "⚡" + title: Умное копирование + desc: Копирование пропускается, если источник уже неизменяем. +- icon: "📏" + title: Один вызов + desc: Без ручного создания ArrayList и обёртывания. +- icon: "🛡️" + title: Любая коллекция + desc: Принимает любую Collection на входе — без промежуточного преобразования в ArrayList. +support: + description: Широко доступно начиная с JDK 10 (март 2018) diff --git a/translations/content/ru/collections/immutable-list-creation.yaml b/translations/content/ru/collections/immutable-list-creation.yaml new file mode 100644 index 0000000..4f174d3 --- /dev/null +++ b/translations/content/ru/collections/immutable-list-creation.yaml @@ -0,0 +1,19 @@ +title: Создание неизменяемых списков +oldApproach: Многословное обёртывание +modernApproach: "List.of()" +summary: Создание неизменяемых списков одним чистым выражением. +explanation: "List.of() создаёт по-настоящему неизменяемый список — без обёртывания\ + \ и защитного копирования. Не допускает null-элементов и структурно неизменяем.\ + \ Старый подход требовал трёх вложенных вызовов." +whyModernWins: +- icon: "📏" + title: Один вызов + desc: Заменить три вложенных вызова одним фабричным методом. +- icon: "🔒" + title: По-настоящему неизменяемый + desc: Не просто обёртка — сам список неизменяем. +- icon: "🛡️" + title: Безопасен для null + desc: Отклоняет null-элементы при создании и немедленно завершается с ошибкой. +support: + description: Широко доступно начиная с JDK 9 (сент. 2017) diff --git a/translations/content/ru/collections/immutable-map-creation.yaml b/translations/content/ru/collections/immutable-map-creation.yaml new file mode 100644 index 0000000..bfd410e --- /dev/null +++ b/translations/content/ru/collections/immutable-map-creation.yaml @@ -0,0 +1,18 @@ +title: Создание неизменяемых Map +oldApproach: Паттерн Map-Builder +modernApproach: "Map.of()" +summary: Создание неизменяемых Map встроенно без Builder. +explanation: "Map.of() принимает пары ключ-значение встроенно и возвращает неизменяемую\ + \ Map. Для более чем 10 записей использовать Map.ofEntries() с парами Map.entry()." +whyModernWins: +- icon: "📏" + title: Встроенное создание + desc: Не требует временной изменяемой Map. +- icon: "🔒" + title: Неизменяемый результат + desc: Map нельзя изменить после создания. +- icon: "🚫" + title: Без null-ключей/значений + desc: Null-записи немедленно отклоняются. +support: + description: Широко доступно начиная с JDK 9 (сент. 2017) diff --git a/translations/content/ru/collections/immutable-set-creation.yaml b/translations/content/ru/collections/immutable-set-creation.yaml new file mode 100644 index 0000000..7abd118 --- /dev/null +++ b/translations/content/ru/collections/immutable-set-creation.yaml @@ -0,0 +1,18 @@ +title: Создание неизменяемых Set +oldApproach: Многословное обёртывание +modernApproach: "Set.of()" +summary: Создание неизменяемых Set одним фабричным вызовом. +explanation: "Set.of() создаёт по-настоящему неизменяемое Set, которое отклоняет\ + \ null-элементы и дубликаты при создании. Больше никакого обёртывания изменяемых Set." +whyModernWins: +- icon: "📏" + title: Лаконично + desc: Одна строка вместо трёх вложенных вызовов. +- icon: "🚫" + title: Обнаруживает дубликаты + desc: Выбрасывает исключение при случайной передаче повторяющихся элементов. +- icon: "🔒" + title: Неизменяемый + desc: Нельзя добавлять или удалять элементы после создания. +support: + description: Широко доступно начиная с JDK 9 (сент. 2017) diff --git a/translations/content/ru/collections/map-entry-factory.yaml b/translations/content/ru/collections/map-entry-factory.yaml new file mode 100644 index 0000000..eb42a22 --- /dev/null +++ b/translations/content/ru/collections/map-entry-factory.yaml @@ -0,0 +1,19 @@ +title: "Фабрика Map.entry()" +oldApproach: SimpleEntry +modernApproach: "Map.entry()" +summary: Создание записей Map с помощью чистого фабричного метода. +explanation: "Map.entry() заменяет многословный конструктор AbstractMap.SimpleEntry.\ + \ Возвращает неизменяемую запись, идеально подходящую для Map.ofEntries()\ + \ и операций со Stream." +whyModernWins: +- icon: "📏" + title: Лаконично + desc: Одна строка вместо трёх с более чётким намерением. +- icon: "🔒" + title: Неизменяемый + desc: Возвращаемую запись нельзя изменить. +- icon: "🧩" + title: Комбинируемый + desc: "Отлично работает с Map.ofEntries() для больших Map." +support: + description: Широко доступно начиная с JDK 9 (сент. 2017) diff --git a/translations/content/ru/collections/reverse-list-iteration.yaml b/translations/content/ru/collections/reverse-list-iteration.yaml new file mode 100644 index 0000000..906d2ca --- /dev/null +++ b/translations/content/ru/collections/reverse-list-iteration.yaml @@ -0,0 +1,20 @@ +title: Обратная итерация по списку +oldApproach: Ручной ListIterator +modernApproach: "reversed()" +summary: Итерация по списку в обратном порядке с помощью чистого цикла for-each. +explanation: "Метод reversed() из SequencedCollection возвращает представление списка\ + \ в обратном порядке. Это представление опирается на исходный список, поэтому\ + \ копирование не происходит. Расширенный цикл for делает обратную итерацию\ + \ такой же читаемой, как прямую." +whyModernWins: +- icon: "📖" + title: Естественный синтаксис + desc: Расширенный цикл for вместо многословного ListIterator. +- icon: "⚡" + title: Без копирования + desc: "reversed() возвращает представление — без накладных расходов на производительность." +- icon: "🧩" + title: Согласованный API + desc: "Работает единообразно на List, Deque и SortedSet." +support: + description: Широко доступно начиная с JDK 21 LTS (сент. 2023) diff --git a/translations/content/ru/collections/sequenced-collections.yaml b/translations/content/ru/collections/sequenced-collections.yaml new file mode 100644 index 0000000..43deeef --- /dev/null +++ b/translations/content/ru/collections/sequenced-collections.yaml @@ -0,0 +1,19 @@ +title: Упорядоченные коллекции +oldApproach: Арифметика с индексами +modernApproach: getFirst/getLast +summary: "Доступ к первому/последнему элементу и получение обратных представлений с помощью чистых методов API." +explanation: "SequencedCollection добавляет getFirst(), getLast(), reversed(), addFirst()\ + \ и addLast() к List, Deque, SortedSet и LinkedHashSet. Больше никакой арифметики\ + \ size-1 или ручной обратной итерации." +whyModernWins: +- icon: "📖" + title: Самодокументируемый + desc: "getLast() понятнее, чем get(size()-1)." +- icon: "🔄" + title: Обратное представление + desc: "reversed() возвращает представление — копирование не требуется." +- icon: "🧩" + title: Единый API + desc: "Работает одинаково на List, Deque и SortedSet." +support: + description: Широко доступно начиная с JDK 21 LTS (сент. 2023) diff --git a/translations/content/ru/collections/stream-toarray-typed.yaml b/translations/content/ru/collections/stream-toarray-typed.yaml new file mode 100644 index 0000000..f2d1ce9 --- /dev/null +++ b/translations/content/ru/collections/stream-toarray-typed.yaml @@ -0,0 +1,19 @@ +title: Типизированный Stream-toArray +oldApproach: Ручная фильтрация + копирование +modernApproach: "toArray(generator)" +summary: Преобразование Stream в типизированные массивы с помощью ссылки на метод. +explanation: "Метод toArray(IntFunction) создаёт правильно типизированный массив\ + \ из Stream. Генератор (String[]::new) сообщает Stream, какой тип массива\ + \ нужно создать." +whyModernWins: +- icon: "🎯" + title: Типобезопасный + desc: "Без приведения к Object[] — тип массива правильный." +- icon: "🔗" + title: Цепочечный + desc: Работает в конце любого потокового конвейера. +- icon: "📏" + title: Лаконично + desc: Одно выражение заменяет ручной цикл. +support: + description: Широко доступно начиная с JDK 8 (март 2014) diff --git a/translations/content/ru/collections/unmodifiable-collectors.yaml b/translations/content/ru/collections/unmodifiable-collectors.yaml new file mode 100644 index 0000000..c56b137 --- /dev/null +++ b/translations/content/ru/collections/unmodifiable-collectors.yaml @@ -0,0 +1,20 @@ +title: Немодифицируемые Collector-ы +oldApproach: collectingAndThen +modernApproach: "stream.toList()" +summary: "Сбор напрямую в немодифицируемый список с помощью stream.toList()." +explanation: "Java 10 добавила toUnmodifiableList(), toUnmodifiableSet() и toUnmodifiableMap()\ + \ для замены многословной обёртки collectingAndThen. Для списков stream.toList()\ + \ из Java 16 предлагает ещё более простую альтернативу — без вызова collect().\ + \ Для других типов коллекций использовать toUnmodifiableSet() и toUnmodifiableMap()." +whyModernWins: +- icon: "📏" + title: Максимально краткий + desc: "stream.toList() не требует ни collect(), ни импорта Collectors." +- icon: "🔒" + title: Неизменяемый + desc: "Результат нельзя изменить — никаких случайных мутаций." +- icon: "📖" + title: Читаемый + desc: Читается естественно как завершающий шаг любого потокового конвейера. +support: + description: Широко доступно начиная с JDK 16 (март 2021) diff --git a/translations/content/ru/concurrency/completablefuture-chaining.yaml b/translations/content/ru/concurrency/completablefuture-chaining.yaml new file mode 100644 index 0000000..64ed4bb --- /dev/null +++ b/translations/content/ru/concurrency/completablefuture-chaining.yaml @@ -0,0 +1,20 @@ +title: Цепочки CompletableFuture +oldApproach: "Блокирующий Future.get()" +modernApproach: CompletableFuture +summary: Объединение асинхронных операций в цепочки без блокировки с помощью CompletableFuture. +explanation: "CompletableFuture позволяет строить неблокирующие асинхронные конвейеры.\ + \ Операции объединяются с помощью thenApply, thenCompose, thenAccept. Ошибки\ + \ обрабатываются через exceptionally(). Несколько Future можно комбинировать\ + \ с allOf/anyOf." +whyModernWins: +- icon: "🔗" + title: Цепочки + desc: Объединение асинхронных шагов в читаемый конвейер. +- icon: "🚫" + title: Без блокировок + desc: Ни один поток не ждёт результатов впустую. +- icon: "🛡️" + title: Обработка ошибок + desc: "exceptionally() и handle() для надёжного восстановления после ошибок." +support: + description: Широко доступно с JDK 8 (март 2014) diff --git a/translations/content/ru/concurrency/concurrent-http-virtual.yaml b/translations/content/ru/concurrency/concurrent-http-virtual.yaml new file mode 100644 index 0000000..1590074 --- /dev/null +++ b/translations/content/ru/concurrency/concurrent-http-virtual.yaml @@ -0,0 +1,19 @@ +title: Параллельные HTTP-запросы с виртуальными потоками +oldApproach: Thread Pool + URLConnection +modernApproach: Виртуальные потоки + HttpClient +summary: Одновременная загрузка множества URL с помощью виртуальных потоков и HttpClient. +explanation: "Виртуальные потоки позволяют практично создавать по одному потоку на каждый\ + \ HTTP-запрос. В сочетании с HttpClient это заменяет сложные асинхронные колбэк-паттерны\ + \ простым блокирующим кодом, который хорошо масштабируется." +whyModernWins: +- icon: "♾️" + title: Один поток на запрос + desc: "Без настройки пула — один виртуальный поток на URL." +- icon: "📖" + title: Простой код + desc: Пишите простой блокирующий код. +- icon: "⚡" + title: Высокая пропускная способность + desc: Тысячи одновременных запросов с минимальными ресурсами. +support: + description: Широко доступно с JDK 21 LTS (сент. 2023) diff --git a/translations/content/ru/concurrency/executor-try-with-resources.yaml b/translations/content/ru/concurrency/executor-try-with-resources.yaml new file mode 100644 index 0000000..6d75fed --- /dev/null +++ b/translations/content/ru/concurrency/executor-try-with-resources.yaml @@ -0,0 +1,19 @@ +title: Автоматическое закрытие ExecutorService +oldApproach: Ручное завершение работы +modernApproach: try-with-resources +summary: Использование try-with-resources для автоматического завершения работы исполнителя. +explanation: "Начиная с Java 19, ExecutorService реализует AutoCloseable. Метод\ + \ close() вызывает shutdown() и ожидает завершения задач. Больше не нужны ручные\ + \ паттерны try/finally для завершения работы." +whyModernWins: +- icon: "🧹" + title: Автоматическая очистка + desc: Завершение работы происходит автоматически при выходе из блока. +- icon: "🛡️" + title: Нет утечек + desc: "Исполнитель всегда завершает работу, даже при исключениях." +- icon: "📖" + title: Знакомый паттерн + desc: "Тот же try-with-resources, что и для файлов, соединений и т.д." +support: + description: Широко доступно с JDK 19 (сент. 2022) diff --git a/translations/content/ru/concurrency/lock-free-lazy-init.yaml b/translations/content/ru/concurrency/lock-free-lazy-init.yaml new file mode 100644 index 0000000..84f85e1 --- /dev/null +++ b/translations/content/ru/concurrency/lock-free-lazy-init.yaml @@ -0,0 +1,19 @@ +title: Ленивая инициализация без блокировок +oldApproach: synchronized + volatile +modernApproach: StableValue +summary: Замена двойной проверки блокировки на StableValue для ленивых синглтонов. +explanation: "StableValue инкапсулирует паттерн ленивой инициализации с корректной\ + \ потокобезопасностью. JVM может оптимизировать путь чтения после инициализации,\ + \ делая его потенциально быстрее, чем volatile-чтения." +whyModernWins: +- icon: "🧹" + title: Нет шаблонного кода + desc: "Никаких volatile, synchronized или двойных проверок на null." +- icon: "⚡" + title: Быстрое чтение + desc: JVM может свернуть значение в константу после инициализации. +- icon: "✅" + title: Гарантированная корректность + desc: "Никаких тонких ошибок порядка — JVM берёт это на себя." +support: + description: "Preview в JDK 25 (JEP 502, StableValue). Требует --enable-preview." diff --git a/translations/content/ru/concurrency/process-api.yaml b/translations/content/ru/concurrency/process-api.yaml new file mode 100644 index 0000000..7ed617c --- /dev/null +++ b/translations/content/ru/concurrency/process-api.yaml @@ -0,0 +1,19 @@ +title: Современный API процессов +oldApproach: "Runtime.exec()" +modernApproach: ProcessHandle +summary: Инспектирование и управление процессами ОС с помощью ProcessHandle. +explanation: "ProcessHandle предоставляет PID, информацию о процессе (команда, аргументы,\ + \ время запуска, загрузка CPU), отношения родитель-потомок и завершение процесса.\ + \ Больше не нужны недокументированные внутренности Process." +whyModernWins: +- icon: "🔍" + title: Полная информация + desc: "Доступ к PID, команде, аргументам, времени запуска, загрузке CPU." +- icon: "🌳" + title: Дерево процессов + desc: "Навигация между родительскими, дочерними и дочерними процессами." +- icon: "📊" + title: Мониторинг + desc: "onExit() возвращает CompletableFuture для асинхронного мониторинга." +support: + description: Широко доступно с JDK 9 (сент. 2017) diff --git a/translations/content/ru/concurrency/scoped-values.yaml b/translations/content/ru/concurrency/scoped-values.yaml new file mode 100644 index 0000000..d38ae3d --- /dev/null +++ b/translations/content/ru/concurrency/scoped-values.yaml @@ -0,0 +1,19 @@ +title: Scoped Values +oldApproach: ThreadLocal +modernApproach: ScopedValue +summary: Безопасный обмен данными по стеку вызовов без ловушек ThreadLocal. +explanation: "ScopedValue предоставляет неизменяемый, наследуемый, ограниченный\ + \ контекст. В отличие от ThreadLocal, Scoped Values автоматически очищаются,\ + \ работают с виртуальными потоками и не могут быть изменены вызываемыми методами." +whyModernWins: +- icon: "🔒" + title: Неизменяемость + desc: "Вызываемые методы могут читать Scoped Value, но не могут его изменить." +- icon: "🧹" + title: Автоматическая очистка + desc: "Нет ручного remove() — значение ограничено блоком." +- icon: "⚡" + title: Совместимость с виртуальными потоками + desc: Эффективно работает с миллионами виртуальных потоков. +support: + description: "Финализировано в JDK 25 LTS (JEP 506, сент. 2025)." diff --git a/translations/content/ru/concurrency/stable-values.yaml b/translations/content/ru/concurrency/stable-values.yaml new file mode 100644 index 0000000..7361946 --- /dev/null +++ b/translations/content/ru/concurrency/stable-values.yaml @@ -0,0 +1,19 @@ +title: Stable Values +oldApproach: Двойная проверка блокировки +modernApproach: StableValue +summary: Потокобезопасная ленивая инициализация без volatile или synchronized. +explanation: "StableValue предоставляет лениво инициализируемое неизменяемое значение\ + \ со встроенной потокобезопасностью. Никакой двойной проверки блокировки, volatile-полей,\ + \ synchronized-блоков. JVM может даже оптимизировать путь чтения после инициализации." +whyModernWins: +- icon: "🧹" + title: Нулевой шаблонный код + desc: "Никаких volatile, synchronized или проверок на null." +- icon: "⚡" + title: Оптимизировано JVM + desc: JVM может свернуть значение после инициализации. +- icon: "🛡️" + title: Гарантировано однократное выполнение + desc: "Supplier выполняется ровно один раз, даже при конкуренции." +support: + description: "Preview в JDK 25 (JEP 502). Требует --enable-preview." diff --git a/translations/content/ru/concurrency/structured-concurrency.yaml b/translations/content/ru/concurrency/structured-concurrency.yaml new file mode 100644 index 0000000..6da32de --- /dev/null +++ b/translations/content/ru/concurrency/structured-concurrency.yaml @@ -0,0 +1,20 @@ +title: Структурированный параллелизм +oldApproach: Ручное управление жизненным циклом потоков +modernApproach: StructuredTaskScope +summary: Управление жизненным циклом параллельных задач как единой единицей работы. +explanation: "Структурированный параллелизм рассматривает группу параллельных задач\ + \ как единую операцию. Если одна подзадача завершается с ошибкой, остальные\ + \ отменяются. Scope гарантирует, что ни один поток не будет потерян, и создаёт\ + \ чёткие отношения родитель-потомок." +whyModernWins: +- icon: "🛡️" + title: Нет утечек потоков + desc: Все разветвлённые задачи завершаются до закрытия Scope. +- icon: "⚡" + title: Быстрый сбой + desc: ShutdownOnFailure отменяет дочерние задачи при сбое одной из них. +- icon: "📐" + title: Чёткая структура + desc: Жизненный цикл задач соответствует лексическому Scope в коде. +support: + description: "Preview в JDK 25 (пятое preview, JEP 505). Требует --enable-preview." diff --git a/translations/content/ru/concurrency/thread-sleep-duration.yaml b/translations/content/ru/concurrency/thread-sleep-duration.yaml new file mode 100644 index 0000000..25dd478 --- /dev/null +++ b/translations/content/ru/concurrency/thread-sleep-duration.yaml @@ -0,0 +1,19 @@ +title: Thread.sleep с Duration +oldApproach: Миллисекунды +modernApproach: Duration +summary: Использование Duration для самодокументирующихся временных значений. +explanation: "Thread.sleep(Duration) делает единицу времени явной. Больше не нужно\ + \ гадать, означает ли 5000 миллисекунды или микросекунды. Работает с\ + \ Duration.ofSeconds, ofMillis, ofMinutes и т.д." +whyModernWins: +- icon: "📖" + title: Самодокументирующийся + desc: Duration.ofSeconds(5) не вызывает двусмысленности. +- icon: "🛡️" + title: Безопасность единиц + desc: Нет случайной передачи микросекунд вместо миллисекунд. +- icon: "🧩" + title: Компонуемый + desc: "Арифметика Duration: plus(), multipliedBy() и т.д." +support: + description: Широко доступно с JDK 19 (сент. 2022) diff --git a/translations/content/ru/concurrency/virtual-threads.yaml b/translations/content/ru/concurrency/virtual-threads.yaml new file mode 100644 index 0000000..04e0941 --- /dev/null +++ b/translations/content/ru/concurrency/virtual-threads.yaml @@ -0,0 +1,20 @@ +title: Виртуальные потоки +oldApproach: Платформенные потоки +modernApproach: Виртуальные потоки +summary: Создание миллионов лёгких виртуальных потоков вместо тяжёлых потоков ОС. +explanation: "Виртуальные потоки — это лёгкие потоки, управляемые JVM, а не операционной\ + \ системой. Можно создавать миллионы таких потоков без настройки пулов потоков.\ + \ Они идеально подходят для задач, интенсивно использующих ввод-вывод, таких\ + \ как HTTP-вызовы и запросы к базе данных." +whyModernWins: +- icon: "⚡" + title: Лёгкие + desc: "Виртуальные потоки используют КБ памяти, платформенные потоки — МБ." +- icon: "♾️" + title: Масштабируемые + desc: "Создавайте миллионы потоков — настройка пула не требуется." +- icon: "🧹" + title: Простая модель + desc: Пишите блокирующий код, который масштабируется как асинхронный. +support: + description: Широко доступно с JDK 21 LTS (сент. 2023) diff --git a/translations/content/ru/datetime/date-formatting.yaml b/translations/content/ru/datetime/date-formatting.yaml new file mode 100644 index 0000000..c27c882 --- /dev/null +++ b/translations/content/ru/datetime/date-formatting.yaml @@ -0,0 +1,17 @@ +title: Форматирование дат +oldApproach: SimpleDateFormat +modernApproach: DateTimeFormatter +summary: Форматируйте значения дат с помощью потокобезопасного неизменяемого DateTimeFormatter. +explanation: DateTimeFormatter неизменяем и потокобезопасен, в отличие от SimpleDateFormat. Его можно сохранять как константу и использовать совместно. Для стандартных форматов доступны предопределённые форматтеры, такие как ISO_LOCAL_DATE. +whyModernWins: +- icon: 🛡️ + title: Потокобезопасность + desc: Форматтеры можно использовать между потоками без синхронизации. +- icon: 📋 + title: Встроенные форматы + desc: "ISO_LOCAL_DATE, ISO_INSTANT и другие для стандартных форматов." +- icon: 🔒 + title: Неизменяемость + desc: Можно безопасно хранить как static final константу. +support: + description: Широко доступен начиная с JDK 8 (март 2014) diff --git a/translations/content/ru/datetime/duration-and-period.yaml b/translations/content/ru/datetime/duration-and-period.yaml new file mode 100644 index 0000000..e0325db --- /dev/null +++ b/translations/content/ru/datetime/duration-and-period.yaml @@ -0,0 +1,17 @@ +title: Duration и Period +oldApproach: Арифметика в миллисекундах +modernApproach: Duration / Period +summary: Вычисляйте разницу во времени типобезопасно с помощью Duration и Period. +explanation: Duration представляет временные величины (часы, минуты, секунды). Period представляет календарные величины (годы, месяцы, дни). ChronoUnit.between() подходит для простых разниц. Все классы корректно обрабатывают граничные случаи. +whyModernWins: +- icon: 🎯 + title: Типобезопасность + desc: "Duration для времени, Period для даты — никакой путаницы." +- icon: 🛡️ + title: Корректные вычисления + desc: "Обрабатывает переход на летнее время, високосные годы и секунды координации." +- icon: 📖 + title: Читаемость + desc: ChronoUnit.DAYS.between() читается как естественный язык. +support: + description: Широко доступен начиная с JDK 8 (март 2014) diff --git a/translations/content/ru/datetime/hex-format.yaml b/translations/content/ru/datetime/hex-format.yaml new file mode 100644 index 0000000..c259422 --- /dev/null +++ b/translations/content/ru/datetime/hex-format.yaml @@ -0,0 +1,17 @@ +title: HexFormat +oldApproach: Ручное шестнадцатеричное преобразование +modernApproach: HexFormat +summary: Преобразуйте шестнадцатеричные строки в байтовые массивы и обратно с помощью HexFormat. +explanation: "HexFormat обеспечивает двунаправленное кодирование и декодирование hex для байтов, целых чисел и массивов. Разделители, префиксы, суффиксы и регистр символов настраиваемы. Ручное форматирование и разбор больше не нужны." +whyModernWins: +- icon: 📐 + title: Двунаправленность + desc: "Преобразуйте байты→hex и hex→байты с помощью единого API." +- icon: 🔧 + title: Настраиваемость + desc: "Разделители, префикс, суффикс, регистр символов." +- icon: 📦 + title: Поддержка массивов + desc: Кодирование и декодирование целых байтовых массивов за один раз. +support: + description: Широко доступен начиная с JDK 17 LTS (сентябрь 2021) diff --git a/translations/content/ru/datetime/instant-precision.yaml b/translations/content/ru/datetime/instant-precision.yaml new file mode 100644 index 0000000..eccf104 --- /dev/null +++ b/translations/content/ru/datetime/instant-precision.yaml @@ -0,0 +1,17 @@ +title: Instant с точностью до наносекунд +oldApproach: Миллисекунды +modernApproach: Наносекунды +summary: Получайте временные метки с точностью до микросекунд или наносекунд. +explanation: "Java 9 улучшила разрешение часов, и теперь Instant.now() на большинстве платформ обеспечивает точность до микросекунд (на некоторых — до наносекунд). Устаревший currentTimeMillis() предоставляет точность только до миллисекунд." +whyModernWins: +- icon: 🎯 + title: Высокая точность + desc: "Временные метки с точностью до микросекунд/наносекунд вместо миллисекунд." +- icon: 📐 + title: Типобезопасность + desc: "Instant несёт в себе свою точность — никаких неоднозначных значений long." +- icon: 🌐 + title: Основан на UTC + desc: "Instant всегда в UTC — никакой путаницы с часовыми поясами." +support: + description: Широко доступен начиная с JDK 9 (сентябрь 2017) diff --git a/translations/content/ru/datetime/java-time-basics.yaml b/translations/content/ru/datetime/java-time-basics.yaml new file mode 100644 index 0000000..7aea057 --- /dev/null +++ b/translations/content/ru/datetime/java-time-basics.yaml @@ -0,0 +1,17 @@ +title: Основы API java.time +oldApproach: Date + Calendar +modernApproach: java.time.* +summary: Используйте неизменяемые и понятные типы даты/времени вместо Date и Calendar. +explanation: "java.time предоставляет LocalDate, LocalTime, LocalDateTime, Instant и ZonedDateTime — все неизменяемые и потокобезопасные. Месяцы индексируются с 1. Больше никакой путаницы с Calendar.JANUARY = 0." +whyModernWins: +- icon: 🔒 + title: Неизменяемость + desc: Значения даты/времени не могут быть случайно изменены. +- icon: 📖 + title: Понятный API + desc: "Month.JANUARY вместо 0. DayOfWeek.MONDAY вместо 2." +- icon: 🛡️ + title: Потокобезопасность + desc: "Синхронизация не требуется — можно свободно использовать между потоками." +support: + description: Широко доступен начиная с JDK 8 (март 2014) diff --git a/translations/content/ru/datetime/math-clamp.yaml b/translations/content/ru/datetime/math-clamp.yaml new file mode 100644 index 0000000..230e74e --- /dev/null +++ b/translations/content/ru/datetime/math-clamp.yaml @@ -0,0 +1,17 @@ +title: Math.clamp() +oldApproach: Вложенные min/max +modernApproach: Math.clamp() +summary: Ограничьте значение диапазоном с помощью единственного понятного вызова. +explanation: "Math.clamp(value, min, max) ограничивает значение диапазоном [min, max]. Понятнее, чем вложенные Math.min/Math.max, и доступен для int, long, float и double." +whyModernWins: +- icon: 📖 + title: Самодокументируемый + desc: "clamp(value, min, max) однозначен." +- icon: 🛡️ + title: Меньше ошибок + desc: Больше никакой случайной перестановки порядка min и max. +- icon: 🎯 + title: Все числовые типы + desc: "Работает с int, long, float и double." +support: + description: Широко доступен начиная с JDK 21 LTS (сентябрь 2023) diff --git a/translations/content/ru/enterprise/ejb-timer-vs-jakarta-scheduler.yaml b/translations/content/ru/enterprise/ejb-timer-vs-jakarta-scheduler.yaml new file mode 100644 index 0000000..080cd79 --- /dev/null +++ b/translations/content/ru/enterprise/ejb-timer-vs-jakarta-scheduler.yaml @@ -0,0 +1,17 @@ +title: EJB Timer vs. Jakarta Scheduler +oldApproach: EJB TimerService +modernApproach: ManagedScheduledExecutorService +summary: Замените громоздкие EJB-таймеры на ManagedScheduledExecutorService из Jakarta Concurrency для более простого планирования задач. +explanation: EJB-таймеры требуют @Stateless- или @Singleton-бина с @Timeout-колбэком и XML- или аннотационными выражениями расписания. Jakarta Concurrency предоставляет ManagedScheduledExecutorService, использующий привычный API планирования java.util.concurrent. Результат — меньше шаблонного кода, упрощённое модульное тестирование и отсутствие зависимости от EJB-контейнера. +whyModernWins: +- icon: 🪶 + title: Меньше шаблонного кода + desc: "Никаких @Timeout-колбэков или ScheduleExpression — используйте стандартный API ScheduledExecutorService." +- icon: 🧪 + title: Лучшая тестируемость + desc: Простые методы и заглушки для Executor делают модульное тестирование без EJB-контейнера простым. +- icon: ☁️ + title: Совместимость с облачными подходами + desc: Управляемые Executor-ы интегрируются в жизненный цикл контейнера и работают в облегчённых средах выполнения. +support: + description: "Доступно начиная с Jakarta EE 10 / Concurrency 3.0" diff --git a/translations/content/ru/enterprise/ejb-vs-cdi.yaml b/translations/content/ru/enterprise/ejb-vs-cdi.yaml new file mode 100644 index 0000000..db06b69 --- /dev/null +++ b/translations/content/ru/enterprise/ejb-vs-cdi.yaml @@ -0,0 +1,17 @@ +title: EJB против CDI +oldApproach: EJB +modernApproach: CDI-бин +summary: Замените громоздкие EJB облегчёнными CDI-бинами для внедрения зависимостей и управления транзакциями. +explanation: CDI (Contexts and Dependency Injection) предоставляет те же возможности внедрения зависимостей и управления транзакциями, что и EJB, но в виде простых Java-классов без специфичных для контейнера интерфейсов или суперклассов. Области видимости, такие как @ApplicationScoped и @RequestScoped, управляют жизненным циклом, а @Transactional заменяет обязательную транзакционную семантику EJB. +whyModernWins: +- icon: 🪶 + title: Облегчённость + desc: CDI-бины — это простые Java-классы без EJB-специфичных интерфейсов или дескрипторов. +- icon: 💉 + title: Единое внедрение + desc: "@Inject работает единообразно для любого управляемого бина, JAX-RS-ресурса и компонента Jakarta EE." +- icon: 🧪 + title: Простое модульное тестирование + desc: Простые классы без EJB-прокси-overhead легко инстанциировать и заменять заглушками. +support: + description: Широко доступно начиная с Jakarta EE 8 / Java 11 diff --git a/translations/content/ru/enterprise/jdbc-resultset-vs-jpa-criteria.yaml b/translations/content/ru/enterprise/jdbc-resultset-vs-jpa-criteria.yaml new file mode 100644 index 0000000..af12f56 --- /dev/null +++ b/translations/content/ru/enterprise/jdbc-resultset-vs-jpa-criteria.yaml @@ -0,0 +1,17 @@ +title: Маппинг JDBC ResultSet vs. JPA Criteria API +oldApproach: JDBC ResultSet +modernApproach: JPA Criteria API +summary: Замените ручное маппирование JDBC ResultSet на типобезопасный Criteria API JPA для динамических запросов. +explanation: Сырой JDBC требует построения SQL-строк, установки параметров по индексу и ручного маппинга каждого столбца ResultSet — процесс, подверженный ошибкам и незаметно ломающийся при изменении схемы. JPA Criteria API формирует запросы программно с помощью типобезопасного паттерна Builder. Имена столбцов проверяются по модели сущностей, маппинг результатов выполняется автоматически, а сложные динамические запросы легко компонуются без конкатенации строк. +whyModernWins: +- icon: 🔒 + title: Типобезопасные запросы + desc: Criteria Builder выявляет неверные имена полей и ошибки типов на этапе компиляции. +- icon: 🗺️ + title: Автоматический маппинг + desc: JPA отображает строки результата на объекты-сущности — никакого ручного извлечения столбец за столбцом. +- icon: 🧩 + title: Компонуемые предикаты + desc: Динамические WHERE-клаузулы легко строить с помощью and(), or() и повторно используемых объектов Predicate. +support: + description: Широко доступно начиная с Jakarta EE 8 / Java 11 diff --git a/translations/content/ru/enterprise/jdbc-vs-jooq.yaml b/translations/content/ru/enterprise/jdbc-vs-jooq.yaml new file mode 100644 index 0000000..70f39ea --- /dev/null +++ b/translations/content/ru/enterprise/jdbc-vs-jooq.yaml @@ -0,0 +1,17 @@ +title: JDBC против jOOQ +oldApproach: Сырой JDBC +modernApproach: jOOQ SQL DSL +summary: Замените строковой SQL с сырым JDBC на типобезопасный плавный SQL DSL библиотеки jOOQ. +explanation: "jOOQ (Java Object Oriented Querying) генерирует Java-код из схемы базы данных, превращая имена таблиц и столбцов в типобезопасные Java-константы. Плавный DSL отражает синтаксис SQL, делая запросы читаемыми и компонуемыми. Все параметры привязываются автоматически, что устраняет риски SQL-инъекций. В отличие от JPA/JPQL, jOOQ полностью поддерживает SQL — оконные функции, CTE, RETURNING-клаузулы и расширения, специфичные для поставщика, являются первоклассными возможностями." +whyModernWins: +- icon: 🔒 + title: Типобезопасные столбцы + desc: Имена столбцов — это сгенерированные Java-константы; опечатки и ошибки типов становятся ошибками компиляции, а не ошибками времени выполнения. +- icon: 📖 + title: Плавность SQL + desc: DSL jOOQ точно отражает синтаксис SQL, поэтому сложные JOIN-ы, подзапросы и CTE остаются читаемыми. +- icon: 🛡️ + title: Защита от инъекций по дизайну + desc: Параметры всегда привязываются безопасно — никакой конкатенации строк означает никакого риска SQL-инъекции. +support: + description: Свободная версия jOOQ поддерживает все распространённые СУБД с открытым исходным кодом; более старые коммерческие СУБД требуют платной лицензии diff --git a/translations/content/ru/enterprise/jdbc-vs-jpa.yaml b/translations/content/ru/enterprise/jdbc-vs-jpa.yaml new file mode 100644 index 0000000..3bc31c7 --- /dev/null +++ b/translations/content/ru/enterprise/jdbc-vs-jpa.yaml @@ -0,0 +1,17 @@ +title: JDBC против JPA +oldApproach: JDBC +modernApproach: JPA EntityManager +summary: Замените многословный шаблонный код JDBC на объектно-реляционное отображение JPA и EntityManager. +explanation: "JPA (Jakarta Persistence API) отображает Java-объекты на строки базы данных, устраняя необходимость в ручной обработке ResultSet и конкатенации SQL-строк. EntityManager предоставляет методы find(), persist() и JPQL-запросы, позволяя работать с доменными объектами вместо сырого SQL, пока контейнер управляет пулом соединений и транзакциями." +whyModernWins: +- icon: 🗺️ + title: Объектное отображение + desc: Сущности — это простые аннотированные классы, не требующие ручного преобразования ResultSet в объект. +- icon: 🔒 + title: Типобезопасные запросы + desc: JPQL работает с типами и полями сущностей, а не с сырыми строками таблиц и столбцов. +- icon: ⚡ + title: Встроенное кэширование + desc: Кэши первого и второго уровня автоматически снижают количество обращений к базе данных. +support: + description: Широко доступно начиная с Jakarta EE 8 / Java 11 diff --git a/translations/content/ru/enterprise/jndi-lookup-vs-cdi-injection.yaml b/translations/content/ru/enterprise/jndi-lookup-vs-cdi-injection.yaml new file mode 100644 index 0000000..60e57e2 --- /dev/null +++ b/translations/content/ru/enterprise/jndi-lookup-vs-cdi-injection.yaml @@ -0,0 +1,17 @@ +title: JNDI Lookup vs. CDI-инъекция +oldApproach: JNDI Lookup +modernApproach: CDI @Inject +summary: Замените хрупкие JNDI-поиски по строкам на типобезопасную CDI-инъекцию для управляемых контейнером ресурсов. +explanation: Традиционный паттерн JNDI вынуждает использовать строковые имена ресурсов, обрабатывать NamingException и управлять InitialContext. CDI-инъекция с @Inject (или @Resource для ресурсов контейнера) позволяет контейнеру автоматически связывать зависимости. Опечатки становятся ошибками компиляции, а классы легче тестировать, поскольку зависимости можно вводить напрямую. +whyModernWins: +- icon: 🔒 + title: Типобезопасная связка + desc: Ошибки инъекции обнаруживаются во время развёртывания, а не во время выполнения через строковые поиски. +- icon: 🗑️ + title: Никакого шаблонного кода + desc: Устраняет создание InitialContext, JNDI-строки с именами и обработку NamingException. +- icon: 🧪 + title: Тестируемость + desc: Зависимости — это инъецируемые поля, которые легко заменить заглушками в модульных тестах. +support: + description: Широко доступно начиная с Jakarta EE 8 / Java 11 diff --git a/translations/content/ru/enterprise/jpa-vs-jakarta-data.yaml b/translations/content/ru/enterprise/jpa-vs-jakarta-data.yaml new file mode 100644 index 0000000..4b945bc --- /dev/null +++ b/translations/content/ru/enterprise/jpa-vs-jakarta-data.yaml @@ -0,0 +1,17 @@ +title: JPA против Jakarta Data +oldApproach: JPA EntityManager +modernApproach: Jakarta Data Repository +summary: Объявите интерфейс репозитория и позвольте Jakarta Data автоматически сгенерировать реализацию DAO. +explanation: Jakarta Data (Jakarta EE 11) превращает доступ к данным в чистое объявление интерфейса. Вы аннотируете интерфейс с @Repository и расширяете встроенный тип репозитория, например CrudRepository. Среда выполнения генерирует реализацию — включая производные запросы по именам методов, таким как findByName — так что не нужно ни шаблонного кода EntityManager, ни JPQL-строк, ни написанных вручную методов save/find. +whyModernWins: +- icon: 🪄 + title: Ноль шаблонного кода + desc: Объявите интерфейс; контейнер генерирует полную реализацию DAO во время развёртывания. +- icon: 🔍 + title: Производные запросы + desc: Имена методов вроде findByNameAndStatus разбираются автоматически — никакого JPQL или SQL не требуется. +- icon: 🔌 + title: Переносимость + desc: Любая Jakarta EE 11-совместимая среда выполнения предоставит реализацию репозитория без привязки к поставщику. +support: + description: "Доступно начиная с Jakarta EE 11 / Java 21 (2024)" diff --git a/translations/content/ru/enterprise/jsf-managed-bean-vs-cdi-named.yaml b/translations/content/ru/enterprise/jsf-managed-bean-vs-cdi-named.yaml new file mode 100644 index 0000000..b2a9e7c --- /dev/null +++ b/translations/content/ru/enterprise/jsf-managed-bean-vs-cdi-named.yaml @@ -0,0 +1,17 @@ +title: JSF Managed Bean vs. CDI Named Bean +oldApproach: "@ManagedBean" +modernApproach: "@Named + CDI" +summary: Замените устаревший JSF-@ManagedBean на CDI-@Named для единой модели внедрения зависимостей. +explanation: Аннотации @ManagedBean и @ManagedProperty из JSF были объявлены устаревшими в Jakarta Faces 2.3 и удалены в Jakarta EE 10. CDI-замена использует @Named для доступа к бину через EL-выражения и @Inject для связывания зависимостей. Это унифицирует модель бинов — JSF-страницы, JAX-RS-ресурсы и EJB используют один и тот же CDI-контейнер. +whyModernWins: +- icon: 🔗 + title: Единая модель + desc: Один CDI-контейнер управляет всеми бинами — JSF, REST и сервисные слои используют одно и то же внедрение. +- icon: 🗑️ + title: Меньше шаблонного кода + desc: "@Inject заменяет @ManagedProperty и обязательный сеттер-метод." +- icon: 🔮 + title: Ориентация на будущее + desc: "@ManagedBean был удалён в Jakarta EE 10; @Named — поддерживаемая замена." +support: + description: "CDI @Named доступен начиная с Java EE 6; @ManagedBean удалён в Jakarta EE 10" diff --git a/translations/content/ru/enterprise/manual-transaction-vs-declarative.yaml b/translations/content/ru/enterprise/manual-transaction-vs-declarative.yaml new file mode 100644 index 0000000..bb3e259 --- /dev/null +++ b/translations/content/ru/enterprise/manual-transaction-vs-declarative.yaml @@ -0,0 +1,17 @@ +title: Ручное управление транзакциями JPA vs. декларативный @Transactional +oldApproach: Ручная транзакция +modernApproach: "@Transactional" +summary: Замените многословные блоки begin/commit/rollback одной аннотацией @Transactional. +explanation: Ручное управление транзакциями требует явных вызовов begin(), commit() и rollback(), обёрнутых в блоки try-catch — каждый сервисный метод повторяет этот шаблонный код. Аннотация @Transactional делегирует управление жизненным циклом контейнеру — он запускает транзакцию перед вызовом метода, фиксирует её при успехе и автоматически откатывает при RuntimeException. +whyModernWins: +- icon: 🗑️ + title: Никакого шаблонного кода + desc: Одна аннотация заменяет повторяющиеся блоки begin/commit/rollback с try-catch. +- icon: 🛡️ + title: Более безопасный откат + desc: Контейнер гарантирует откат при непроверяемых исключениях — никакого риска забыть блок catch. +- icon: 📐 + title: Декларативное управление + desc: Распространение, изоляция и правила отката выражаются как атрибуты аннотации. +support: + description: Широко доступно начиная с Jakarta EE 8 / Java 11 diff --git a/translations/content/ru/enterprise/mdb-vs-reactive-messaging.yaml b/translations/content/ru/enterprise/mdb-vs-reactive-messaging.yaml new file mode 100644 index 0000000..c4b111b --- /dev/null +++ b/translations/content/ru/enterprise/mdb-vs-reactive-messaging.yaml @@ -0,0 +1,17 @@ +title: Message-Driven Bean vs. Reactive Messaging +oldApproach: Message-Driven Bean +modernApproach: Reactive Messaging +summary: Замените JMS Message-Driven Beans на MicroProfile Reactive Messaging для более простой обработки событий. +explanation: Message-Driven Beans требуют реализации MessageListener, настройки свойств активации и ручной десериализации JMS-сообщений. MicroProfile Reactive Messaging использует простую аннотацию @Incoming на методе, который получает типизированные объекты напрямую. Конфигурация каналов выносится наружу, делая код независимым от брокера и значительно упрощая тестирование. +whyModernWins: +- icon: 🪶 + title: Минимальный код + desc: "Один метод с @Incoming заменяет класс MDB, интерфейс MessageListener и конфигурацию активации." +- icon: 🔌 + title: Независимость от брокера + desc: Меняйте коннекторы Kafka, AMQP или JMS через конфигурацию, не изменяя код приложения. +- icon: ☁️ + title: Подходит для облачных подходов + desc: Противодавление реактивных потоков и облегчённая среда выполнения делают его идеальным для контейнеризированных развёртываний. +support: + description: "Доступно начиная с MicroProfile 4.0 / SmallRye Reactive Messaging" diff --git a/translations/content/ru/enterprise/servlet-vs-jaxrs.yaml b/translations/content/ru/enterprise/servlet-vs-jaxrs.yaml new file mode 100644 index 0000000..5f1b98e --- /dev/null +++ b/translations/content/ru/enterprise/servlet-vs-jaxrs.yaml @@ -0,0 +1,17 @@ +title: Servlet против JAX-RS +oldApproach: HttpServlet +modernApproach: JAX-RS-ресурс +summary: Замените многословный шаблонный код HttpServlet декларативными классами ресурсов JAX-RS. +explanation: "JAX-RS (Jakarta RESTful Web Services) позволяет предоставлять REST-эндпоинты с помощью простых аннотаций, таких как @GET, @Path и @Produces. Никакого ручного разбора параметров запроса или установки Content-Type в ответе — среда выполнения берёт на себя маршализацию и маршрутизацию автоматически." +whyModernWins: +- icon: 📐 + title: Декларативная маршрутизация + desc: Аннотации определяют HTTP-метод, путь и Content-Type вместо императивного if/else-диспатчинга. +- icon: 🔄 + title: Автоматическая маршализация + desc: "Возвращайте POJO напрямую; среда выполнения сериализует их в JSON или XML на основе @Produces." +- icon: 🧪 + title: Упрощённое тестирование + desc: Классы ресурсов — это простые Java-объекты, тестируемые без Servlet-контейнера. +support: + description: Широко доступно начиная с Jakarta EE 8 / Java 11 diff --git a/translations/content/ru/enterprise/singleton-ejb-vs-cdi-application-scoped.yaml b/translations/content/ru/enterprise/singleton-ejb-vs-cdi-application-scoped.yaml new file mode 100644 index 0000000..ad2d323 --- /dev/null +++ b/translations/content/ru/enterprise/singleton-ejb-vs-cdi-application-scoped.yaml @@ -0,0 +1,17 @@ +title: Singleton EJB vs. CDI @ApplicationScoped +oldApproach: "@Singleton EJB" +modernApproach: "@ApplicationScoped CDI" +summary: Замените Singleton-EJB на CDI-бины с @ApplicationScoped для более простого управления общим состоянием. +explanation: "Singleton-EJB объединяет управление параллелизмом (@Lock, @ConcurrencyManagement) и раннюю инициализацию (@Startup) в EJB-контейнере. CDI-бин с @ApplicationScoped достигает того же жизненного цикла с одним экземпляром при значительно меньших усилиях. Когда требуется управление параллелизмом, стандартные утилиты java.util.concurrent обеспечивают более тонкий контроль, чем Lock-аннотации EJB." +whyModernWins: +- icon: 🪶 + title: Меньше аннотационного шума + desc: "Никаких @ConcurrencyManagement, @Lock или @Startup — только одна аннотация @ApplicationScoped." +- icon: 🔧 + title: Гибкий параллелизм + desc: Используйте блокировки java.util.concurrent или volatile для точно необходимой потокобезопасности. +- icon: 🧪 + title: Простое тестирование + desc: Простые CDI-бины можно напрямую инстанциировать в тестах без EJB-контейнера. +support: + description: Широко доступно начиная с Jakarta EE 8 / Java 11 diff --git a/translations/content/ru/enterprise/soap-vs-jakarta-rest.yaml b/translations/content/ru/enterprise/soap-vs-jakarta-rest.yaml new file mode 100644 index 0000000..d68010b --- /dev/null +++ b/translations/content/ru/enterprise/soap-vs-jakarta-rest.yaml @@ -0,0 +1,17 @@ +title: SOAP веб-сервисы vs. Jakarta REST +oldApproach: JAX-WS / SOAP +modernApproach: Jakarta REST / JSON +summary: Замените громоздкие SOAP/WSDL-эндпоинты чистыми Jakarta REST-ресурсами, возвращающими JSON. +explanation: Веб-сервисы на основе SOAP опираются на WSDL-контракты, XML-маршализацию и JAX-WS-аннотации, создавая значительные накладные расходы. Jakarta REST (ранее JAX-RS) использует интуитивные аннотации, такие как @GET, @Path и @Produces, для предоставления RESTful JSON API. Модель программирования проще, полезные нагрузки меньше, а подход соответствует стилю взаимодействия современных микросервисов. +whyModernWins: +- icon: 🪶 + title: Более лёгкие нагрузки + desc: JSON компактнее, чем SOAP XML-конверты, что снижает потребление полосы пропускания и накладные расходы на разбор. +- icon: 📐 + title: Простые аннотации + desc: "@GET, @Path и @Produces заменяют сложность WSDL, @WebService и @WebMethod." +- icon: 🔌 + title: Готовность к микросервисам + desc: REST/JSON — это стандарт межсервисного взаимодействия в облачно-ориентированных архитектурах. +support: + description: Широко доступно начиная с Jakarta EE 8 / Java 11 diff --git a/translations/content/ru/enterprise/spring-api-versioning.yaml b/translations/content/ru/enterprise/spring-api-versioning.yaml new file mode 100644 index 0000000..0bd6d9d --- /dev/null +++ b/translations/content/ru/enterprise/spring-api-versioning.yaml @@ -0,0 +1,17 @@ +title: Версионирование API в Spring Framework 7 +oldApproach: Ручное версионирование URL-пути +modernApproach: Нативное версионирование API +summary: Замените продублированные контроллеры с версионными префиксами на нативную поддержку версионирования API в Spring Framework 7. +explanation: До Spring Framework 7 версионирование API требовало отдельных классов контроллеров для каждой версии (например, /api/v1/products, /api/v2/products), что дублировало маппинги запросов и рассредотачивало логику версий по множеству файлов. Spring Framework 7 вводит нативное версионирование через новый атрибут version в @RequestMapping и связанных аннотациях, а также хук configureApiVersioning в WebMvcConfigurer. Версия может извлекаться из заголовка запроса, сегмента URL-пути или параметра запроса — всё управляется централизованно. +whyModernWins: +- icon: 🗂️ + title: Без дублирования контроллеров + desc: Все версии живут в одном классе контроллера; только отдельные методы-обработчики несут атрибут version. +- icon: ⚙️ + title: Централизованная стратегия версионирования + desc: Переключайтесь с заголовочного на URL- или параметровое версионирование одним вызовом configureApiVersioning. +- icon: 📈 + title: Инкрементальная эволюция + desc: Добавляйте новую версию к методу, не затрагивая несвязанные эндпоинты и не создавая новых файлов контроллеров. +support: + description: "Доступно начиная с Spring Framework 7.0 (требует Java 17+)" diff --git a/translations/content/ru/enterprise/spring-null-safety-jspecify.yaml b/translations/content/ru/enterprise/spring-null-safety-jspecify.yaml new file mode 100644 index 0000000..81be273 --- /dev/null +++ b/translations/content/ru/enterprise/spring-null-safety-jspecify.yaml @@ -0,0 +1,17 @@ +title: Null-безопасность Spring с JSpecify +oldApproach: Spring @NonNull/@Nullable +modernApproach: JSpecify @NullMarked +summary: Spring 7 переходит на аннотации JSpecify, делая non-null значением по умолчанию и снижая аннотационный шум. +explanation: "Spring 5 и 6 вводили собственные аннотации null-безопасности в пакете `org.springframework.lang`. Несмотря на полезность, они были специфичны для фреймворка и требовали явной аннотации каждого non-null элемента. Spring 7 переходит на JSpecify — кросс-экосистемный стандарт null-безопасности. Аннотация `@NullMarked` на уровне класса или пакета объявляет, что все неаннотированные типы являются non-null по умолчанию. Только действительно nullable типы требуют аннотации `@Nullable`, что резко снижает многословность. Аннотации JSpecify распознаются ведущими инструментами статического анализа — NullAway, Error Prone и IntelliJ IDEA — обеспечивая более широкую поддержку инструментами, чем Spring-специфичные аннотации." +whyModernWins: +- icon: ✂️ + title: Non-null как умолчание + desc: "@NullMarked делает все неаннотированные типы non-null, так что аннотировать нужно только nullable-исключения." +- icon: 🌐 + title: Экосистемный стандарт + desc: Аннотации JSpecify — это кросс-фреймворковый стандарт, распознаваемый NullAway, Error Prone и IDE. +- icon: 🔍 + title: Расширенная поддержка инструментами + desc: Современные статические анализаторы понимают null-модель JSpecify и сообщают о нарушениях на этапе компиляции. +support: + description: "Доступно начиная с Spring Framework 7.0 (требует Java 17+)" diff --git a/translations/content/ru/enterprise/spring-xml-config-vs-annotations.yaml b/translations/content/ru/enterprise/spring-xml-config-vs-annotations.yaml new file mode 100644 index 0000000..160c002 --- /dev/null +++ b/translations/content/ru/enterprise/spring-xml-config-vs-annotations.yaml @@ -0,0 +1,17 @@ +title: Spring XML-конфигурация бинов vs. на основе аннотаций +oldApproach: XML-определения бинов +modernApproach: Бины на основе аннотаций +summary: Замените многословные Spring XML-определения бинов лаконичной конфигурацией на основе аннотаций с Spring Boot. +explanation: "Традиционные Spring-приложения связывали бины через XML-файлы конфигурации, объявляя каждый класс с его зависимостями в виде многословных элементов . Хотя поддержка аннотаций существовала с Spring 2.5, XML оставался преобладающим подходом до тех пор, пока Spring Boot не ввёл автоконфигурацию. Spring Boot обнаруживает бины, аннотированные @Component, @Service, @Repository и @Controller, через сканирование classpath, автоматически удовлетворяет зависимости через инъекцию конструктора и настраивает инфраструктуру, такую как DataSource, из classpath — устраняя все файлы XML-связывания." +whyModernWins: +- icon: 🚫 + title: Никакого XML + desc: "@SpringBootApplication активирует сканирование компонентов и автоконфигурацию, устраняя все XML-файлы связывания." +- icon: 💉 + title: Инъекция конструктора + desc: Spring автоматически инъецирует зависимости через конструкторы, делая бины проще для тестирования и понимания. +- icon: ⚡ + title: Автоконфигурация + desc: Spring Boot настраивает DataSource, JPA и другую инфраструктуру из classpath без шаблонного кода. +support: + description: "Широко доступно начиная с Spring Boot 1.0 (апрель 2014); Spring Boot 3 требует Java 17+" diff --git a/translations/content/ru/errors/helpful-npe.yaml b/translations/content/ru/errors/helpful-npe.yaml new file mode 100644 index 0000000..fd210e0 --- /dev/null +++ b/translations/content/ru/errors/helpful-npe.yaml @@ -0,0 +1,17 @@ +title: "Информативные NullPointerExceptions" +oldApproach: "Криптичная NPE" +modernApproach: "Подробная NPE" +summary: "JVM автоматически сообщает, какая переменная была null." +explanation: "Информативные NPE описывают, какое выражение было null и какая операция завершилась ошибкой. По умолчанию включено начиная с Java 14 — изменений в коде не требуется, достаточно обновить JDK." +whyModernWins: +- icon: "🔍" + title: "Точная переменная" + desc: "Сообщение указывает переменную null в цепочке вызовов." +- icon: "⚡" + title: "Более быстрая отладка" + desc: "Больше не нужно гадать, какой из пяти цепочечных вызовов вернул null." +- icon: "🆓" + title: "Бесплатное улучшение" + desc: "Никаких изменений в коде — достаточно запустить на JDK 14+." +support: + description: "Доступно в JDK 14 (март 2020)" diff --git a/translations/content/ru/errors/multi-catch.yaml b/translations/content/ru/errors/multi-catch.yaml new file mode 100644 index 0000000..e2521b1 --- /dev/null +++ b/translations/content/ru/errors/multi-catch.yaml @@ -0,0 +1,17 @@ +title: "Обработка исключений с Multi-Catch" +oldApproach: "Отдельные блоки catch" +modernApproach: "Multi-Catch" +summary: "Перехват нескольких типов исключений в одном блоке catch." +explanation: "Multi-Catch обрабатывает несколько типов исключений одним и тем же кодом. Переменная исключения является эффективно final, поэтому её можно повторно выбросить без оборачивания." +whyModernWins: +- icon: "📏" + title: "DRY" + desc: "Одна и та же логика обработки пишется один раз, а не трижды." +- icon: "🔄" + title: "Повторный выброс" + desc: "Пойманное исключение можно повторно выбросить с его точным типом." +- icon: "📖" + title: "Наглядность" + desc: "Все обрабатываемые типы видны в одном месте." +support: + description: "Доступно в JDK 7 (июль 2011)" diff --git a/translations/content/ru/errors/null-in-switch.yaml b/translations/content/ru/errors/null-in-switch.yaml new file mode 100644 index 0000000..d015900 --- /dev/null +++ b/translations/content/ru/errors/null-in-switch.yaml @@ -0,0 +1,17 @@ +title: "Случай null в switch" +oldApproach: "Проверка перед switch" +modernApproach: "case null" +summary: "Обрабатывать null непосредственно как case в switch — без отдельной проверки." +explanation: "Switch с сопоставлением шаблонов может сопоставлять null как метку case. Это устраняет необходимость проверки null перед switch, делая обработку null явной и наглядной." +whyModernWins: +- icon: "🎯" + title: "Явность" + desc: "Обработка null видна непосредственно в switch." +- icon: "🛡️" + title: "Нет NPE" + desc: "Switch на значение null не выбрасывает NullPointerException." +- icon: "📐" + title: "Всё в одном" + desc: "Все случаи, включая null, в одном выражении switch." +support: + description: "Доступно в JDK 21 LTS (сент. 2023)" diff --git a/translations/content/ru/errors/optional-chaining.yaml b/translations/content/ru/errors/optional-chaining.yaml new file mode 100644 index 0000000..6b9983d --- /dev/null +++ b/translations/content/ru/errors/optional-chaining.yaml @@ -0,0 +1,17 @@ +title: "Цепочки Optional" +oldApproach: "Вложенные проверки null" +modernApproach: "Конвейер Optional" +summary: "Замена вложенных проверок null на конвейер Optional." +explanation: "Optional.map() связывает допускающие null значения и прерывает цепочку при первом null. orElse() возвращает значение по умолчанию. Это устраняет пирамиду вложенных проверок null." +whyModernWins: +- icon: "🔗" + title: "Цепочки" + desc: "Каждый шаг .map() прозрачно обрабатывает null." +- icon: "📖" + title: "Линейный поток" + desc: "Чтение слева направо вместо вложенных блоков if." +- icon: "🛡️" + title: "Защита от NPE" + desc: "null обрабатывается на каждом шаге — сбои невозможны." +support: + description: "Доступно с JDK 8+ (улучшено в 9+)" diff --git a/translations/content/ru/errors/optional-orelsethrow.yaml b/translations/content/ru/errors/optional-orelsethrow.yaml new file mode 100644 index 0000000..d77d16d --- /dev/null +++ b/translations/content/ru/errors/optional-orelsethrow.yaml @@ -0,0 +1,17 @@ +title: "Optional.orElseThrow() без Supplier" +oldApproach: "get() или orElseThrow(supplier)" +modernApproach: "orElseThrow()" +summary: "Использовать Optional.orElseThrow() как более ясную альтернативу get(), выражающую намерение." +explanation: "Optional.get() широко считается code smell, поскольку скрывает возможность сбоя. Метод orElseThrow() без аргументов, добавленный в Java 10, делает то же самое, но явно выражает намерение: разработчик ожидает значение и хочет получить исключение, если его нет." +whyModernWins: +- icon: "📖" + title: "Самодокументирующийся" + desc: "orElseThrow() явно сигнализирует, что пустой результат не ожидается." +- icon: "🔒" + title: "Без get()" + desc: "Инструменты статического анализа помечают get() как рискованный; orElseThrow() является идиоматичным." +- icon: "⚡" + title: "Меньше шаблонного кода" + desc: "Не нужен Supplier для стандартного NoSuchElementException." +support: + description: "Доступно с JDK 10 (март 2018)." diff --git a/translations/content/ru/errors/record-based-errors.yaml b/translations/content/ru/errors/record-based-errors.yaml new file mode 100644 index 0000000..33da39d --- /dev/null +++ b/translations/content/ru/errors/record-based-errors.yaml @@ -0,0 +1,17 @@ +title: "Ответы об ошибках на основе Record" +oldApproach: "Map или громоздкий класс" +modernApproach: "Record-ошибки" +summary: "Использовать Records для лаконичных неизменяемых типов ответов об ошибках." +explanation: "Records отлично подходят для ответов об ошибках — они неизменяемы, имеют встроенные equals/hashCode для сравнения и toString для логирования. Пользовательские конструкторы позволяют выполнять валидацию или задавать значения по умолчанию." +whyModernWins: +- icon: "📏" + title: "Лаконичность" + desc: "Определение типов ошибок в 3 строках вместо 30." +- icon: "🔒" + title: "Неизменяемость" + desc: "Данные об ошибке не могут быть случайно изменены после создания." +- icon: "📋" + title: "Автоматический toString" + desc: "Идеально для логирования — все поля отображаются автоматически." +support: + description: "Доступно в JDK 16 (март 2021)" diff --git a/translations/content/ru/errors/require-nonnull-else.yaml b/translations/content/ru/errors/require-nonnull-else.yaml new file mode 100644 index 0000000..0844547 --- /dev/null +++ b/translations/content/ru/errors/require-nonnull-else.yaml @@ -0,0 +1,17 @@ +title: "Objects.requireNonNullElse()" +oldApproach: "Тернарная проверка null" +modernApproach: "requireNonNullElse()" +summary: "Получить ненулевое значение с явным значением по умолчанию — без тернарного оператора." +explanation: "requireNonNullElse возвращает первый аргумент, если он не null, иначе — второй. Значение по умолчанию само не может быть null — при двух null-значениях выбрасывается NPE, что выявляет ошибки на раннем этапе." +whyModernWins: +- icon: "📖" + title: "Явное намерение" + desc: "Название метода точно описывает, что он делает." +- icon: "🛡️" + title: "Null-безопасное значение по умолчанию" + desc: "Значение по умолчанию также проверяется на null." +- icon: "📏" + title: "Читаемость" + desc: "Лучше тернарного оператора для простой логики null-или-по-умолчанию." +support: + description: "Доступно в JDK 9 (сент. 2017)" diff --git a/translations/content/ru/io/deserialization-filters.yaml b/translations/content/ru/io/deserialization-filters.yaml new file mode 100644 index 0000000..def4966 --- /dev/null +++ b/translations/content/ru/io/deserialization-filters.yaml @@ -0,0 +1,17 @@ +title: "Фильтры десериализации" +oldApproach: "Принимать всё" +modernApproach: "ObjectInputFilter" +summary: "Ограничение допустимых для десериализации классов в целях предотвращения атак." +explanation: "ObjectInputFilter позволяет вести списки разрешённых и запрещённых классов, а также ограничивать глубину графа объектов, размеры массивов и счётчики ссылок. Это защищает от уязвимостей десериализации без внешних библиотек." +whyModernWins: +- icon: "🛡️" + title: "Безопасность" + desc: "Предотвращение десериализации неожиданных или вредоносных классов." +- icon: "📐" + title: "Детальный контроль" + desc: "Управление глубиной, размерами массивов, ссылками и шаблонами классов." +- icon: "🏗️" + title: "На уровне JVM" + desc: "Установка глобального фильтра для всех десериализаций в JVM." +support: + description: "Доступно в JDK 9 (сент. 2017)" diff --git a/translations/content/ru/io/file-memory-mapping.yaml b/translations/content/ru/io/file-memory-mapping.yaml new file mode 100644 index 0000000..bd760fd --- /dev/null +++ b/translations/content/ru/io/file-memory-mapping.yaml @@ -0,0 +1,17 @@ +title: "Отображение файлов в память" +oldApproach: "MappedByteBuffer" +modernApproach: "MemorySegment с Arena" +summary: "Отображение файлов размером более 2 ГБ с детерминированной очисткой через MemorySegment." +explanation: "Foreign Function & Memory API (JEP 454) вводит MemorySegment для безопасного и эффективного доступа к памяти. В отличие от MappedByteBuffer, MemorySegment поддерживает файлы размером более 2 ГБ (Integer.MAX_VALUE), обеспечивает детерминированную очистку через Arena и лучшую производительность на современном оборудовании." +whyModernWins: +- icon: "📏" + title: "Без ограничений по размеру" + desc: "Отображение файлов размером более 2 ГБ без обходных путей." +- icon: "🔒" + title: "Детерминированная очистка" + desc: "Arena гарантирует освобождение памяти при выходе из области видимости, а не при сборке мусора." +- icon: "⚡" + title: "Лучшая производительность" + desc: "Оптимизировано для современных моделей памяти и оборудования." +support: + description: "Доступно в JDK 22 (март 2024)" diff --git a/translations/content/ru/io/files-mismatch.yaml b/translations/content/ru/io/files-mismatch.yaml new file mode 100644 index 0000000..c4d6d51 --- /dev/null +++ b/translations/content/ru/io/files-mismatch.yaml @@ -0,0 +1,17 @@ +title: "Files.mismatch()" +oldApproach: "Ручное побайтовое сравнение" +modernApproach: "Files.mismatch()" +summary: "Эффективное сравнение двух файлов без загрузки их в память." +explanation: "Files.mismatch() возвращает позицию первого отличающегося байта или -1, если файлы идентичны. Читает лениво и прерывается при первом отличии." +whyModernWins: +- icon: "⚡" + title: "Эффективное использование памяти" + desc: "Не загружает полные файлы в байтовые массивы." +- icon: "🎯" + title: "Точное определение различия" + desc: "Возвращает точную позицию байта первого отличия." +- icon: "📏" + title: "Один вызов" + desc: "Никакой ручной логики сравнения байтовых массивов." +support: + description: "Доступно в JDK 12 (март 2019)" diff --git a/translations/content/ru/io/http-client.yaml b/translations/content/ru/io/http-client.yaml new file mode 100644 index 0000000..d7bb179 --- /dev/null +++ b/translations/content/ru/io/http-client.yaml @@ -0,0 +1,17 @@ +title: "Современный HTTP-клиент" +oldApproach: "HttpURLConnection" +modernApproach: "HttpClient" +summary: "Использование встроенного HttpClient для чистых современных HTTP-запросов." +explanation: "HttpClient поддерживает HTTP/1.1 и HTTP/2, асинхронные запросы, WebSocket, пользовательские исполнители и пул соединений. Больше не нужно приводить URLConnection или вручную читать потоки InputStream." +whyModernWins: +- icon: "📐" + title: "Builder API" + desc: "Текучий Builder для запросов, заголовков и таймаутов." +- icon: "🔄" + title: "Поддержка HTTP/2" + desc: "Встроенный HTTP/2 с мультиплексированием и Server Push." +- icon: "⚡" + title: "Асинхронность" + desc: "sendAsync() возвращает CompletableFuture." +support: + description: "Доступно в JDK 11 (сент. 2018)" diff --git a/translations/content/ru/io/inputstream-transferto.yaml b/translations/content/ru/io/inputstream-transferto.yaml new file mode 100644 index 0000000..8095c76 --- /dev/null +++ b/translations/content/ru/io/inputstream-transferto.yaml @@ -0,0 +1,17 @@ +title: "InputStream.transferTo()" +oldApproach: "Ручной цикл копирования" +modernApproach: "transferTo()" +summary: "Копирование InputStream в OutputStream одним вызовом." +explanation: "transferTo() читает все байты из входного потока и записывает их в выходной поток. Никакого управления буфером, никаких циклов. Используется оптимизированный внутренний буфер." +whyModernWins: +- icon: "📏" + title: "Одна строка" + desc: "Замена всего цикла чтения/записи одним вызовом метода." +- icon: "⚡" + title: "Оптимизировано" + desc: "Размер внутреннего буфера оптимизирован для производительности." +- icon: "🛡️" + title: "Без ошибок" + desc: "Никаких ошибок смещения на единицу при управлении буфером." +support: + description: "Доступно в JDK 9 (сент. 2017)" diff --git a/translations/content/ru/io/io-class-console-io.yaml b/translations/content/ru/io/io-class-console-io.yaml new file mode 100644 index 0000000..abc5230 --- /dev/null +++ b/translations/content/ru/io/io-class-console-io.yaml @@ -0,0 +1,17 @@ +title: "Класс IO для консольного ввода-вывода" +oldApproach: "System.out / Scanner" +modernApproach: "Класс IO" +summary: "Новый класс IO предоставляет простые, краткие методы для консольного ввода и вывода." +explanation: "Java 25 вводит класс IO (java.io.IO) как часть неявно объявленных классов. Он предоставляет статические методы println(), print(), readln() и read(), которые заменяют неудобное сочетание System.out и Scanner. IO.readln(prompt) объединяет приглашение ввода и чтение в одном вызове. Класс автоматически доступен в компактных исходных файлах и может использоваться в обычных классах через импорт." +whyModernWins: +- icon: "✨" + title: "Значительно проще" + desc: "Два метода заменяют семь строк настройки Scanner, приглашения, чтения и очистки." +- icon: "🔒" + title: "Без утечек ресурсов" + desc: "Не нужно закрывать Scanner — методы IO управляют ресурсами внутри." +- icon: "🎓" + title: "Удобно для начинающих" + desc: "Новые разработчики могут использовать консольный ввод-вывод без знания Scanner, System.out или операторов import." +support: + description: "Предварительный просмотр в JDK 25 как часть неявно объявленных классов (JEP 495)" diff --git a/translations/content/ru/io/path-of.yaml b/translations/content/ru/io/path-of.yaml new file mode 100644 index 0000000..8e07068 --- /dev/null +++ b/translations/content/ru/io/path-of.yaml @@ -0,0 +1,17 @@ +title: "Фабричный метод Path.of()" +oldApproach: "Paths.get()" +modernApproach: "Path.of()" +summary: "Использование Path.of() — современного фабричного метода интерфейса Path." +explanation: "Path.of() — это фабричный метод, добавленный непосредственно в интерфейс Path, заменяющий вспомогательный класс Paths. Он лучше обнаруживается и соответствует шаблону List.of(), Map.of() и т.д." +whyModernWins: +- icon: "📐" + title: "Единообразный API" + desc: "Следует шаблону фабрики .of() как List.of(), Set.of()." +- icon: "📖" + title: "Легко найти" + desc: "Находится непосредственно в типе Path, а не в отдельном классе Paths." +- icon: "🧹" + title: "На один класс меньше" + desc: "Не нужно импортировать вспомогательный класс Paths." +support: + description: "Доступно в JDK 11 (сент. 2018)" diff --git a/translations/content/ru/io/reading-files.yaml b/translations/content/ru/io/reading-files.yaml new file mode 100644 index 0000000..1c51c3e --- /dev/null +++ b/translations/content/ru/io/reading-files.yaml @@ -0,0 +1,17 @@ +title: "Чтение файлов" +oldApproach: "BufferedReader" +modernApproach: "Files.readString()" +summary: "Чтение всего файла в строку одной строкой кода." +explanation: "Files.readString() читает всё содержимое файла в строку. Автоматически обрабатывает кодировку (по умолчанию UTF-8) и очистку ресурсов. Для больших файлов предпочтительнее Files.lines() для ленивой потоковой обработки." +whyModernWins: +- icon: "📏" + title: "Одна строка" + desc: "Замена 8 строк шаблонного кода BufferedReader." +- icon: "🧹" + title: "Автоматическая очистка" + desc: "Дескриптор файла закрывается автоматически." +- icon: "🌐" + title: "UTF-8 по умолчанию" + desc: "Правильная кодировка по умолчанию — никакой путаницы с кодировкой символов." +support: + description: "Доступно в JDK 11 (сент. 2018)" diff --git a/translations/content/ru/io/try-with-resources-effectively-final.yaml b/translations/content/ru/io/try-with-resources-effectively-final.yaml new file mode 100644 index 0000000..b276b99 --- /dev/null +++ b/translations/content/ru/io/try-with-resources-effectively-final.yaml @@ -0,0 +1,17 @@ +title: "Улучшенный try-with-resources" +oldApproach: "Повторное объявление переменной" +modernApproach: "Эффективно финальная переменная" +summary: "Использование существующих эффективно финальных переменных непосредственно в try-with-resources." +explanation: "Java 9 позволяет использовать эффективно финальные переменные непосредственно в try-with-resources без повторного объявления. Это чище, когда ресурс был создан за пределами блока try." +whyModernWins: +- icon: "🧹" + title: "Без повторного объявления" + desc: "Использование существующего имени переменной напрямую." +- icon: "📖" + title: "Меньше путаницы" + desc: "Нет отдельного имени переменной внутри блока try." +- icon: "📏" + title: "Лаконично" + desc: "Меньше строк при той же безопасности ресурсов." +support: + description: "Доступно в JDK 9 (сент. 2017)" diff --git a/translations/content/ru/io/writing-files.yaml b/translations/content/ru/io/writing-files.yaml new file mode 100644 index 0000000..743b44d --- /dev/null +++ b/translations/content/ru/io/writing-files.yaml @@ -0,0 +1,17 @@ +title: "Запись файлов" +oldApproach: "FileWriter + BufferedWriter" +modernApproach: "Files.writeString()" +summary: "Запись строки в файл одной строкой кода." +explanation: "Files.writeString() записывает содержимое в файл с кодировкой UTF-8 по умолчанию. Для добавления содержимого, создания файла и т.п. можно передавать опции." +whyModernWins: +- icon: "📏" + title: "Одна строка" + desc: "Не нужна обёртка Writer или try-with-resources." +- icon: "🛡️" + title: "Безопасные значения по умолчанию" + desc: "Кодировка UTF-8, корректное закрытие дескриптора файла." +- icon: "🔧" + title: "Опции" + desc: "Флаги OpenOption для добавления содержимого, создания файла и т.д." +support: + description: "Доступно в JDK 11 (сент. 2018)" diff --git a/translations/content/ru/language/call-c-from-java.yaml b/translations/content/ru/language/call-c-from-java.yaml new file mode 100644 index 0000000..1ee2ae1 --- /dev/null +++ b/translations/content/ru/language/call-c-from-java.yaml @@ -0,0 +1,17 @@ +title: "Вызов C-кода из Java" +oldApproach: "JNI (Java Native Interface)" +modernApproach: "FFM (Foreign Function & Memory API)" +summary: "FFM позволяет Java напрямую вызывать C-библиотеки без шаблонного кода JNI и без знания Java на стороне C." +explanation: "Java предлагает два подхода для вызова нативного кода C/C++: традиционный JNI и современный FFM API. При использовании JNI нужно объявить метод как native, запустить javac -h для генерации заголовочного файла C и реализовать функцию с громоздким JNI C API (JNIEnv, jstring и т.д.). FFM, ставший стандартным API в Java 22, устраняет всё это: C-код остаётся чистым C — никаких JNI-соглашений не требуется. Это значительно упрощает вызов существующих библиотек C/C++ без каких-либо изменений. На стороне Java используется Arena для безопасного управления памятью вне кучи и MethodHandle для downcall, что обеспечивает гибкость и безопасность." +whyModernWins: +- icon: "👁" + title: "C-код остаётся чистым C" + desc: "Функции C не нужны JNI-аннотации или шаблонный JNIEnv — любая существующая C-библиотека может быть вызвана напрямую." +- icon: "⚡" + title: "Более гибкий подход" + desc: "Большинство существующих библиотек C/C++ можно вызывать напрямую без написания адаптерного кода или генерации заголовочных файлов." +- icon: "🛠️" + title: "Упрощённый рабочий процесс" + desc: "Не нужно останавливаться, запускать javac -h или реализовывать интерфейс, определённый в сгенерированном .h-файле." +support: + description: "Стандартизирован в JDK 22 (март 2024); ранее находился в инкубационной фазе с JDK 14" diff --git a/translations/content/ru/language/compact-canonical-constructor.yaml b/translations/content/ru/language/compact-canonical-constructor.yaml new file mode 100644 index 0000000..10a3310 --- /dev/null +++ b/translations/content/ru/language/compact-canonical-constructor.yaml @@ -0,0 +1,17 @@ +title: "Компактный канонический конструктор" +oldApproach: "Явная валидация в конструкторе" +modernApproach: "Компактный конструктор" +summary: "Валидация и нормализация полей record без повторения списка параметров." +explanation: "Record может объявить компактный канонический конструктор, который опускает список параметров и присваивания полей. Компилятор автоматически присваивает параметры полям после выполнения вашей логики валидации. Это идеально подходит для проверки предусловий, защитного копирования и нормализации." +whyModernWins: +- icon: "✂️" + title: "Меньше повторений" + desc: "Не нужно повторять список параметров и вручную присваивать каждое поле." +- icon: "🛡️" + title: "Валидация" + desc: "Отлично подходит для проверки на null, валидации диапазонов и защитного копирования." +- icon: "📖" + title: "Более понятное намерение" + desc: "Компактный синтаксис акцентирует внимание на валидации, а не на шаблонном коде." +support: + description: "Доступно в JDK 16 (март 2021)" diff --git a/translations/content/ru/language/compact-source-files.yaml b/translations/content/ru/language/compact-source-files.yaml new file mode 100644 index 0000000..d0c8e6d --- /dev/null +++ b/translations/content/ru/language/compact-source-files.yaml @@ -0,0 +1,17 @@ +title: "Компактные исходные файлы" +oldApproach: "Церемония классов для main" +modernApproach: "void main()" +summary: "Писать полноценную программу без объявления класса и public static void main." +explanation: "Компактные исходные файлы устраняют церемонию объявлений классов и сигнатуры метода main для простых программ. В сочетании с неявным импортом java.io.IO даже println доступен напрямую." +whyModernWins: +- icon: "🚀" + title: "Ноль церемоний" + desc: "Никакого класса, никакого public static void main, никакого String[] args." +- icon: "🎓" + title: "Дружелюбно для начинающих" + desc: "Новые программисты могут писать полезный код с первой же строки." +- icon: "📝" + title: "Похоже на скрипт" + desc: "Идеально для быстрых прототипов, скриптов и примеров." +support: + description: "Финализировано в JDK 25 LTS (JEP 512, сент. 2025)." diff --git a/translations/content/ru/language/default-interface-methods.yaml b/translations/content/ru/language/default-interface-methods.yaml new file mode 100644 index 0000000..a9f35c5 --- /dev/null +++ b/translations/content/ru/language/default-interface-methods.yaml @@ -0,0 +1,17 @@ +title: "Методы по умолчанию в интерфейсах" +oldApproach: "Абстрактные классы для общего поведения" +modernApproach: "Методы по умолчанию в интерфейсах" +summary: "Размещать реализации методов непосредственно в интерфейсах, обеспечивая множественное наследование поведения." +explanation: "До Java 8 совместное использование поведения между несвязанными классами требовало абстрактных классов, что ограничивало одиночным наследованием. Методы по умолчанию позволяют интерфейсам предоставлять реализации методов, так что классы могут наследовать поведение от нескольких интерфейсов. Это было ключевым для развития Collections API (например, List.forEach, Map.getOrDefault) без нарушения существующих реализаций." +whyModernWins: +- icon: "🔀" + title: "Множественное наследование" + desc: "Классы могут реализовывать множество интерфейсов с методами по умолчанию — в отличие от единственного абстрактного класса." +- icon: "📦" + title: "Эволюция API" + desc: "Добавлять новые методы в интерфейсы без нарушения существующих реализаций." +- icon: "🧩" + title: "Компонуемое поведение" + desc: "Свободно комбинировать возможности из нескольких интерфейсов." +support: + description: "Доступно с JDK 8 (март 2014)." diff --git a/translations/content/ru/language/diamond-operator.yaml b/translations/content/ru/language/diamond-operator.yaml new file mode 100644 index 0000000..ac1c34b --- /dev/null +++ b/translations/content/ru/language/diamond-operator.yaml @@ -0,0 +1,17 @@ +title: "Оператор diamond с анонимными классами" +oldApproach: "Повторение аргументов типа" +modernApproach: "Diamond <>" +summary: "Оператор diamond теперь работает и с анонимными классами." +explanation: "Java 7 представила <>, но это не работало с анонимными внутренними классами. Java 9 исправила это, так что аргументы типа в правой части никогда не нужно повторять." +whyModernWins: +- icon: "📏" + title: "Единые правила" + desc: "Diamond работает везде — как в конструкторах, так и в анонимных классах." +- icon: "🧹" + title: "Меньше избыточности" + desc: "Аргументы типа указываются один раз слева и никогда не повторяются." +- icon: "🔧" + title: "Принцип DRY" + desc: "Компилятор уже знает тип — зачем писать его дважды?" +support: + description: "Diamond с анонимными классами с JDK 9 (сент. 2017)." diff --git a/translations/content/ru/language/exhaustive-switch.yaml b/translations/content/ru/language/exhaustive-switch.yaml new file mode 100644 index 0000000..b45fefd --- /dev/null +++ b/translations/content/ru/language/exhaustive-switch.yaml @@ -0,0 +1,17 @@ +title: "Исчерпывающий switch без default" +oldApproach: "Обязательная ветка default" +modernApproach: "Исчерпываемость sealed" +summary: "Компилятор проверяет, что все sealed-подтипы охвачены — default не требуется." +explanation: "При switch по sealed-типу компилятор знает все возможные подтипы и проверяет, что каждый случай обработан. Если добавить новый подтип, компилятор укажет на каждый switch, который стал неполным." +whyModernWins: +- icon: "✅" + title: "Безопасность на этапе компиляции" + desc: "Добавьте новый подтип — компилятор покажет каждое место, которое нужно обновить." +- icon: "🚫" + title: "Нет мёртвого кода" + desc: "Никакой недостижимой ветки default, скрывающей ошибки." +- icon: "📐" + title: "Алгебраические типы" + desc: "sealed + records + исчерпывающий switch = настоящие ADT в Java." +support: + description: "Доступно в JDK 21 LTS (сент. 2023)" diff --git a/translations/content/ru/language/flexible-constructor-bodies.yaml b/translations/content/ru/language/flexible-constructor-bodies.yaml new file mode 100644 index 0000000..7a22414 --- /dev/null +++ b/translations/content/ru/language/flexible-constructor-bodies.yaml @@ -0,0 +1,17 @@ +title: "Гибкое тело конструктора" +oldApproach: "Валидация после super()" +modernApproach: "Код перед super()" +summary: "Валидировать и вычислять значения до вызова super() или this()." +explanation: "Java 25 снимает ограничение, согласно которому super() должен быть первым оператором. Теперь можно валидировать аргументы, вычислять производные значения и подготавливать состояние перед делегированием родительскому конструктору." +whyModernWins: +- icon: "🛡️" + title: "Раннее завершение" + desc: "Валидировать аргументы до выполнения конструктора суперкласса." +- icon: "🧮" + title: "Сначала вычислить" + desc: "Вывести значения и подготовить данные до вызова super()." +- icon: "🧹" + title: "Никаких обходных путей" + desc: "Больше не нужны статические вспомогательные методы или паттерн Factory для обхода ограничения." +support: + description: "Финализировано в JDK 25 LTS (JEP 513, сент. 2025)." diff --git a/translations/content/ru/language/guarded-patterns.yaml b/translations/content/ru/language/guarded-patterns.yaml new file mode 100644 index 0000000..31a1145 --- /dev/null +++ b/translations/content/ru/language/guarded-patterns.yaml @@ -0,0 +1,17 @@ +title: "Охраняемые паттерны с when" +oldApproach: "Вложенный if" +modernApproach: "Клауза when" +summary: "Добавлять условия к ветвям паттернов с помощью охранников when." +explanation: "Охраняемые паттерны позволяют уточнить совпадение по типу дополнительным булевым условием. Благодаря этому вся логика ветвления остаётся в switch, а не вложена в ветви в виде if-операторов." +whyModernWins: +- icon: "🎯" + title: "Точное совпадение" + desc: "Объединить тип и условие в одной метке case." +- icon: "📐" + title: "Плоская структура" + desc: "Никаких вложенных if/else внутри ветвей switch." +- icon: "📖" + title: "Читаемое намерение" + desc: "Клауза when читается как естественный язык." +support: + description: "Доступно в JDK 21 LTS (сент. 2023)" diff --git a/translations/content/ru/language/markdown-javadoc-comments.yaml b/translations/content/ru/language/markdown-javadoc-comments.yaml new file mode 100644 index 0000000..cc02f6d --- /dev/null +++ b/translations/content/ru/language/markdown-javadoc-comments.yaml @@ -0,0 +1,17 @@ +title: "Markdown в комментариях Javadoc" +oldApproach: "Javadoc на основе HTML" +modernApproach: "Javadoc на Markdown" +summary: "Писать комментарии Javadoc на Markdown вместо HTML для лучшей читаемости." +explanation: "Java 23 вводит комментарии Javadoc на Markdown с синтаксисом /// как альтернативу традиционному формату /** */ на основе HTML. Синтаксис Markdown более естественен для написания и чтения: поддерживаются блоки кода, выделение, списки и ссылки. Компилятор преобразует Markdown в HTML для вывода Javadoc." +whyModernWins: +- icon: "📖" + title: "Естественный синтаксис" + desc: "Обратные кавычки для inline-кода и ``` для блоков вместо HTML-тегов." +- icon: "✍️" + title: "Проще писать" + desc: "Никакого {@code},
, 

-тегов — просто пишите Markdown." +- icon: "👁" + title: "Лучше в редакторах" + desc: "Markdown отлично отображается в современных IDE и текстовых редакторах." +support: + description: "Доступно с JDK 23 (сент. 2024)" diff --git a/translations/content/ru/language/module-import-declarations.yaml b/translations/content/ru/language/module-import-declarations.yaml new file mode 100644 index 0000000..3fcb33b --- /dev/null +++ b/translations/content/ru/language/module-import-declarations.yaml @@ -0,0 +1,17 @@ +title: "Объявления импорта модулей" +oldApproach: "Множество импортов" +modernApproach: "import module" +summary: "Импортировать все экспортируемые пакеты модуля одним объявлением." +explanation: "Объявления импорта модулей позволяют импортировать всё, что экспортирует модуль, одной строкой. Особенно полезно для java.base, который охватывает коллекции, ввод-вывод, потоки и многое другое." +whyModernWins: +- icon: "🧹" + title: "Одна строка" + desc: "Заменить стену импортов одним импортом модуля." +- icon: "📦" + title: "Осведомлённость о модульной системе" + desc: "Использует модульную систему для импорта связанных пакетов." +- icon: "🚀" + title: "Быстрый старт" + desc: "Идеально для скриптов и прототипов, где списки импортов утомительны." +support: + description: "Финализировано в JDK 25 LTS (JEP 511, сент. 2025)." diff --git a/translations/content/ru/language/pattern-matching-instanceof.yaml b/translations/content/ru/language/pattern-matching-instanceof.yaml new file mode 100644 index 0000000..d7321d6 --- /dev/null +++ b/translations/content/ru/language/pattern-matching-instanceof.yaml @@ -0,0 +1,17 @@ +title: "Pattern Matching для instanceof" +oldApproach: "instanceof + приведение типа" +modernApproach: "Переменная паттерна" +summary: "Совместить проверку типа и приведение в одном шаге с помощью pattern matching." +explanation: "Pattern matching для instanceof устраняет избыточное приведение после проверки типа. Переменная автоматически ограничена областью видимости, в которой паттерн совпадает, что делает код безопаснее и короче." +whyModernWins: +- icon: "🔄" + title: "Нет лишнего приведения" + desc: "Проверка типа и привязка переменной происходят в одном выражении." +- icon: "📏" + title: "Меньше строк" + desc: "Одна строка вместо двух — строка с приведением исчезает полностью." +- icon: "🛡️" + title: "Безопасность области видимости" + desc: "Переменная паттерна находится в области видимости только там, где тип гарантирован." +support: + description: "Доступно в JDK 16 (март 2021)" diff --git a/translations/content/ru/language/pattern-matching-switch.yaml b/translations/content/ru/language/pattern-matching-switch.yaml new file mode 100644 index 0000000..eb3d94e --- /dev/null +++ b/translations/content/ru/language/pattern-matching-switch.yaml @@ -0,0 +1,17 @@ +title: "Pattern Matching в switch" +oldApproach: "Цепочка if-else" +modernApproach: "Паттерны типов" +summary: "Заменить цепочки if-else-instanceof чистыми паттернами типов в switch." +explanation: "Pattern matching в switch позволяет напрямую сопоставлять типы, объединяя проверку типа, приведение и привязку в лаконичной метке case. Компилятор проверяет полноту охвата." +whyModernWins: +- icon: "📐" + title: "Структурированная диспетчеризация" + desc: "Switch делает структуру ветвления явной и понятной." +- icon: "🎯" + title: "Форма выражения" + desc: "Возвращает значение напрямую — изменяемая переменная не нужна." +- icon: "✅" + title: "Проверка исчерпываемости" + desc: "Компилятор обеспечивает обработку всех типов." +support: + description: "Доступно в JDK 21 LTS (сент. 2023)" diff --git a/translations/content/ru/language/primitive-types-in-patterns.yaml b/translations/content/ru/language/primitive-types-in-patterns.yaml new file mode 100644 index 0000000..7ac1d2b --- /dev/null +++ b/translations/content/ru/language/primitive-types-in-patterns.yaml @@ -0,0 +1,17 @@ +title: "Примитивные типы в паттернах" +oldApproach: "Ручные проверки диапазонов" +modernApproach: "Примитивные паттерны" +summary: "Pattern matching теперь работает и с примитивными типами, а не только с объектами." +explanation: "Java 25 расширяет pattern matching на примитивные типы. int, long, double и т.д. можно использовать в паттернах switch с охранниками when, что избавляет от боксинга и ручных проверок диапазонов." +whyModernWins: +- icon: "📦" + title: "Нет боксинга" + desc: "Сопоставлять примитивы напрямую — обёртка Integer не нужна." +- icon: "🎯" + title: "Единообразие паттернов" + desc: "Одинаковый синтаксис паттернов для объектов и примитивов." +- icon: "⚡" + title: "Лучшая производительность" + desc: "Избежать накладных расходов автобоксинга при pattern matching." +support: + description: "Предварительный просмотр в JDK 25 (третий превью, JEP 507). Требует --enable-preview." diff --git a/translations/content/ru/language/private-interface-methods.yaml b/translations/content/ru/language/private-interface-methods.yaml new file mode 100644 index 0000000..3bf286d --- /dev/null +++ b/translations/content/ru/language/private-interface-methods.yaml @@ -0,0 +1,17 @@ +title: "Приватные методы интерфейса" +oldApproach: "Дублированная логика" +modernApproach: "Приватные методы" +summary: "Выносить общую логику в интерфейсах в приватные методы." +explanation: "Java 9 позволяет использовать приватные методы в интерфейсах, что даёт возможность совместно использовать код между методами по умолчанию без раскрытия деталей реализации классам-реализаторам." +whyModernWins: +- icon: "🧩" + title: "Повторное использование кода" + desc: "Совместно использовать логику между методами по умолчанию без дублирования." +- icon: "🔐" + title: "Инкапсуляция" + desc: "Детали реализации остаются скрытыми от классов-реализаторов." +- icon: "🧹" + title: "DRY-интерфейсы" + desc: "Больше никакого копирования и вставки между методами по умолчанию." +support: + description: "Доступно в JDK 9 (сент. 2017)" diff --git a/translations/content/ru/language/record-patterns.yaml b/translations/content/ru/language/record-patterns.yaml new file mode 100644 index 0000000..f9c8b84 --- /dev/null +++ b/translations/content/ru/language/record-patterns.yaml @@ -0,0 +1,17 @@ +title: "Паттерны записей (деструктуризация)" +oldApproach: "Ручной доступ к полям" +modernApproach: "Деструктуризация" +summary: "Деструктурировать записи прямо в паттернах — извлекать поля за один шаг." +explanation: "Паттерны записей позволяют разложить компоненты записи непосредственно в instanceof и switch. Вложенные паттерны также поддерживаются, что обеспечивает глубокое сопоставление без промежуточных переменных." +whyModernWins: +- icon: "🎯" + title: "Прямой доступ" + desc: "Обращаться к компонентам записи без ручного вызова аксессоров." +- icon: "🪆" + title: "Вложенность" + desc: "Паттерны можно вкладывать — сопоставлять внутренние записи в одном выражении." +- icon: "📏" + title: "Компактный код" + desc: "Пять строк превращаются в две — меньше церемоний, та же ясность." +support: + description: "Доступно в JDK 21 LTS (сент. 2023)" diff --git a/translations/content/ru/language/records-for-data-classes.yaml b/translations/content/ru/language/records-for-data-classes.yaml new file mode 100644 index 0000000..e46fb63 --- /dev/null +++ b/translations/content/ru/language/records-for-data-classes.yaml @@ -0,0 +1,17 @@ +title: "Записи для классов данных" +oldApproach: "Многословный POJO" +modernApproach: "record" +summary: "Одна строка заменяет более 30 строк шаблонного кода для неизменяемых носителей данных." +explanation: "Записи автоматически генерируют конструктор, аксессоры (x(), y()), equals(), hashCode() и toString(). Они неизменяемы по своей природе и идеально подходят для DTO, объектов-значений и pattern matching." +whyModernWins: +- icon: "⚡" + title: "Однострочное определение" + desc: "Одна строка заменяет конструктор, геттеры, equals, hashCode, toString." +- icon: "🔒" + title: "Неизменяемость по умолчанию" + desc: "Все поля final — никаких ловушек с сеттерами." +- icon: "🧩" + title: "Дружественность к паттернам" + desc: "Записи работают с паттернами деструктуризации в switch и instanceof." +support: + description: "Доступно в JDK 16 (март 2021)" diff --git a/translations/content/ru/language/sealed-classes.yaml b/translations/content/ru/language/sealed-classes.yaml new file mode 100644 index 0000000..d52b1e7 --- /dev/null +++ b/translations/content/ru/language/sealed-classes.yaml @@ -0,0 +1,17 @@ +title: "Запечатанные классы для иерархий типов" +oldApproach: "Открытая иерархия" +modernApproach: "sealed permits" +summary: "Ограничить, какие классы могут расширять тип — для исчерпывающих switch." +explanation: "Запечатанные классы определяют закрытое множество подтипов. Компилятор знает все возможные случаи и позволяет использовать исчерпывающий pattern matching без ветки default. В сочетании с записями они моделируют алгебраические типы данных." +whyModernWins: +- icon: "🔐" + title: "Контролируемая иерархия" + desc: "Расширять могут только разрешённые подтипы — никаких неожиданных подклассов." +- icon: "✅" + title: "Исчерпывающее сопоставление" + desc: "Компилятор проверяет, что switch охватывает все случаи — default не нужен." +- icon: "📐" + title: "Алгебраические типы данных" + desc: "Естественное моделирование типов-сумм — sealed + records = ADT в Java." +support: + description: "Доступно в JDK 17 LTS (сент. 2021)" diff --git a/translations/content/ru/language/static-members-in-inner-classes.yaml b/translations/content/ru/language/static-members-in-inner-classes.yaml new file mode 100644 index 0000000..8ad0124 --- /dev/null +++ b/translations/content/ru/language/static-members-in-inner-classes.yaml @@ -0,0 +1,17 @@ +title: "Статические члены во внутренних классах" +oldApproach: "Необходимость статически вложенного класса" +modernApproach: "Статические члены во внутренних классах" +summary: "Определять статические члены во внутренних классах без использования статически вложенных классов." +explanation: "До Java 16 только статически вложенные классы могли содержать статические члены. Внутренние (нестатические) классы не могли иметь статики, поскольку требовали экземпляра охватывающего класса. Java 16 ослабляет это ограничение и позволяет статические поля, методы и даже вложенные типы во внутренних классах." +whyModernWins: +- icon: "🔓" + title: "Больше гибкости" + desc: "Внутренние классы теперь при необходимости могут иметь статические члены." +- icon: "🧩" + title: "Общее состояние" + desc: "Отслеживать общее состояние между экземплярами внутреннего класса." +- icon: "📐" + title: "Свобода проектирования" + desc: "Не нужно повышать до статически вложенного класса из-за одного статического поля." +support: + description: "Доступно в JDK 16 (март 2021)" diff --git a/translations/content/ru/language/static-methods-in-interfaces.yaml b/translations/content/ru/language/static-methods-in-interfaces.yaml new file mode 100644 index 0000000..44a4602 --- /dev/null +++ b/translations/content/ru/language/static-methods-in-interfaces.yaml @@ -0,0 +1,17 @@ +title: "Статические методы в интерфейсах" +oldApproach: "Вспомогательные классы" +modernApproach: "Статические методы интерфейса" +summary: "Добавлять статические вспомогательные методы непосредственно в интерфейсы вместо отдельных вспомогательных классов." +explanation: "До Java 8 вспомогательные методы, относящиеся к интерфейсу, должны были находиться в отдельном классе (например, Collections для Collection). Статические методы в интерфейсах позволяют держать связанные вспомогательные функции вместе. Широко используется в современных API: Comparator.comparing(), Stream.of() и List.of()." +whyModernWins: +- icon: "📦" + title: "Лучшая организация" + desc: "Хранить связанные вспомогательные функции рядом с интерфейсом, а не в отдельном классе." +- icon: "🔍" + title: "Обнаруживаемость" + desc: "Фабричные и вспомогательные методы находятся там, где их ожидают." +- icon: "🧩" + title: "Связность API" + desc: "Больше не нужны отдельные классы *Utils или *Helper." +support: + description: "Доступно с JDK 8 (март 2014)" diff --git a/translations/content/ru/language/switch-expressions.yaml b/translations/content/ru/language/switch-expressions.yaml new file mode 100644 index 0000000..329a767 --- /dev/null +++ b/translations/content/ru/language/switch-expressions.yaml @@ -0,0 +1,17 @@ +title: "Switch-выражения" +oldApproach: "Оператор switch" +modernApproach: "Switch-выражение" +summary: "Switch как выражение, возвращающее значение — без break и без fall-through." +explanation: "Switch-выражения возвращают значение напрямую, используют стрелочный синтаксис для предотвращения ошибок fall-through, а компилятор проверяет исчерпываемость. Это заменяет подверженную ошибкам форму оператора." +whyModernWins: +- icon: "🎯" + title: "Возвращает значение" + desc: "Присвоить результат switch напрямую — временная переменная не нужна." +- icon: "🛡️" + title: "Нет fall-through" + desc: "Стрелочный синтаксис устраняет случайные ошибки fall-through из-за пропущенного break." +- icon: "✅" + title: "Проверка исчерпываемости" + desc: "Компилятор обеспечивает охват всех случаев." +support: + description: "Доступно в JDK 14 (март 2020)" diff --git a/translations/content/ru/language/text-blocks-for-multiline-strings.yaml b/translations/content/ru/language/text-blocks-for-multiline-strings.yaml new file mode 100644 index 0000000..ddead24 --- /dev/null +++ b/translations/content/ru/language/text-blocks-for-multiline-strings.yaml @@ -0,0 +1,17 @@ +title: "Текстовые блоки для многострочных строк" +oldApproach: "Конкатенация строк" +modernApproach: "Текстовые блоки" +summary: "Писать многострочные строки естественно с помощью текстовых блоков с тройными кавычками." +explanation: "Текстовые блоки позволяют писать многострочные строки именно так, как они выглядят. Больше не нужны escape-последовательности для кавычек или \\n. Компилятор автоматически удаляет случайные отступы." +whyModernWins: +- icon: "📖" + title: "Читается как есть" + desc: "JSON, SQL и HTML в исходном коде выглядят как настоящие JSON, SQL и HTML." +- icon: "🚫" + title: "Никаких escape-последовательностей" + desc: "Встроенные кавычки не требуют экранирования обратной косой чертой." +- icon: "📐" + title: "Умные отступы" + desc: "Ведущие пробелы автоматически обрезаются по позиции закрывающего разделителя." +support: + description: "Доступно в JDK 15 (сент. 2020)" diff --git a/translations/content/ru/language/type-inference-with-var.yaml b/translations/content/ru/language/type-inference-with-var.yaml new file mode 100644 index 0000000..71ce0c5 --- /dev/null +++ b/translations/content/ru/language/type-inference-with-var.yaml @@ -0,0 +1,17 @@ +title: "Вывод типов с var" +oldApproach: "Явные типы" +modernApproach: "Ключевое слово var" +summary: "Использовать var для вывода типов локальных переменных — меньше шума, та же безопасность." +explanation: "Начиная с Java 10 компилятор выводит типы локальных переменных из правой части выражения. Это уменьшает визуальный шум без ущерба для типобезопасности. var рекомендуется там, где тип очевиден из контекста." +whyModernWins: +- icon: "⚡" + title: "Меньше шаблонного кода" + desc: "Не нужно повторять сложные обобщённые типы по обе стороны присваивания." +- icon: "👁" + title: "Лучшая читаемость" + desc: "Фокус на именах переменных и значениях, а не на объявлениях типов." +- icon: "🔒" + title: "По-прежнему типобезопасно" + desc: "Компилятор выводит и применяет точный тип во время компиляции." +support: + description: "Доступно в JDK 10 (март 2018)" diff --git a/translations/content/ru/language/unnamed-variables.yaml b/translations/content/ru/language/unnamed-variables.yaml new file mode 100644 index 0000000..f52f1f6 --- /dev/null +++ b/translations/content/ru/language/unnamed-variables.yaml @@ -0,0 +1,17 @@ +title: "Безымянные переменные с _" +oldApproach: "Неиспользуемая переменная" +modernApproach: "_ как заполнитель" +summary: "Использовать _, чтобы явно указать, что переменная намеренно не используется." +explanation: "Безымянные переменные сообщают читателям и инструментам, что значение намеренно игнорируется. Больше не нужны соглашения об именовании типа «ignored» или «unused», больше нет предупреждений IDE." +whyModernWins: +- icon: "📢" + title: "Явное намерение" + desc: "_ явно говорит «это значение здесь не нужно»." +- icon: "🔇" + title: "Нет предупреждений" + desc: "IDE и линтеры больше не помечают намеренно неиспользуемые переменные." +- icon: "🧹" + title: "Более чистые лямбды" + desc: "Лямбды с несколькими параметрами выглядят чище, когда нужны только некоторые из них." +support: + description: "Финализировано в JDK 22 (JEP 456, март 2024)." diff --git a/translations/content/ru/security/key-derivation-functions.yaml b/translations/content/ru/security/key-derivation-functions.yaml new file mode 100644 index 0000000..6776795 --- /dev/null +++ b/translations/content/ru/security/key-derivation-functions.yaml @@ -0,0 +1,19 @@ +title: Функции формирования ключей +oldApproach: Ручной PBKDF2 +modernApproach: KDF API +summary: Формирование криптографических ключей с помощью стандартного KDF API. +explanation: KDF API предоставляет стандартный интерфейс для функций формирования + ключей, включая HKDF. Он заменяет неудобный шаблон SecretKeyFactory + PBEKeySpec + на чистый строительный API. +whyModernWins: +- icon: 📐 + title: Чистый API + desc: Шаблон строителя вместо неудобных конструкторов KeySpec. +- icon: 🔧 + title: Поддержка HKDF + desc: Современный алгоритм HKDF наряду с PBKDF2. +- icon: 🛡️ + title: Стандарт + desc: Единый API для всех алгоритмов формирования ключей. +support: + description: Финализировано в JDK 25 LTS (JEP 510, сент. 2025). diff --git a/translations/content/ru/security/pem-encoding.yaml b/translations/content/ru/security/pem-encoding.yaml new file mode 100644 index 0000000..ddbb8d4 --- /dev/null +++ b/translations/content/ru/security/pem-encoding.yaml @@ -0,0 +1,19 @@ +title: Кодирование/декодирование PEM +oldApproach: Ручной Base64 + заголовки +modernApproach: PEM API +summary: Нативное кодирование и декодирование криптографических объектов в формате PEM. +explanation: PEM API предоставляет стандартное кодирование/декодирование для сертификатов, + ключей и других криптографических объектов в формате PEM. Больше никакого ручного + оборачивания Base64 с заголовками BEGIN/END. +whyModernWins: +- icon: 🧹 + title: Без ручного Base64 + desc: Заголовки PEM, перенос строк и Base64 обрабатываются автоматически. +- icon: 🔄 + title: Двунаправленный + desc: Кодирование в PEM и декодирование из PEM с помощью одного API. +- icon: 🛡️ + title: Стандартный формат + desc: Создаёт PEM-вывод, соответствующий RFC 7468. +support: + description: Предварительная версия в JDK 25 (JEP 470). Требует --enable-preview. diff --git a/translations/content/ru/security/random-generator.yaml b/translations/content/ru/security/random-generator.yaml new file mode 100644 index 0000000..2c991c6 --- /dev/null +++ b/translations/content/ru/security/random-generator.yaml @@ -0,0 +1,21 @@ +title: Интерфейс RandomGenerator +oldApproach: new Random() / ThreadLocalRandom +modernApproach: Фабрика RandomGenerator +summary: Использование интерфейса RandomGenerator для выбора алгоритмов генерации + случайных чисел по имени без привязки к конкретному классу. +explanation: JDK 17 представил RandomGenerator как общий интерфейс для всех реализаций + RNG. Вместо жёсткой привязки к new Random() или ThreadLocalRandom можно выбирать + алгоритмы по имени через фабрику, что упрощает переключение между алгоритмами, + оптимизированными для разных сценариев (скорость, статистическое качество, делимость). +whyModernWins: +- icon: 🔧 + title: Независимость от алгоритма + desc: Выбор лучшего алгоритма RNG по имени без изменения структуры кода. +- icon: ⚡ + title: Лучшие алгоритмы + desc: Доступ к современным генераторам LXM с превосходными статистическими свойствами. +- icon: 🔗 + title: Единый API + desc: Один интерфейс охватывает Random, ThreadLocalRandom, SplittableRandom и другие. +support: + description: Доступно с JDK 17 (сентябрь 2021, JEP 356). diff --git a/translations/content/ru/security/strong-random.yaml b/translations/content/ru/security/strong-random.yaml new file mode 100644 index 0000000..0d78ecb --- /dev/null +++ b/translations/content/ru/security/strong-random.yaml @@ -0,0 +1,19 @@ +title: Генерация надёжных случайных чисел +oldApproach: new SecureRandom() +modernApproach: getInstanceStrong() +summary: Получение наиболее надёжной реализации SecureRandom на платформе. +explanation: getInstanceStrong() возвращает реализацию SecureRandom, настроенную + как наиболее надёжная на платформе. Это управляется свойством безопасности + securerandom.strongAlgorithms. +whyModernWins: +- icon: 🛡️ + title: Наиболее надёжный + desc: Автоматически выбирает лучший алгоритм для платформы. +- icon: 📖 + title: Явное намерение + desc: Явно указывает, что требуется высокая степень случайности. +- icon: 🔧 + title: Настраиваемый + desc: Администраторы могут изменить надёжный алгоритм через свойства безопасности. +support: + description: Широко доступно с JDK 9 (сент. 2017) diff --git a/translations/content/ru/security/tls-default.yaml b/translations/content/ru/security/tls-default.yaml new file mode 100644 index 0000000..b74c174 --- /dev/null +++ b/translations/content/ru/security/tls-default.yaml @@ -0,0 +1,19 @@ +title: TLS 1.3 по умолчанию +oldApproach: Ручная настройка TLS +modernApproach: TLS 1.3 по умолчанию +summary: "TLS 1.3 включён по умолчанию — явная настройка протокола не требуется." +explanation: Java 11 добавила поддержку TLS 1.3 и сделала его предпочтительным протоколом. + HttpClient использует его автоматически. Больше не нужно вручную указывать версии + протокола для защищённых соединений. +whyModernWins: +- icon: 🛡️ + title: Более безопасный + desc: TLS 1.3 удаляет устаревшие наборы шифров и шаблоны рукопожатия. +- icon: ⚡ + title: Более быстрое рукопожатие + desc: TLS 1.3 завершается за один обмен вместо двух. +- icon: 🆓 + title: Без конфигурации + desc: "Безопасен по умолчанию — явный выбор протокола не требуется." +support: + description: Широко доступно с JDK 11 (сент. 2018) diff --git a/translations/content/ru/streams/collectors-flatmapping.yaml b/translations/content/ru/streams/collectors-flatmapping.yaml new file mode 100644 index 0000000..34f90f4 --- /dev/null +++ b/translations/content/ru/streams/collectors-flatmapping.yaml @@ -0,0 +1,19 @@ +title: Collectors.flatMapping() +oldApproach: Вложенный flatMap +modernApproach: flatMapping() +summary: Использование flatMapping() для сглаживания внутри группирующего Collector. +explanation: Collectors.flatMapping() применяет отображение «один ко многим» в качестве + нижестоящего Collector. Это эквивалент Collector для Stream.flatMap() — полезен + внутри groupingBy или partitioningBy. +whyModernWins: +- icon: 🧩 + title: Компонуемый + desc: Работает как нижестоящий Collector внутри groupingBy. +- icon: 📐 + title: Один проход + desc: Сглаживание и группировка за один проход Stream. +- icon: 🔗 + title: Вкладываемый + desc: Сочетается с другими нижестоящими Collectors. +support: + description: Широко доступно с JDK 9 (сент. 2017) diff --git a/translations/content/ru/streams/optional-ifpresentorelse.yaml b/translations/content/ru/streams/optional-ifpresentorelse.yaml new file mode 100644 index 0000000..c97827c --- /dev/null +++ b/translations/content/ru/streams/optional-ifpresentorelse.yaml @@ -0,0 +1,19 @@ +title: Optional.ifPresentOrElse() +oldApproach: if/else с Optional +modernApproach: ifPresentOrElse() +summary: Обработка обоих случаев Optional — наличие и отсутствие значения — в одном + вызове. +explanation: ifPresentOrElse() принимает Consumer для случая наличия значения и Runnable + для случая пустого Optional. Устраняет антипаттерн isPresent/get. +whyModernWins: +- icon: 📏 + title: Единое выражение + desc: Оба случая обрабатываются в одном вызове метода. +- icon: 🚫 + title: Без get() + desc: Устраняет опасный паттерн isPresent() + get(). +- icon: 🔗 + title: Цепочный + desc: Естественно связывается после findUser() или любого метода, возвращающего Optional. +support: + description: Широко доступно с JDK 9 (сент. 2017) diff --git a/translations/content/ru/streams/optional-or.yaml b/translations/content/ru/streams/optional-or.yaml new file mode 100644 index 0000000..2aa4bf8 --- /dev/null +++ b/translations/content/ru/streams/optional-or.yaml @@ -0,0 +1,19 @@ +title: Optional.or() как запасной вариант +oldApproach: Вложенный запасной вариант +modernApproach: Цепочка .or() +summary: Цепочка запасных вариантов Optional без вложенных проверок. +explanation: Optional.or() возвращает исходный Optional, если он содержит значение, + в противном случае вычисляет Supplier для получения альтернативного Optional. + Supplier-ы ленивы — они вызываются только при необходимости. +whyModernWins: +- icon: 🔗 + title: Цепочный + desc: Выстраивать запасные варианты в читаемый пайплайн. +- icon: ⚡ + title: Ленивое вычисление + desc: Supplier запасного варианта выполняется только при необходимости. +- icon: 📖 + title: Декларативный + desc: "Читается как 'попробуй основное, или вторичное, или по умолчанию'." +support: + description: Широко доступно с JDK 9 (сент. 2017) diff --git a/translations/content/ru/streams/predicate-not.yaml b/translations/content/ru/streams/predicate-not.yaml new file mode 100644 index 0000000..b572c8b --- /dev/null +++ b/translations/content/ru/streams/predicate-not.yaml @@ -0,0 +1,20 @@ +title: Predicate.not() для отрицания +oldApproach: Отрицание через лямбду +modernApproach: Predicate.not() +summary: Использование Predicate.not() для чистого отрицания ссылок на методы вместо + написания лямбда-обёрток. +explanation: До Java 11 для отрицания ссылки на метод требовалась её обёртка в лямбду. + Predicate.not() позволяет напрямую отрицать любой предикат, что делает код читаемым + и согласованным со стилем ссылок на методы во всём пайплайне Stream. +whyModernWins: +- icon: 👁 + title: Чистое отрицание + desc: Не нужны лямбда-обёртки для ссылок на методы только ради отрицания. +- icon: 🔗 + title: Компонуемый + desc: Работает с любым Predicate и позволяет строить чистые цепочки предикатов. +- icon: 📖 + title: Естественно читаемый + desc: Predicate.not(String::isBlank) читается как естественный язык. +support: + description: Доступно с JDK 11 (сентябрь 2018). diff --git a/translations/content/ru/streams/stream-gatherers.yaml b/translations/content/ru/streams/stream-gatherers.yaml new file mode 100644 index 0000000..d878150 --- /dev/null +++ b/translations/content/ru/streams/stream-gatherers.yaml @@ -0,0 +1,19 @@ +title: Stream Gatherers +oldApproach: Пользовательский Collector +modernApproach: gather() +summary: Использование Gatherers для пользовательских промежуточных операций Stream. +explanation: Gatherers — это новая промежуточная операция Stream, позволяющая выражать + сложные преобразования, такие как скользящие окна, группы фиксированного размера + и операции сканирования, которые были невозможны со стандартными операциями Stream. +whyModernWins: +- icon: 🧩 + title: Компонуемый + desc: Gatherers сочетаются с другими операциями Stream. +- icon: 📦 + title: Встроенные операции + desc: "windowFixed, windowSliding, fold, scan доступны из коробки." +- icon: 🔧 + title: Расширяемый + desc: Написание собственных Gatherers для любых промежуточных преобразований. +support: + description: "Финализировано в JDK 24 (JEP 485, март 2025)." diff --git a/translations/content/ru/streams/stream-iterate-predicate.yaml b/translations/content/ru/streams/stream-iterate-predicate.yaml new file mode 100644 index 0000000..046aab2 --- /dev/null +++ b/translations/content/ru/streams/stream-iterate-predicate.yaml @@ -0,0 +1,19 @@ +title: Stream.iterate() с предикатом +oldApproach: iterate + limit +modernApproach: iterate(seed, pred, op) +summary: Использование предиката для завершения итерации — как цикл for в виде Stream. +explanation: "Трёхаргументный Stream.iterate(seed, hasNext, next) работает как цикл\ + \ for: seed — начальное значение, hasNext определяет остановку, next генерирует\ + \ следующее значение." +whyModernWins: +- icon: 🎯 + title: Естественная остановка + desc: Остановка по условию, а не по произвольному ограничению. +- icon: 📐 + title: Эквивалент цикла for + desc: Та же семантика, что у for(seed; hasNext; next). +- icon: 🛡️ + title: Нет риска бесконечного Stream + desc: Предикат гарантирует завершение. +support: + description: Широко доступно с JDK 9 (сент. 2017) diff --git a/translations/content/ru/streams/stream-mapmulti.yaml b/translations/content/ru/streams/stream-mapmulti.yaml new file mode 100644 index 0000000..b8c0b41 --- /dev/null +++ b/translations/content/ru/streams/stream-mapmulti.yaml @@ -0,0 +1,20 @@ +title: Stream.mapMulti() +oldApproach: flatMap + List +modernApproach: mapMulti() +summary: Вывод нуля или более элементов на входной элемент без создания промежуточных + Stream. +explanation: mapMulti() — императивная альтернатива flatMap, которая избегает создания + промежуточных объектов Stream для каждого элемента. Эффективнее, когда отображение + порождает небольшое количество элементов. +whyModernWins: +- icon: ⚡ + title: Меньше аллокаций + desc: Не создаётся промежуточный Stream для каждого элемента. +- icon: 🎯 + title: Императивный стиль + desc: Использование циклов и условий напрямую. +- icon: 📐 + title: Гибкий + desc: Вывод нуля, одного или нескольких элементов с полным контролем. +support: + description: Широко доступно с JDK 16 (март 2021) diff --git a/translations/content/ru/streams/stream-of-nullable.yaml b/translations/content/ru/streams/stream-of-nullable.yaml new file mode 100644 index 0000000..a6efe03 --- /dev/null +++ b/translations/content/ru/streams/stream-of-nullable.yaml @@ -0,0 +1,19 @@ +title: Stream.ofNullable() +oldApproach: Проверка на null +modernApproach: ofNullable() +summary: Создание Stream с нулём или одним элементом из nullable-значения. +explanation: Stream.ofNullable() возвращает однопоточный Stream, если значение не + null, или пустой Stream, если оно null. Устраняет паттерн тернарной проверки на + null. +whyModernWins: +- icon: 📏 + title: Краткость + desc: Один вызов заменяет тернарное выражение. +- icon: 🔗 + title: flatMap-совместимый + desc: Отлично подходит внутри flatMap для пропуска null-значений. +- icon: 🛡️ + title: Null-безопасный + desc: Нет риска NPE — null превращается в пустой Stream. +support: + description: Широко доступно с JDK 9 (сент. 2017) diff --git a/translations/content/ru/streams/stream-takewhile-dropwhile.yaml b/translations/content/ru/streams/stream-takewhile-dropwhile.yaml new file mode 100644 index 0000000..81d4fd4 --- /dev/null +++ b/translations/content/ru/streams/stream-takewhile-dropwhile.yaml @@ -0,0 +1,19 @@ +title: Stream takeWhile / dropWhile +oldApproach: Ручной цикл +modernApproach: takeWhile/dropWhile +summary: Брать или пропускать элементы из Stream на основе предиката. +explanation: takeWhile() возвращает элементы, пока предикат истинен, и останавливается + при первом ложном значении. dropWhile() пропускает элементы, пока предикат истинен, + и возвращает остальные. Оба лучше работают с упорядоченными Stream. +whyModernWins: +- icon: 🎯 + title: Оценка с коротким замыканием + desc: Прекращает обработку, как только предикат не выполняется. +- icon: 🔗 + title: Совместимый с пайплайном + desc: Естественно связывается с другими операциями Stream. +- icon: 📖 + title: Декларативный + desc: "takeWhile читается как естественный язык: 'брать, пока меньше 100'." +support: + description: Широко доступно с JDK 9 (сент. 2017) diff --git a/translations/content/ru/streams/stream-tolist.yaml b/translations/content/ru/streams/stream-tolist.yaml new file mode 100644 index 0000000..947504e --- /dev/null +++ b/translations/content/ru/streams/stream-tolist.yaml @@ -0,0 +1,19 @@ +title: Stream.toList() +oldApproach: Collectors.toList() +modernApproach: .toList() +summary: Терминальный toList() заменяет многословный collect(Collectors.toList()). +explanation: "Stream.toList() возвращает неизменяемый список. Это эквивалент\ + \ .collect(Collectors.toUnmodifiableList()), но гораздо короче. Примечание: результат\ + \ неизменяем, в отличие от Collectors.toList()." +whyModernWins: +- icon: 📏 + title: 7 символов против 24 + desc: .toList() заменяет .collect(Collectors.toList()). +- icon: 🔒 + title: Неизменяемый + desc: Результирующий список нельзя изменить. +- icon: 📖 + title: Fluent + desc: Читается естественно в конце пайплайна. +support: + description: Широко доступно с JDK 16 (март 2021) diff --git a/translations/content/ru/streams/virtual-thread-executor.yaml b/translations/content/ru/streams/virtual-thread-executor.yaml new file mode 100644 index 0000000..0fcf38e --- /dev/null +++ b/translations/content/ru/streams/virtual-thread-executor.yaml @@ -0,0 +1,20 @@ +title: Исполнитель виртуальных потоков +oldApproach: Фиксированный пул потоков +modernApproach: Virtual-Thread-Executor +summary: Использование исполнителей виртуальных потоков для неограниченного лёгковесного + параллелизма. +explanation: Исполнитель виртуальных потоков создаёт новый виртуальный поток для + каждой задачи. Не нужно задавать размер пула — виртуальные потоки достаточно дёшевы, + чтобы создавать их миллионами. +whyModernWins: +- icon: ♾️ + title: Без настройки размера + desc: Не нужно задавать размер пула — создавать столько потоков, сколько нужно. +- icon: ⚡ + title: Лёгковесный + desc: Виртуальные потоки используют КБ вместо МБ памяти. +- icon: 🧹 + title: Auto-closeable + desc: try-with-resources автоматически выполняет завершение работы. +support: + description: Широко доступно с JDK 21 LTS (сент. 2023) diff --git a/translations/content/ru/strings/string-chars-stream.yaml b/translations/content/ru/strings/string-chars-stream.yaml new file mode 100644 index 0000000..1a90bce --- /dev/null +++ b/translations/content/ru/strings/string-chars-stream.yaml @@ -0,0 +1,19 @@ +title: Символы строки как поток +oldApproach: Ручной цикл +modernApproach: Поток chars() +summary: Обработка символов строки в виде конвейера потока. +explanation: String.chars() возвращает IntStream значений символов, обеспечивая + функциональную обработку. Для поддержки Unicode метод codePoints() корректно + обрабатывает дополнительные символы. +whyModernWins: +- icon: 🔗 + title: Цепочки вызовов + desc: Применяйте filter, map, collect к потокам символов. +- icon: 📐 + title: Декларативный стиль + desc: Описывает что делать, а не как итерировать. +- icon: 🌐 + title: Поддержка Unicode + desc: codePoints() корректно обрабатывает эмодзи и дополнительные символы. +support: + description: Доступно с JDK 8+ (улучшено в 9+) diff --git a/translations/content/ru/strings/string-formatted.yaml b/translations/content/ru/strings/string-formatted.yaml new file mode 100644 index 0000000..944aaec --- /dev/null +++ b/translations/content/ru/strings/string-formatted.yaml @@ -0,0 +1,19 @@ +title: String.formatted() +oldApproach: String.format() +modernApproach: formatted() +summary: Вызов formatted() непосредственно на строке-шаблоне. +explanation: String.formatted() — это метод экземпляра, аналогичный String.format(), + но вызываемый на строке форматирования. Он читается более естественно в потоке + слева направо. +whyModernWins: +- icon: 📖 + title: Естественная читаемость + desc: "Template.formatted(args) воспринимается лучше, чем String.format(template, args)." +- icon: 🔗 + title: Цепочки вызовов + desc: Можно объединять в цепочку с другими строковыми методами. +- icon: 📏 + title: Меньше кода + desc: Устраняет избыточный статический вызов String.format(). +support: + description: Широко доступно с JDK 15 (сентябрь 2020) diff --git a/translations/content/ru/strings/string-indent-transform.yaml b/translations/content/ru/strings/string-indent-transform.yaml new file mode 100644 index 0000000..ba358b5 --- /dev/null +++ b/translations/content/ru/strings/string-indent-transform.yaml @@ -0,0 +1,19 @@ +title: String.indent() и transform() +oldApproach: Ручное добавление отступа +modernApproach: indent() / transform() +summary: Добавление отступов к тексту и цепочечное применение строковых преобразований. +explanation: indent(n) добавляет n пробелов к каждой строке. transform(fn) применяет + произвольную функцию и возвращает результат, позволяя выстраивать цепочки + строковых операций. +whyModernWins: +- icon: 📏 + title: Встроенный метод + desc: Добавление отступов — частая операция, теперь это один вызов. +- icon: 🔗 + title: Цепочки вызовов + desc: transform() позволяет строить цепочки операций над строками. +- icon: 🧹 + title: Чистый код + desc: Не нужно вручную разбивать строки и писать циклы с StringBuilder. +support: + description: Широко доступно с JDK 12 (март 2019) diff --git a/translations/content/ru/strings/string-isblank.yaml b/translations/content/ru/strings/string-isblank.yaml new file mode 100644 index 0000000..78c449c --- /dev/null +++ b/translations/content/ru/strings/string-isblank.yaml @@ -0,0 +1,18 @@ +title: String.isBlank() +oldApproach: trim().isEmpty() +modernApproach: isBlank() +summary: Проверка пустых строк одним вызовом метода. +explanation: isBlank() возвращает true, если строка пуста или содержит только + пробельные символы, включая Unicode-пробелы, которые trim() не учитывает. +whyModernWins: +- icon: 📖 + title: Самодокументирующийся + desc: isBlank() точно описывает то, что проверяет. +- icon: 🌐 + title: Поддержка Unicode + desc: Обрабатывает все пробельные символы Unicode, а не только ASCII. +- icon: ⚡ + title: Без аллокаций + desc: Не создаёт промежуточную обрезанную строку. +support: + description: Широко доступно с JDK 11 (сентябрь 2018) diff --git a/translations/content/ru/strings/string-lines.yaml b/translations/content/ru/strings/string-lines.yaml new file mode 100644 index 0000000..c56535e --- /dev/null +++ b/translations/content/ru/strings/string-lines.yaml @@ -0,0 +1,21 @@ +title: String.lines() для разбиения на строки +oldApproach: "split(\"\\\\n\")" +modernApproach: lines() +summary: Использование String.lines() для разбиения текста на поток строк без + издержек регулярных выражений. +explanation: "String.lines() возвращает Stream строк, разбитых по \\n,\ + \ \\r или \\r\\n. Это ленивее и эффективнее, чем split(), избегает компиляции\ + \ регулярных выражений и естественно интегрируется в Stream API для дальнейшей\ + \ обработки." +whyModernWins: +- icon: ⚡ + title: Ленивый поток + desc: Строки генерируются по запросу, а не все сразу, как в split(). +- icon: 🔧 + title: Универсальные разделители строк + desc: "Автоматически обрабатывает \\n, \\r и \\r\\n без регулярных выражений." +- icon: 🔗 + title: Интеграция со Stream + desc: Возвращает поток для прямого использования с filter, map, collect. +support: + description: Доступно с JDK 11 (сентябрь 2018). diff --git a/translations/content/ru/strings/string-repeat.yaml b/translations/content/ru/strings/string-repeat.yaml new file mode 100644 index 0000000..49c4fde --- /dev/null +++ b/translations/content/ru/strings/string-repeat.yaml @@ -0,0 +1,19 @@ +title: String.repeat() +oldApproach: Цикл с StringBuilder +modernApproach: repeat() +summary: Повторение строки n раз без цикла. +explanation: "String.repeat(int) возвращает строку, объединённую с собой n раз.\ + \ Обрабатывает граничные случаи: repeat(0) возвращает пустую строку, repeat(1)\ + \ возвращает ту же строку." +whyModernWins: +- icon: 📏 + title: Однострочник + desc: Заменяет 5 строк кода с StringBuilder одним вызовом. +- icon: ⚡ + title: Оптимизировано + desc: Внутренняя реализация оптимизирована для большого числа повторений. +- icon: 📖 + title: Ясное намерение + desc: repeat(3) сразу передаёт цель. +support: + description: Широко доступно с JDK 11 (сентябрь 2018) diff --git a/translations/content/ru/strings/string-strip.yaml b/translations/content/ru/strings/string-strip.yaml new file mode 100644 index 0000000..90a3461 --- /dev/null +++ b/translations/content/ru/strings/string-strip.yaml @@ -0,0 +1,20 @@ +title: String.strip() vs trim() +oldApproach: trim() +modernApproach: strip() +summary: Unicode-совместимое удаление пробелов с помощью strip(), stripLeading(), + stripTrailing(). +explanation: trim() удаляет только символы ≤ U+0020 (управляющие символы ASCII + и пробел). strip() использует Character.isWhitespace(), который обрабатывает + Unicode-пробелы, такие как неразрывный пробел, идеографический пробел и др. +whyModernWins: +- icon: 🌐 + title: Корректная работа с Unicode + desc: Обрабатывает все пробельные символы из всех систем письма. +- icon: 🎯 + title: Направленность + desc: stripLeading() и stripTrailing() для одностороннего удаления. +- icon: 🛡️ + title: Меньше ошибок + desc: Нет неожиданных пробелов в интернациональном тексте. +support: + description: Широко доступно с JDK 11 (сентябрь 2018) diff --git a/translations/content/ru/tooling/aot-class-preloading.yaml b/translations/content/ru/tooling/aot-class-preloading.yaml new file mode 100644 index 0000000..a5501fb --- /dev/null +++ b/translations/content/ru/tooling/aot-class-preloading.yaml @@ -0,0 +1,20 @@ +title: AOT-предзагрузка классов +oldApproach: Холодный старт при каждом запуске +modernApproach: AOT-кэш +summary: Кэширование загрузки и компиляции классов для мгновенного запуска. +explanation: AOT-предзагрузка классов сохраняет загруженные и связанные классы из + обучающего прогона в кэш. При последующих запусках классы загружаются из кэша, + пропуская этапы верификации и компоновки. В сочетании с AOT-компиляцией это + резко сокращает время запуска. +whyModernWins: +- icon: ⚡ + title: Более быстрый запуск + desc: Пропуск загрузки, верификации и компоновки классов. +- icon: 📦 + title: Кэшированное состояние + desc: Обучающий прогон фиксирует оптимальное состояние классов. +- icon: 🔧 + title: Без изменений в коде + desc: Работает с существующими приложениями — достаточно добавить флаги JVM. +support: + description: Доступно как стандартная функция в JDK 25 LTS (JEP 514/515, сентябрь 2025). diff --git a/translations/content/ru/tooling/built-in-http-server.yaml b/translations/content/ru/tooling/built-in-http-server.yaml new file mode 100644 index 0000000..4ee600d --- /dev/null +++ b/translations/content/ru/tooling/built-in-http-server.yaml @@ -0,0 +1,25 @@ +title: Встроенный HTTP-сервер +oldApproach: Внешний сервер / фреймворк +modernApproach: CLI jwebserver +summary: Java 18 включает встроенный минималистичный HTTP-сервер для прототипирования + и раздачи файлов. +explanation: В JDK 18 добавлен простой HTTP-сервер файлов без зависимостей, доступный + через инструмент командной строки jwebserver или API SimpleFileServer. Он раздаёт + статические файлы из указанного каталога без какой-либо конфигурации. CLI-инструмент + идеально подходит для быстрого прототипирования, тестирования и ситуативного + обмена файлами — внешние зависимости или фреймворки не нужны. API позволяет + использовать его программно с настраиваемыми обработчиками и уровнями вывода. +whyModernWins: +- icon: 🚀 + title: Никакой настройки + desc: "Запустите jwebserver в любом каталоге — установка, конфигурация и зависимости\ + \ не нужны." +- icon: 📦 + title: Встроен в JDK + desc: Поставляется с каждой установкой JDK 18+, всегда доступен на любой машине с Java. +- icon: 🧪 + title: Идеально для прототипирования + desc: Мгновенная раздача статических файлов для тестирования HTML, API или + фронтенд-разработки. +support: + description: Доступно начиная с JDK 18 (март 2022) diff --git a/translations/content/ru/tooling/compact-object-headers.yaml b/translations/content/ru/tooling/compact-object-headers.yaml new file mode 100644 index 0000000..ba51517 --- /dev/null +++ b/translations/content/ru/tooling/compact-object-headers.yaml @@ -0,0 +1,21 @@ +title: Компактные заголовки объектов +oldApproach: 128-битный заголовок +modernApproach: 64-битный заголовок +summary: Уменьшение размера заголовков объектов вдвое для повышения плотности + хранения и эффективности кэша. +explanation: Компактные заголовки объектов сокращают накладные расходы на каждый + объект со 128 бит до 64 бит на 64-битных платформах. Это экономит память и + улучшает использование кэша, особенно для приложений с большим количеством + небольших объектов. +whyModernWins: +- icon: 📦 + title: "Заголовки меньше на 50\u202F%" + desc: 8 байт вместо 16 на каждый объект. +- icon: ⚡ + title: Лучшее использование кэша + desc: Больше объектов помещается в строки кэша процессора. +- icon: 📊 + title: Более высокая плотность + desc: Больше объектов в том же объёме кучи. +support: + description: Финализировано в JDK 25 LTS (JEP 519, сентябрь 2025). diff --git a/translations/content/ru/tooling/jfr-profiling.yaml b/translations/content/ru/tooling/jfr-profiling.yaml new file mode 100644 index 0000000..36f2d92 --- /dev/null +++ b/translations/content/ru/tooling/jfr-profiling.yaml @@ -0,0 +1,23 @@ +title: JFR для профилирования +oldApproach: Внешний профилировщик +modernApproach: Java Flight Recorder +summary: Профилирование любого Java-приложения встроенным Flight Recorder — без + внешних инструментов. +explanation: Java Flight Recorder (JFR) — инструмент профилирования, встроенный + в JVM с минимальными накладными расходами. Он фиксирует события CPU, памяти, + GC, ввода-вывода, потоков и пользовательские события с минимальным влиянием + на производительность (~1%). +whyModernWins: +- icon: 🆓 + title: Встроен + desc: Никаких внешних профилировщиков для установки или лицензирования. +- icon: ⚡ + title: Минимальные накладные расходы + desc: "~1\u202F% влияния на производительность — безопасно для использования\ + \ в продакшене." +- icon: 📊 + title: Богатый набор событий + desc: "CPU, память, GC, потоки, ввод-вывод, блокировки и пользовательские\ + \ события." +support: + description: Широко доступно начиная с JDK 9/11 (открытый исходный код с версии 11) diff --git a/translations/content/ru/tooling/jshell-prototyping.yaml b/translations/content/ru/tooling/jshell-prototyping.yaml new file mode 100644 index 0000000..306226d --- /dev/null +++ b/translations/content/ru/tooling/jshell-prototyping.yaml @@ -0,0 +1,20 @@ +title: JShell для прототипирования +oldApproach: Создать файл + скомпилировать + запустить +modernApproach: REPL JShell +summary: Интерактивное тестирование выражений Java без создания файлов. +explanation: JShell — это цикл чтения-вычисления-вывода (REPL) для Java. Тестируйте + выражения, исследуйте API и прототипируйте код, не создавая файлы, не компилируя + и не пишу метод main. Автодополнение по Tab и контекстная документация уже + включены. +whyModernWins: +- icon: ⚡ + title: Мгновенная обратная связь + desc: Введите выражение и сразу увидите результат. +- icon: 📝 + title: Файлы не нужны + desc: Никаких .java-файлов, никакого шага компиляции. +- icon: 🔍 + title: Изучение API + desc: Автодополнение по Tab помогает обнаруживать методы и параметры. +support: + description: Широко доступно начиная с JDK 9 (сентябрь 2017) diff --git a/translations/content/ru/tooling/junit6-with-jspecify.yaml b/translations/content/ru/tooling/junit6-with-jspecify.yaml new file mode 100644 index 0000000..a41a863 --- /dev/null +++ b/translations/content/ru/tooling/junit6-with-jspecify.yaml @@ -0,0 +1,29 @@ +title: JUnit 6 с типобезопасностью JSpecify по null +oldApproach: API без аннотаций +modernApproach: "@NullMarked API" +summary: "JUnit 6 принимает @NullMarked из JSpecify, делая null-контракты явными\ + \ во всём Assertion API." +explanation: "JUnit 5 поставлялся без стандартизированных аннотаций допустимости null,\ + \ поэтому разработчики вынуждены были догадываться, могут ли параметры или\ + \ возвращаемые значения методов утверждения быть null. JUnit 6 принимает JSpecify\ + \ для всего своего модуля: аннотация @NullMarked делает все неаннотированные\ + \ типы ненулевыми по умолчанию, а @Nullable отмечает исключения. Класс Assertions\ + \ явно аннотирует параметры — например, assertNull(@Nullable Object actual) и\ + \ fail(@Nullable String message), — благодаря чему IDE и статические анализаторы,\ + \ такие как NullAway и Error Prone, могут обнаруживать ошибки работы с null во\ + \ время компиляции, а не во время выполнения." +whyModernWins: +- icon: 📜 + title: Явные контракты + desc: "@NullMarked в модуле JUnit 6 документирует семантику null прямо в API\ + \ — читать исходный код не нужно." +- icon: 🛡️ + title: Безопасность на этапе компиляции + desc: IDE и анализаторы предупреждают при передаче null туда, где ожидается + ненулевое значение, выявляя ошибки до запуска тестов. +- icon: 🌐 + title: Стандарт экосистемы + desc: JSpecify принят Spring, Guava и другими библиотеками — единая семантика + null во всём стеке. +support: + description: Доступно начиная с JUnit 6.0 (октябрь 2025, требуется Java 17+) diff --git a/translations/content/ru/tooling/multi-file-source.yaml b/translations/content/ru/tooling/multi-file-source.yaml new file mode 100644 index 0000000..63e63cd --- /dev/null +++ b/translations/content/ru/tooling/multi-file-source.yaml @@ -0,0 +1,19 @@ +title: Запуск многофайлового исходного кода +oldApproach: Сначала скомпилировать всё +modernApproach: Запуск исходного кода +summary: Запуск многофайловых программ без явного шага компиляции. +explanation: Java 22+ может автоматически компилировать при запуске связанные + исходные файлы из .java-файла. Это делает небольшие многофайловые программы + такими же простыми в запуске, как скрипты, без необходимости в Maven или Gradle. +whyModernWins: +- icon: 🚀 + title: Никакой настройки + desc: Инструменты сборки для небольших многофайловых программ не нужны. +- icon: 🔗 + title: Автоматическое разрешение зависимостей + desc: Используемые классы находятся и компилируются автоматически. +- icon: 📝 + title: Похоже на скрипт + desc: Запускайте многофайловые программы как скрипты. +support: + description: Доступно начиная с JDK 22 (март 2024) diff --git a/translations/content/ru/tooling/single-file-execution.yaml b/translations/content/ru/tooling/single-file-execution.yaml new file mode 100644 index 0000000..812e156 --- /dev/null +++ b/translations/content/ru/tooling/single-file-execution.yaml @@ -0,0 +1,20 @@ +title: Выполнение однофайловых программ +oldApproach: Двухэтапная компиляция +modernApproach: Прямой запуск +summary: Запуск однофайловых Java-программ напрямую без javac. +explanation: Лаунчер Java может скомпилировать и выполнить один исходный файл + одной командой. В сочетании с поддержкой shebang в Unix Java-файлы могут + работать как скрипты. Отдельный шаг компиляции не нужен. +whyModernWins: +- icon: ⚡ + title: Одна команда + desc: "java File.java компилирует и выполняет за один шаг." +- icon: 📝 + title: Похоже на скрипт + desc: Добавьте строку shebang, чтобы превратить .java-файлы в исполняемые + скрипты. +- icon: 🎓 + title: Удобно для обучения + desc: Новички могут сразу запускать код, не изучая инструменты сборки. +support: + description: Широко доступно начиная с JDK 11 (сентябрь 2018) diff --git a/translations/content/tr/collections/copying-collections-immutably.yaml b/translations/content/tr/collections/copying-collections-immutably.yaml index a38954b..58e0c67 100644 --- a/translations/content/tr/collections/copying-collections-immutably.yaml +++ b/translations/content/tr/collections/copying-collections-immutably.yaml @@ -12,7 +12,7 @@ whyModernWins: title: "Tek çağrı" desc: "Elle ArrayList oluşturma ve sarmalama gerekmez." - icon: "🛡️" - title: "Savunmacı kopya" - desc: "Orijinaldeki değişiklikler kopyayı etkilemez." + title: "Her Collection" + desc: "Herhangi bir Collection'ı girdi olarak kabul eder—ara ArrayList dönüşümü gerekmez." support: description: JDK 10'dan itibaren geniş çapta kullanılabilir (Mart 2018) diff --git a/translations/content/tr/collections/stream-toarray-typed.yaml b/translations/content/tr/collections/stream-toarray-typed.yaml index 6486725..1c11267 100644 --- a/translations/content/tr/collections/stream-toarray-typed.yaml +++ b/translations/content/tr/collections/stream-toarray-typed.yaml @@ -1,6 +1,6 @@ --- title: Tiplendirilmiş stream toArray -oldApproach: Elle Dizi Kopyalama +oldApproach: Elle Filtreleme + Kopyalama modernApproach: toArray(generator) summary: "Stream'leri metot referansıyla tiplendirilmiş dizilere dönüştürün." explanation: "toArray(IntFunction) metodu, bir stream'den düzgün tiplendirilmiş bir dizi oluşturur. Üretici (String[]::new), stream'e hangi türde dizi oluşturacağını söyler." diff --git a/translations/content/zh-CN/collections/copying-collections-immutably.yaml b/translations/content/zh-CN/collections/copying-collections-immutably.yaml index f4f81a6..6f51120 100644 --- a/translations/content/zh-CN/collections/copying-collections-immutably.yaml +++ b/translations/content/zh-CN/collections/copying-collections-immutably.yaml @@ -12,7 +12,7 @@ whyModernWins: title: 真正不可变 desc: "结果集合结构上不可修改。" - icon: 📏 - title: 简洁 - desc: "一行代码替代多行手动复制和包装。" + title: 支持任意 Collection + desc: "接受任意 Collection 作为输入,无需中间转换为 ArrayList。" support: description: 自 JDK 10 起广泛可用(2018 年 3 月) diff --git a/translations/content/zh-CN/collections/stream-toarray-typed.yaml b/translations/content/zh-CN/collections/stream-toarray-typed.yaml index 66471fe..dab29dd 100644 --- a/translations/content/zh-CN/collections/stream-toarray-typed.yaml +++ b/translations/content/zh-CN/collections/stream-toarray-typed.yaml @@ -1,6 +1,6 @@ --- title: 类型化流 toArray -oldApproach: 手动数组复制 +oldApproach: 手动过滤 + 复制 modernApproach: toArray(generator) summary: "使用方法引用将流转换为类型化数组。" explanation: "toArray(IntFunction) 方法从流创建正确类型化的数组。传递 String[]::new 获取 String[],而非 Object[]。" diff --git a/translations/strings/ru.yaml b/translations/strings/ru.yaml new file mode 100644 index 0000000..98765b1 --- /dev/null +++ b/translations/strings/ru.yaml @@ -0,0 +1,95 @@ +site: + title: java.evolved + tagline: Java эволюционировал. Ваш код тоже может. + tagline_line1: Java эволюционировал. + tagline_line2: Ваш код тоже может. + description: Коллекция современных фрагментов кода на Java. Каждый устаревший шаблон + рядом с его чистой, современной заменой — бок о бок. + heroSnippetCount: ✦ {{snippetCount}} современных шаблонов · Java 8 → Java 25 + heroOld: Старый + heroModern: Современный + allComparisons: Все сравнения + snippetsBadge: '{{snippetCount}} сниппетов' +nav: + allPatterns: ← Все шаблоны + toggleTheme: Переключить тему + viewOnGitHub: Посмотреть на GitHub + selectLanguage: Выбрать язык +breadcrumb: + home: Главная +sections: + codeComparison: Сравнение кода + whyModernWins: Почему современный подход лучше + oldApproach: Старый подход + modernApproach: Современный подход + sinceJdk: Начиная с JDK + difficulty: Сложность + jdkSupport: Поддержка JDK + howItWorks: Как это работает + relatedDocs: Связанная документация + proof: Доказательство + proofLink: Посмотреть исходник доказательства + relatedPatterns: Похожие шаблоны +filters: + show: 'Показать:' + all: Все + jdk: 'JDK:' + category: 'Категория:' + noResults: Шаблоны кода для этого фильтра не найдены. +difficulty: + beginner: Начинающий + intermediate: Средний + advanced: Продвинутый +search: + placeholder: Поиск сниппетов… + noResults: Результаты не найдены. + esc: ESC + searchTrigger: Поиск… + navigate: навигация + open: открыть + close: закрыть +cards: + old: Старый + modern: Современный + hoverHint: наведите, чтобы увидеть современный → + hoverHintRelated: Наведите, чтобы увидеть современный ➜ + touchHint: 👆 нажмите или смахните → + learnMore: подробнее +copy: + copy: Копировать + copied: Скопировано! +share: + label: Поделиться +view: + expandAll: Развернуть все + collapseAll: Свернуть все +stats: + modernPatterns: Современных шаблонов + jdkVersions: Охваченных версий JDK + categories: Категорий + linesOfPython: Строк Python кода понадобилось бы +footer: + tagline: Java эволюционировал. Ваш код тоже может. + madeWith: Сделано с ❤️ + and: и + inspiredBy: Вдохновлено + viewOnGitHub: Посмотреть на GitHub +copilot: + headline: Модернизируйте вашу кодовую базу Java с GitHub Copilot. + description: Позвольте Copilot помочь вам перенести устаревшие шаблоны на современный Java — автоматически. + appModernization: Модернизация приложений → + javaGuide: Руководство по Java → +support: + available: Доступно + preview: Предварительный просмотр + experimental: Экспериментально +contribute: + button: Внести вклад + codeIssue: Сообщить о проблеме в коде + translationIssue: Сообщить о проблеме с переводом + suggestPattern: Предложить новый шаблон + seeIssue: "Заметили проблему в этом коде?" + reportIt: "Сообщите нам." +untranslated: + notice: Эта страница ещё не переведена на {{localeName}}. + viewInEnglish: Просмотреть на английском