├── .editorconfig ├── .gitattributes ├── .github ├── images │ ├── fireplace-dark.png │ └── fireplace-light.png ├── renovate.json5 └── workflows │ ├── build.yml │ └── release.yaml ├── .gitignore ├── HEADER ├── LICENSE ├── README.adoc ├── build-logic ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── Utils.kt │ ├── fireplace.application.gradle.kts │ ├── fireplace.java-library.gradle.kts │ ├── fireplace.licence-report.gradle.kts │ ├── fireplace.local-eclipse-swt-platform.gradle.kts │ ├── fireplace.maven-publication.gradle.kts │ ├── fireplace.published-java-library.gradle.kts │ └── fireplace.tests.gradle.kts ├── build.gradle.kts ├── fireplace-app ├── build.gradle.kts └── src │ └── main │ ├── java │ └── io │ │ └── github │ │ └── bric3 │ │ └── fireplace │ │ ├── icons │ │ ├── GeneratedIcon.java │ │ ├── darkMode_moon.java │ │ └── darkMode_sun.java │ │ └── ui │ │ └── debug │ │ ├── AssertiveRepaintManager.java │ │ ├── CheckThreadViolationRepaintManager.java │ │ └── EventDispatchThreadHangMonitor.java │ ├── kotlin │ └── io │ │ └── github │ │ └── bric3 │ │ └── fireplace │ │ ├── FireplaceMain.kt │ │ ├── Utils.kt │ │ ├── appDebug │ │ ├── FireplaceAppSystemProperties.kt │ │ └── FireplaceAppUIManagerProperties.kt │ │ ├── charts │ │ ├── Chart.kt │ │ ├── ChartComponent.kt │ │ ├── ChartDataset.kt │ │ ├── ChartRenderer.kt │ │ ├── ChartSpecification.kt │ │ ├── ChartUtils.kt │ │ ├── LineChartRenderer.kt │ │ ├── Range.kt │ │ ├── RectangleContent.kt │ │ ├── RectangleMargin.kt │ │ ├── ToolTipComponentContributor.kt │ │ ├── XYDataset.kt │ │ └── XYPercentageDataset.kt │ │ ├── jfr │ │ ├── ProfileContentPanel.kt │ │ ├── support │ │ │ ├── JFRLoaderBinder.kt │ │ │ ├── JfrAnalyzer.kt │ │ │ ├── JfrFilesDropHandler.kt │ │ │ ├── JfrFrameColorMode.kt │ │ │ ├── JfrFrameNodeConverter.kt │ │ │ └── JfrUtils.kt │ │ └── views │ │ │ ├── cpu │ │ │ └── MethodCpuSample.kt │ │ │ ├── events │ │ │ ├── EventBrowser.kt │ │ │ ├── EventTypesByCategoryTreeModel.kt │ │ │ ├── EventsTableModel.kt │ │ │ ├── SingleEventAttributesTableModel.kt │ │ │ └── StackFrameTableModel.kt │ │ │ ├── general │ │ │ ├── NativeLibraries.kt │ │ │ └── SystemProperties.kt │ │ │ └── memory │ │ │ └── Allocations.kt │ │ └── ui │ │ ├── FlamegraphPane.kt │ │ ├── ThreadFlamegraphView.kt │ │ ├── ViewPanel.kt │ │ └── toolkit │ │ ├── AppearanceControl.kt │ │ ├── ColorIcon.kt │ │ ├── DragAndDropTarget.kt │ │ ├── FollowingTipService.kt │ │ ├── FrameResizeLabel.kt │ │ ├── Hud.kt │ │ ├── JPanelWithPainter.kt │ │ ├── Painter.kt │ │ ├── SwingUtils.kt │ │ ├── Tables.kt │ │ ├── TitleBar.kt │ │ ├── ToolTipListener.kt │ │ └── UIUtil.kt │ └── resources │ ├── dardMode-moon.svg │ ├── dardMode-sun.svg │ ├── fire.png │ └── fire.pxm ├── fireplace-swing-animation ├── build.gradle.kts └── src │ └── main │ └── java │ └── io │ └── github │ └── bric3 │ └── fireplace │ └── flamegraph │ └── animation │ └── ZoomAnimation.java ├── fireplace-swing ├── build.gradle.kts └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── bric3 │ │ │ └── fireplace │ │ │ ├── core │ │ │ └── ui │ │ │ │ ├── Colors.java │ │ │ │ ├── JScrollPaneWithBackButton.java │ │ │ │ ├── LightDarkColor.java │ │ │ │ ├── MouseInputListenerWorkaroundForToolTipEnabledComponent.java │ │ │ │ ├── StringClipper.java │ │ │ │ ├── SwingUtils.java │ │ │ │ └── package-info.java │ │ │ └── flamegraph │ │ │ ├── ColorMapper.java │ │ │ ├── DefaultFrameRenderer.java │ │ │ ├── DimmingFrameColorProvider.java │ │ │ ├── FlamegraphImage.java │ │ │ ├── FlamegraphRenderEngine.java │ │ │ ├── FlamegraphView.java │ │ │ ├── FrameBox.java │ │ │ ├── FrameColorProvider.java │ │ │ ├── FrameFontProvider.java │ │ │ ├── FrameModel.java │ │ │ ├── FrameRenderer.java │ │ │ ├── FrameRenderingFlags.java │ │ │ ├── FrameTextsProvider.java │ │ │ ├── ZoomTarget.java │ │ │ └── package-info.java │ └── javadoc │ │ └── overview.html │ └── test │ ├── java │ └── io │ │ └── github │ │ └── bric3 │ │ └── fireplace │ │ └── flamegraph │ │ ├── FlamegraphImageTest.java │ │ ├── FlamegraphViewTest.java │ │ └── ImageTestUtils.java │ └── resources │ ├── fg-ak-200x72-gha-linux.png │ ├── fg-ak-200x72-gha-linux.svg │ ├── fg-ak-200x72-macOs.png │ └── fg-ak-200x72-macOs.svg ├── fireplace-swt-awt-bridge ├── build.gradle.kts └── src │ └── main │ └── java │ └── io │ └── github │ └── bric3 │ └── fireplace │ └── swt_awt │ ├── EmbeddingComposite.java │ ├── SWTKeyLogger.java │ └── SWT_AWTBridge.java ├── fireplace-swt-experiment-app ├── build.gradle.kts └── src │ └── main │ └── java │ └── io │ └── github │ └── bric3 │ └── fireplace │ └── swt │ ├── FirePlaceSwtMain.java │ └── StyledToolTip.java ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.github/images/fireplace-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bric3/fireplace/6f004162f361b2310a52064849698205414256d3/.github/images/fireplace-dark.png -------------------------------------------------------------------------------- /.github/images/fireplace-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bric3/fireplace/6f004162f361b2310a52064849698205414256d3/.github/images/fireplace-light.png -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | // Options doc https://docs.renovatebot.com/configuration-options/ 2 | // validate with 3 | // docker run --mount type=bind,source=$(pwd)/.github/renovate.json5,target=/usr/src/app/renovate.json5,readonly -it renovate/renovate renovate-config-validator 4 | { 5 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 6 | "extends": [ 7 | "config:base" 8 | ], 9 | "labels": [ 10 | "dependency-update" 11 | ], 12 | // automerge minor deps 13 | "packageRules": [ 14 | { 15 | "description": "Automatically merge minor and patch-level updates", 16 | "matchUpdateTypes": [ 17 | // where version is: major.minor.patch 18 | "minor", 19 | "patch" 20 | ], 21 | "automerge": true, 22 | // Do not create a PR to avoid PR-related email spam, if tests succeed merge directly 23 | // otherwise make a PR if tests fail 24 | "automergeType": "branch" 25 | } 26 | ], 27 | "vulnerabilityAlerts": { 28 | "description": "Automatically merge vulnerability fixes", 29 | "labels": [ 30 | "vulnerability-fix" 31 | ], 32 | "automerge": true, 33 | }, 34 | "dependencyDashboard": true, 35 | "prConcurrentLimit": 10, 36 | "prHourlyLimit": 5, 37 | // Schedule the bot to run before morning 38 | "timezone": "UTC", 39 | "schedule": [ 40 | "before 9am" 41 | // "before 9am on monday" // once a week before monday morning 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time 6 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 7 | 8 | name: Java CI with Gradle 9 | 10 | on: 11 | push: 12 | branches: [ master ] 13 | pull_request: 14 | branches: [ master ] 15 | 16 | concurrency: 17 | group: ci-tests-${{ github.ref }}-1 18 | cancel-in-progress: true 19 | 20 | env: 21 | # https://docs.github.com/en/developers/webhooks-and-events/webhook-events-and-payloads#pull_request 22 | GITHUB_PR_NUMBER: ${{github.event.pull_request.number}} 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 0 31 | 32 | # - name: debug 33 | # run: echo "${{ toJSON(github.event)}}" 34 | 35 | - name: Set up JDK 36 | uses: actions/setup-java@v4 37 | with: 38 | java-version: '21' 39 | distribution: 'zulu' 40 | 41 | - name: Setup Gradle 42 | id: setup-gradle 43 | uses: gradle/actions/setup-gradle@v4 44 | 45 | - name: Build with Gradle 46 | run: ./gradlew build --stacktrace 47 | 48 | - name: Upload build reports 49 | uses: actions/upload-artifact@v4 50 | if: failure() 51 | with: 52 | name: build-reports 53 | path: "**/build/reports/*" 54 | 55 | - name: Upload Test Report 56 | uses: actions/upload-artifact@v4 57 | if: failure() 58 | with: 59 | name: junit-test-results 60 | path: '**/build/test-results/test/TEST-*.xml' 61 | retention-days: 1 62 | 63 | - name: Comment with build scan url 64 | uses: actions/github-script@v7 65 | if: github.event_name == 'pull_request' && failure() 66 | with: 67 | github-token: ${{secrets.GITHUB_TOKEN}} 68 | script: | 69 | github.rest.issues.createComment({ 70 | issue_number: context.issue.number, 71 | owner: context.repo.owner, 72 | repo: context.repo.repo, 73 | body: '❌ ${{ github.workflow }} failed: ${{ steps.build.outputs.build-scan-url }}' 74 | }) 75 | 76 | - name: Publish snapshot when on master 77 | if: success() && github.event_name != 'pull_request' && github.ref == 'refs/heads/master' 78 | env: 79 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEY }} 80 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD }} 81 | ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.ORG_GRADLE_PROJECT_OSSRHUSERNAME }} 82 | ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.ORG_GRADLE_PROJECT_OSSRHPASSWORD }} 83 | run: ./gradlew publish -Ppublish.central=true -Psemver.stage=snapshot 84 | 85 | # This job will update the PR with the JUnit report 86 | # In order to be able to make the most of it this job in particular has 87 | # augmented permissions. 88 | junit-report: 89 | name: JUnit Report 90 | if: | 91 | failure() 92 | && github.event_name == 'pull_request' 93 | needs: [ build ] 94 | permissions: 95 | checks: write # for mikepenz/action-junit-report 96 | 97 | runs-on: ubuntu-latest 98 | steps: 99 | - name: Download Test Report 100 | uses: actions/download-artifact@v4 101 | with: 102 | name: junit-test-results 103 | - name: Publish Test Report 104 | uses: mikepenz/action-junit-report@v5 105 | with: 106 | check_name: Test Report 107 | commit: ${{github.event.workflow_run.head_sha}} 108 | report_paths: '**/build/test-results/test/TEST-*.xml' -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: CI-release 2 | 3 | on: 4 | # Release event https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#release 5 | # Note: The `prereleased` type will not trigger for pre-releases published from draft releases, but the `published` 6 | # type will trigger. If you want a workflow to run when stable and pre-releases publish, subscribe to `published` 7 | # instead of `released` and `prereleased`. 8 | release: 9 | types: [ published ] 10 | 11 | workflow_dispatch: 12 | inputs: 13 | tag: 14 | description: 'Tag to release' 15 | required: true 16 | dry-run: 17 | description: Dry-run 18 | required: false 19 | default: true 20 | type: boolean 21 | 22 | run-name: >- 23 | ${{ 24 | github.event_name == 'workflow_dispatch' && format( 25 | '{0}: {1} {2}', 26 | github.workflow, 27 | inputs.tag, 28 | fromJSON('["", " (dry-run)"]')[inputs.dry-run] 29 | ) || '' 30 | }} 31 | 32 | # Ensure that only a single release automation workflow can run at a time. 33 | concurrency: Release automation 34 | 35 | # Environment variables 36 | # https://docs.github.com/en/actions/learn-github-actions/variables 37 | 38 | jobs: 39 | release-publish: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Check semver version in tag 43 | run: | 44 | prefix="v" 45 | tag_name="${{ github.event.inputs.tag || github.event.release.tag_name }}" 46 | semver_regex="(0|[1-9]\d*)+\.(0|[1-9]\d*)+\.(0|[1-9]\d*)+(-(([a-z-][\da-z-]+|[\da-z-]+[a-z-][\da-z-]*|0|[1-9]\d*)(\.([a-z-][\da-z-]+|[\da-z-]+[a-z-][\da-z-]*|0|[1-9]\d*))*))?(\\+([\da-z-]+(\.[\da-z-]+)*))?" 47 | echo "Checking version: $semver_regex" 48 | echo "$tag_name" | grep -Eq "^$prefix$semver_regex\$" 49 | shell: bash 50 | - uses: actions/checkout@v4 51 | with: 52 | ref: ${{ github.event.inputs.tag || github.event.release.tag_name }} 53 | fetch-depth: 50 54 | - name: Set up JDK 55 | uses: actions/setup-java@v4 56 | with: 57 | java-version: '21' 58 | distribution: 'zulu' 59 | - name: Setup Gradle 60 | uses: gradle/actions/setup-gradle@v4 61 | - name: Publish to Staging Repository 62 | if: success() 63 | env: 64 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGKEY }} 65 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.ORG_GRADLE_PROJECT_SIGNINGPASSWORD }} 66 | ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.ORG_GRADLE_PROJECT_OSSRHUSERNAME }} 67 | ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.ORG_GRADLE_PROJECT_OSSRHPASSWORD }} 68 | ORG_GRADLE_PROJECT_githubUser: ${{ github.actor }} 69 | ORG_GRADLE_PROJECT_githubToken: ${{ secrets.GITHUB_TOKEN }} 70 | # com.javiersc.semver.gradle.plugin should pick the actual tag 71 | run: ./gradlew publish -Ppublish.central=${{ inputs.dry-run && 'false' || 'true' }} --stacktrace 72 | 73 | # Don't forget to deploy the release to central 74 | # * 'Close' the repo to trigger the evaluations of to components against the requirements 75 | # * 'Release' the repo 76 | # https://central.sonatype.org/publish/release/ 77 | # If any errors occur, check the FAQ 78 | # https://central.sonatype.org/faq/400-error/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/vscode,gradle,intellij,java,eclipse,vim 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=vscode,gradle,intellij,java,eclipse,vim 4 | 5 | ### Eclipse ### 6 | .metadata 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | .recommenders 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # PyDev specific (Python IDE for Eclipse) 25 | *.pydevproject 26 | 27 | # CDT-specific (C/C++ Development Tooling) 28 | .cproject 29 | 30 | # CDT- autotools 31 | .autotools 32 | 33 | # Java annotation processor (APT) 34 | .factorypath 35 | 36 | # PDT-specific (PHP Development Tools) 37 | .buildpath 38 | 39 | # sbteclipse plugin 40 | .target 41 | 42 | # Tern plugin 43 | .tern-project 44 | 45 | # TeXlipse plugin 46 | .texlipse 47 | 48 | # STS (Spring Tool Suite) 49 | .springBeans 50 | 51 | # Code Recommenders 52 | .recommenders/ 53 | 54 | # Annotation Processing 55 | .apt_generated/ 56 | .apt_generated_test/ 57 | 58 | # Scala IDE specific (Scala & Java development for Eclipse) 59 | .cache-main 60 | .scala_dependencies 61 | .worksheet 62 | 63 | # Uncomment this line if you wish to ignore the project description file. 64 | # Typically, this file would be tracked if it contains build/dependency configurations: 65 | #.project 66 | 67 | ### Eclipse Patch ### 68 | # Spring Boot Tooling 69 | .sts4-cache/ 70 | 71 | ### Intellij ### 72 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 73 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 74 | 75 | # User-specific stuff 76 | .idea 77 | 78 | *.iml 79 | *.ipr 80 | 81 | # CMake 82 | cmake-build-*/ 83 | 84 | # File-based project format 85 | *.iws 86 | 87 | # IntelliJ 88 | out/ 89 | 90 | # Crashlytics plugin (for Android Studio and IntelliJ) 91 | com_crashlytics_export_strings.xml 92 | crashlytics.properties 93 | crashlytics-build.properties 94 | fabric.properties 95 | 96 | 97 | ### Java ### 98 | # Compiled class file 99 | *.class 100 | 101 | # Log file 102 | *.log 103 | 104 | # BlueJ files 105 | *.ctxt 106 | 107 | # Mobile Tools for Java (J2ME) 108 | .mtj.tmp/ 109 | 110 | # Package Files # 111 | *.jar 112 | *.war 113 | *.nar 114 | *.ear 115 | *.zip 116 | *.tar.gz 117 | *.rar 118 | 119 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 120 | hs_err_pid* 121 | 122 | ### Vim ### 123 | # Swap 124 | [._]*.s[a-v][a-z] 125 | !*.svg # comment out if you don't need vector files 126 | [._]*.sw[a-p] 127 | [._]s[a-rt-v][a-z] 128 | [._]ss[a-gi-z] 129 | [._]sw[a-p] 130 | 131 | # Session 132 | Session.vim 133 | Sessionx.vim 134 | 135 | # Temporary 136 | .netrwhist 137 | *~ 138 | # Auto-generated tag files 139 | tags 140 | # Persistent undo 141 | [._]*.un~ 142 | 143 | ### vscode ### 144 | .vscode/* 145 | !.vscode/settings.json 146 | !.vscode/tasks.json 147 | !.vscode/launch.json 148 | !.vscode/extensions.json 149 | *.code-workspace 150 | 151 | ### Gradle ### 152 | .gradle 153 | build/ 154 | 155 | # Ignore Gradle GUI config 156 | gradle-app.setting 157 | 158 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 159 | !gradle-wrapper.jar 160 | 161 | # Cache of project 162 | .gradletasknamecache 163 | 164 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 165 | # gradle/wrapper/gradle-wrapper.properties 166 | 167 | ### Gradle Patch ### 168 | **/build/ 169 | 170 | 171 | ### asdf ### 172 | .tool-versions 173 | 174 | # End of https://www.toptal.com/developers/gitignore/api/vscode,gradle,intellij,java,eclipse,vim 175 | 176 | -------------------------------------------------------------------------------- /HEADER: -------------------------------------------------------------------------------- 1 | Fireplace 2 | 3 | Copyright (c) ${year} - ${name} 4 | 5 | This Source Code Form is subject to the terms of the Mozilla Public 6 | License, v. 2.0. If a copy of the MPL was not distributed with this 7 | file, You can obtain one at https://mozilla.org/MPL/2.0/. -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | image:https://github.com/bric3/fireplace/actions/workflows/build.yml/badge.svg[Java CI with Gradle,link=https://github.com/bric3/fireplace/actions/workflows/build.yml] 2 | // image:https://snyk.io/test/github/bric3/fireplace/badge.svg?targetFile=build.gradle["Known Vulnerabilities", link="https://snyk.io/test/github/bric3/fireplace?targetFile=build.gradle.kts"] 3 | image:https://img.shields.io/maven-central/v/io.github.bric3.fireplace/fireplace-swing.svg["Maven Central", link="https://search.maven.org/artifact/io.github.bric3.fireplace/fireplace-swing"] 4 | 5 | == Flamegraph / Iciclegraph Java Swing component 6 | 7 | ++++ 8 | 9 | 10 | Shows a flamegraph in either light or dark mode 11 | 12 | ++++ 13 | 14 | This flamegraph component is known to be used in https://github.com/openjdk/jmc[JDK Mission Control 9.0] and in the https://docs.datadoghq.com/developers/ide_integrations/idea/[Datadog plugin for IntelliJ]. 15 | 16 | 17 | == Usage 18 | 19 | .Example usage 20 | [source,java] 21 | ---- 22 | var fg = new FlamegraphView(); 23 | 24 | flamegraphView.setRenderConfiguration( 25 | FrameTextsProvider.of( 26 | frame -> frame.isRoot() ? "root" : frame.actualNode.getFrame().getHumanReadableShortString(), 27 | frame -> frame.isRoot() ? "" : FormatToolkit.getHumanReadable(frame.actualNode.getFrame().getMethod(), false, false, false, false, true, false), 28 | frame -> frame.isRoot() ? "" : frame.actualNode.getFrame().getMethod().getMethodName() 29 | ), 30 | new DimmingFrameColorProvider<>(defaultFrameColorMode.colorMapperUsing(ColorMapper.ofObjectHashUsing(defaultColorPalette.colors()))), 31 | FrameFontProvider.defaultFontProvider() 32 | ); 33 | 34 | jpanel.add(flamegraphView.component); 35 | 36 | 37 | // later, fill in the data 38 | var listOfFrames = FrameBox.flattenAndCalculateCoordinate(new ArrayList(), ...); 39 | flamegraphView.setModel( 40 | new FrameModel<>( 41 | "title, events (CPU, Locks)", // used in the root "frame" 42 | (a, b) -> Objects.equals(a, b), // used to identify equal frames 43 | listOfFrames 44 | ) 45 | ); 46 | 47 | ---- 48 | 49 | == Misc 50 | 51 | Snapshot versions will be delivered at 52 | 53 | > https://s01.oss.sonatype.org/content/repositories/snapshots/io/github/bric3/fireplace[https://s01.oss.sonatype.org/content/repositories/*snapshots*/io/github/bric3/fireplace] 54 | -------------------------------------------------------------------------------- /build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 11 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 12 | 13 | plugins { 14 | // doc: https://docs.gradle.org/current/userguide/kotlin_dsl.html 15 | `kotlin-dsl` 16 | } 17 | 18 | repositories { 19 | mavenCentral() 20 | gradlePluginPortal() 21 | } 22 | 23 | dependencies { 24 | implementation(libs.gradlePlugin.bnd) 25 | implementation(libs.gradlePlugin.semver) 26 | implementation(libs.gradlePlugin.testLogger) 27 | implementation(libs.gradlePlugin.licenceReport) 28 | 29 | // https://github.com/gradle/gradle/issues/15383 30 | implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) 31 | } 32 | 33 | java { 34 | toolchain { 35 | languageVersion.set(JavaLanguageVersion.of(11)) 36 | } 37 | } 38 | 39 | tasks.withType(KotlinCompile::class) { 40 | compilerOptions { 41 | optIn.add("kotlin.ExperimentalStdlibApi") 42 | freeCompilerArgs.addAll(listOf("-Xjsr305=strict")) 43 | jvmTarget.set(JVM_11) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | pluginManagement { 11 | repositories { 12 | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-dependencies") 13 | mavenCentral() 14 | gradlePluginPortal() 15 | } 16 | } 17 | 18 | rootProject.name = "build-logic" 19 | 20 | dependencyResolutionManagement { 21 | repositories { 22 | gradlePluginPortal() 23 | } 24 | versionCatalogs { 25 | create("libs") { 26 | from(files("../gradle/libs.versions.toml")) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/Utils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | import org.gradle.api.Project 11 | import org.gradle.api.artifacts.ExternalModuleDependencyBundle 12 | import org.gradle.api.artifacts.MinimalExternalModuleDependency 13 | import org.gradle.api.artifacts.VersionCatalog 14 | import org.gradle.api.artifacts.VersionCatalogsExtension 15 | import org.gradle.api.artifacts.VersionConstraint 16 | import org.gradle.api.provider.Provider 17 | import org.gradle.kotlin.dsl.the 18 | 19 | // https://github.com/gradle/gradle/issues/15383 20 | val Project.libs 21 | get() = the() 22 | 23 | // Don't name it libs otherwise it shadows the actual libs extension 24 | val Project.libsCatalog 25 | get() = the().named("libs") 26 | fun VersionCatalog.getVersion(version: String): VersionConstraint = 27 | findVersion(version).orElseThrow { IllegalArgumentException("version $version not found in catalog $name") } 28 | fun VersionCatalog.getLibrary(alias: String): Provider = 29 | findLibrary(alias).orElseThrow { IllegalArgumentException("library $alias not found in catalog $name") } 30 | fun VersionCatalog.getBundle(alias: String): Provider = 31 | findBundle(alias).orElseThrow { IllegalArgumentException("bundle $alias not found in catalog $name") } 32 | 33 | fun Project.properties(key: String) = findProperty(key).toString() -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/fireplace.application.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | plugins { 12 | application 13 | id("fireplace.tests") 14 | } 15 | 16 | val javaVersion = 21 17 | java { 18 | toolchain { 19 | languageVersion.set(JavaLanguageVersion.of(javaVersion)) 20 | } 21 | } 22 | 23 | repositories { 24 | mavenCentral() 25 | maven { 26 | url = uri("https://oss.sonatype.org/content/repositories/snapshots/") 27 | } 28 | } 29 | 30 | tasks.withType(JavaCompile::class) { 31 | options.compilerArgs.addAll(arrayOf("-Xlint")) 32 | options.release.set(javaVersion) 33 | } 34 | 35 | // Due to https://github.com/gradle/gradle/issues/18426, tasks are not declared in the TaskContainerScope 36 | tasks.withType().configureEach { 37 | group = "class-with-main" 38 | classpath(sourceSets.main.get().runtimeClasspath) 39 | 40 | // Need to set the toolchain https://github.com/gradle/gradle/issues/16791 41 | javaLauncher.set(javaToolchains.launcherFor(java.toolchain)) 42 | 43 | jvmArgs( 44 | "-ea", 45 | "-XX:+UnlockDiagnosticVMOptions", 46 | "-XX:+DebugNonSafepoints", 47 | "-XX:NativeMemoryTracking=summary", 48 | ) 49 | 50 | projectDir.resolve(properties("hotswap-agent-location")).let { 51 | if (it.exists() && properties("dcevm-enabled").toBoolean()) { 52 | // DCEVM 53 | jvmArgs( 54 | "-XX:+AllowEnhancedClassRedefinition", 55 | "-XX:HotswapAgent=external", 56 | "-javaagent:$it" 57 | ) 58 | } 59 | } 60 | } 61 | 62 | tasks.jar { 63 | manifest.attributes( 64 | "Implementation-Title" to project.name, 65 | "Implementation-Version" to project.version, 66 | "Automatic-Module-Name" to project.name.replace('-', '.'), 67 | "Created-By" to "${providers.systemProperty("java.version").get()} (${providers.systemProperty("java.specification.vendor").get()})", 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/fireplace.java-library.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | import com.javiersc.semver.project.gradle.plugin.SemverExtension 11 | 12 | plugins { 13 | `java-library` 14 | id("fireplace.tests") 15 | id("biz.aQute.bnd.builder") 16 | id("com.javiersc.semver") 17 | } 18 | 19 | val libJavaVersion = 11 20 | 21 | configure { 22 | tagPrefix.set("v") 23 | } 24 | 25 | repositories { 26 | mavenCentral() 27 | } 28 | 29 | dependencies { 30 | compileOnlyApi(libs.jetbrains.annotations) 31 | } 32 | 33 | configure { 34 | withJavadocJar() 35 | withSourcesJar() 36 | } 37 | 38 | val licenseSpec = copySpec { 39 | from("${project.rootDir}/LICENSE") 40 | } 41 | 42 | 43 | tasks { 44 | withType(JavaCompile::class) { 45 | options.encoding = "UTF-8" 46 | options.release.set(libJavaVersion) 47 | } 48 | 49 | withType(Jar::class) { 50 | metaInf.with(licenseSpec) 51 | } 52 | 53 | named("jar") { 54 | // Sets OSGi bundle attributes 55 | // See https://en.wikipedia.org/wiki/OSGi#Bundles for a minimal introduction to the bundle manifest 56 | // See https://enroute.osgi.org/FAQ/520-bnd.html for a high level of what is the "bnd" tool 57 | // If we ever expose any shaded classes, then the bundle info will need to be added after the shadow step. 58 | // For now, though, generating the bundle info here results 59 | bundle { 60 | val version by archiveVersion 61 | bnd(providers.provider { "Bundle-Name=${project.name}" }) 62 | bnd(providers.provider { "Bundle-Description=${project.description}" }) 63 | bnd( 64 | mapOf( 65 | "Bundle-License" to "https://www.mozilla.org/en-US/MPL/2.0/", 66 | "-exportcontents" to listOf( 67 | "!io.github.bric3.fireplace.internal.*", 68 | "io.github.bric3.fireplace.*", 69 | ).joinToString(";"), 70 | "-removeheaders" to "Created-By" 71 | ) 72 | ) 73 | } 74 | 75 | manifest.attributes( 76 | "Implementation-Title" to providers.provider { project.name }, 77 | "Implementation-Version" to providers.provider { project.version }, 78 | "Automatic-Module-Name" to providers.provider { project.name.replace('-', '.') }, 79 | "Created-By" to "${providers.systemProperty("java.version").get()} (${providers.systemProperty("java.specification.vendor").get()})", 80 | ) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/fireplace.licence-report.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | import com.github.jk1.license.filter.ExcludeDependenciesWithoutArtifactsFilter 11 | import com.github.jk1.license.filter.LicenseBundleNormalizer 12 | import com.github.jk1.license.filter.SpdxLicenseBundleNormalizer 13 | import com.github.jk1.license.render.InventoryMarkdownReportRenderer 14 | import com.github.jk1.license.render.TextReportRenderer 15 | 16 | plugins { 17 | id("com.github.jk1.dependency-license-report") 18 | } 19 | 20 | licenseReport { 21 | projects = arrayOf(project) 22 | outputDir = layout.buildDirectory.map { "$it/reports/licenses" }.get() 23 | renderers = arrayOf(InventoryMarkdownReportRenderer(), TextReportRenderer()) 24 | // Dependencies use inconsistent titles for Apache-2.0, so we need to specify mappings 25 | filters = arrayOf( 26 | LicenseBundleNormalizer( 27 | mapOf( 28 | "The Apache License, Version 2.0" to "Apache-2.0", 29 | "The Apache Software License, Version 2.0" to "Apache-2.0", 30 | ) 31 | ), 32 | ExcludeDependenciesWithoutArtifactsFilter(), 33 | SpdxLicenseBundleNormalizer() 34 | ) 35 | } -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/fireplace.local-eclipse-swt-platform.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform 11 | 12 | plugins { 13 | `java-library` 14 | } 15 | 16 | dependencies { 17 | implementation(libs.bundles.eclipse.swt) 18 | } 19 | 20 | // Configure the right SWT dependency for the current platform 21 | // Gradle do not offer a way to resolve a "property" like ${property}, instead it is necessary 22 | // to configure the dependency substitution. 23 | val os: OperatingSystem = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurrentOperatingSystem() 24 | val arch: String = providers.systemProperty("os.arch").get() 25 | configurations.all { 26 | resolutionStrategy { 27 | dependencySubstitution { 28 | // Available SWT packages https://repo1.maven.org/maven2/org/eclipse/platform/ 29 | val osId = when { 30 | os.isWindows -> "win32.win32" 31 | os.isLinux -> "gtk.linux" 32 | os.isMacOsX -> "cocoa.macosx" 33 | else -> throw GradleException("Unsupported OS: $os") 34 | } 35 | 36 | val archId = when (arch) { 37 | "x86_64", "amd64" -> "x86_64" 38 | "aarch64" -> "aarch64" 39 | else -> throw GradleException("Unsupported architecture: $arch") 40 | } 41 | 42 | substitute(module("org.eclipse.platform:org.eclipse.swt.\${osgi.platform}")) 43 | .using(module("org.eclipse.platform:org.eclipse.swt.$osId.$archId:${libs.versions.eclipse.swt.get()}")) 44 | .because("The maven property '\${osgi.platform}' that appear in the artifact coordinate is not handled by Gradle, it is required to replace the dependency") 45 | } 46 | } 47 | } 48 | 49 | tasks.withType { 50 | if (DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX) { 51 | doFirst { 52 | logger.lifecycle("Added JVM argument -XstartOnFirstThread") 53 | } 54 | jvmArgs("-XstartOnFirstThread") 55 | } 56 | } -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/fireplace.maven-publication.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | import com.javiersc.semver.project.gradle.plugin.extensions.isSnapshot 11 | 12 | plugins { 13 | `maven-publish` 14 | signing 15 | } 16 | 17 | publishing { 18 | publications { 19 | withType().configureEach { 20 | val gitRepo = providers.provider { "https://github.com/bric3/fireplace" } 21 | 22 | pom { 23 | name.set(artifactId) 24 | description.set(providers.provider { project.description }) 25 | 26 | url.set(gitRepo) 27 | 28 | issueManagement { 29 | system.set("Github") 30 | url.set(gitRepo.map { "$it/issues" }) 31 | } 32 | 33 | licenses { 34 | license { 35 | distribution.set("repo") 36 | name.set("Mozilla Public License Version 2.0") 37 | url.set("https://www.mozilla.org/en-US/MPL/2.0/") 38 | } 39 | } 40 | 41 | developers { 42 | developer { 43 | id.set("bric3") 44 | name.set("Brice Dutheil") 45 | email.set("brice.dutheil@gmail.com") 46 | } 47 | } 48 | 49 | scm { 50 | connection.set(gitRepo.map { "scm:git:${it}.git" }) 51 | developerConnection.set(gitRepo.map { "scm:git:${it}.git" }) 52 | url.set(gitRepo) 53 | } 54 | } 55 | } 56 | } 57 | 58 | repositories { 59 | val isGithubRelease = providers.environmentVariable("GITHUB_JOB").orNull 60 | .equals("release-publish", true) 61 | 62 | val isPublishToCentral = providers.gradleProperty("publish.central").orNull.toBoolean() 63 | 64 | if (isPublishToCentral) { 65 | maven { 66 | name = "central" 67 | setUrl(isSnapshot.map { 68 | if (isGithubRelease && !it) "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" 69 | else "https://s01.oss.sonatype.org/content/repositories/snapshots" 70 | }.map(::uri)) 71 | credentials { 72 | username = properties("ossrhUsername") 73 | password = properties("ossrhPassword") 74 | } 75 | } 76 | 77 | val ghUser = properties("githubUser") 78 | val ghToken = properties("githubToken") 79 | if (isGithubRelease && ghUser != "null" && ghToken != "null") { 80 | logger.lifecycle("Will be publishing to GitHubPackages") 81 | maven { 82 | name = "GitHubPackages" 83 | url = uri("https://maven.pkg.github.com/bric3/fireplace") 84 | credentials { 85 | username = ghUser 86 | password = ghToken 87 | } 88 | } 89 | } 90 | } else { 91 | maven { 92 | name = "build-dir" 93 | setUrl(rootProject.layout.buildDirectory.map { "$it/publishing-repository" }.zip(isSnapshot) { dir, isSnapshot -> 94 | if (isSnapshot) "$dir/snapshots" else "$dir/releases" 95 | }.map(::uri)) 96 | } 97 | } 98 | } 99 | } 100 | 101 | signing { 102 | setRequired({ gradle.taskGraph.hasTask("publish") }) 103 | useInMemoryPgpKeys( 104 | // properties("signingKeyId") as? String, 105 | properties("signingKey"), 106 | properties("signingPassword") as? String 107 | ) 108 | sign(publishing.publications) 109 | } 110 | 111 | tasks { 112 | register("cleanLocalPublishingRepository") { 113 | doLast { 114 | rootProject.layout.buildDirectory.get().asFile.resolve("publishing-repository").deleteRecursively() 115 | } 116 | } 117 | 118 | withType().configureEach { 119 | doFirst { 120 | logger.lifecycle("Publishing version '${this@configureEach.publication.version}' to ${this@configureEach.repository.url}") 121 | } 122 | } 123 | 124 | withType().configureEach { 125 | doFirst { 126 | logger.lifecycle("Publishing version '${this@configureEach.publication.version}' locally") 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/fireplace.published-java-library.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | plugins { 11 | id("fireplace.java-library") 12 | id("fireplace.maven-publication") 13 | } 14 | 15 | publishing { 16 | publications { 17 | create("maven") { 18 | from(components["java"]) 19 | // Gradle feature variants can't be mapped to Maven's pom 20 | // suppressAllPomMetadataWarnings() 21 | 22 | // versionMapping { 23 | // usage(Usage.JAVA_API) { 24 | // fromResolutionResult() 25 | // } 26 | // 27 | // usage(Usage.JAVA_RUNTIME) { 28 | // fromResolutionOf("runtimeClasspath") 29 | // } 30 | // } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/fireplace.tests.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | import com.adarshr.gradle.testlogger.theme.ThemeType 11 | 12 | plugins { 13 | `jvm-test-suite` 14 | id("com.adarshr.test-logger") 15 | } 16 | 17 | testlogger { 18 | theme = ThemeType.MOCHA_PARALLEL 19 | isShowPassed = false 20 | } 21 | 22 | testing { 23 | suites { 24 | val test by getting(JvmTestSuite::class) { 25 | useJUnitJupiter(libs.versions.junit.jupiter) 26 | dependencies { 27 | implementation.add(libs.assertj) 28 | } 29 | } 30 | 31 | withType(JvmTestSuite::class) { 32 | targets.configureEach { 33 | testTask.configure { 34 | systemProperty("gradle.test.suite.report.location", reports.html.outputLocation.get().asFile) 35 | 36 | // Note: Replaced by testLogger 37 | // testLogging { 38 | // showStackTraces = true 39 | // exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL 40 | // 41 | // events = setOf( 42 | // org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED, 43 | // org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR, 44 | // org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED 45 | // ) 46 | // } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | plugins { 11 | id("com.github.hierynomus.license") version "0.16.1" 12 | } 13 | 14 | allprojects { 15 | group = "io.github.bric3.fireplace" 16 | } 17 | 18 | tasks.register("v") { 19 | doLast { 20 | println(project.version.toString()) 21 | } 22 | } 23 | 24 | license { 25 | ext["year"] = "2021, Today" 26 | ext["name"] = "Brice Dutheil" 27 | header = file("HEADER") 28 | 29 | strictCheck = true 30 | ignoreFailures = false 31 | excludes( 32 | listOf( 33 | "**/*.java.template", 34 | "**/testData/*.java", 35 | ) 36 | ) 37 | 38 | mapping( 39 | mapOf( 40 | "java" to "SLASHSTAR_STYLE", 41 | "kt" to "SLASHSTAR_STYLE", 42 | "kts" to "SLASHSTAR_STYLE", 43 | "yaml" to "SCRIPT_STYLE", 44 | "yml" to "SCRIPT_STYLE", 45 | "svg" to "XML_STYLE", 46 | "md" to "XML_STYLE", 47 | "toml" to "SCRIPT_STYLE" 48 | ) 49 | ) 50 | } 51 | tasks.register("licenseCheckForProjectFiles", com.hierynomus.gradle.license.tasks.LicenseCheck::class) { 52 | source = fileTree(project.projectDir) { 53 | include("**/*.kt", "**/*.kts") 54 | include("**/*.toml") 55 | exclude("**/buildSrc/build/generated-sources/**") 56 | } 57 | } 58 | tasks["license"].dependsOn("licenseCheckForProjectFiles") 59 | tasks.register("licenseFormatForProjectFiles", com.hierynomus.gradle.license.tasks.LicenseFormat::class) { 60 | source = fileTree(project.projectDir) { 61 | include("**/*.kt", "**/*.kts") 62 | include("**/*.toml") 63 | exclude("**/buildSrc/build/generated-sources/**") 64 | } 65 | } 66 | tasks["licenseFormat"].dependsOn("licenseFormatForProjectFiles") 67 | -------------------------------------------------------------------------------- /fireplace-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | plugins { 11 | id("fireplace.application") 12 | id("fireplace.licence-report") 13 | // fork of com.github.johnrengelman.shadow with support for recent versions of Java and Gradle 14 | // https://github.com/johnrengelman/shadow/pull/876 15 | // https://github.com/johnrengelman/shadow/issues/908 16 | id("io.github.goooler.shadow") version "8.1.8" 17 | kotlin("jvm") version "2.1.21" 18 | } 19 | 20 | description = "Opens a JFR file to inspect its content." 21 | 22 | dependencies { 23 | implementation(libs.jetbrains.annotations) 24 | implementation(projects.fireplaceSwing) 25 | implementation(projects.fireplaceSwingAnimation) 26 | implementation(libs.classgraph) 27 | implementation(libs.bundles.flatlaf) 28 | implementation(libs.bundles.darklaf) 29 | implementation(libs.flightrecorder) 30 | implementation(libs.bundles.kotlinx.coroutines) 31 | } 32 | 33 | application { 34 | mainClass.set("io.github.bric3.fireplace.FireplaceMain") 35 | } 36 | 37 | tasks.shadowJar { 38 | archiveClassifier.set("shaded") 39 | dependsOn(tasks.generateLicenseReport) 40 | from(tasks.generateLicenseReport.map { it.outputFolder }) { 41 | include("*.md") 42 | include("*.txt") 43 | } 44 | // TODO relocation ? 45 | // val newLocation = "io.github.bric3.fireplace.shaded_.do_not_use" 46 | // relocate("kotlin", "$newLocation.kotlin") 47 | // relocate("kotlinx", "$newLocation.kotlinx") 48 | // relocate("org.jetbrains", "$newLocation.org.jetbrains") 49 | // relocate("org.intellij", "$newLocation.org.intellij") 50 | dependencies { 51 | // Remove all Kotlin metadata so that it looks like an ordinary Java Jar 52 | exclude("**/*.kotlin_metadata") 53 | exclude("**/*.kotlin_module") 54 | exclude("**/*.kotlin_builtins") 55 | // Eliminate dependencies' pom files 56 | exclude("**/pom.*") 57 | } 58 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/java/io/github/bric3/fireplace/icons/GeneratedIcon.java: -------------------------------------------------------------------------------- 1 | package io.github.bric3.fireplace.icons; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | import java.util.function.Function; 6 | 7 | public interface GeneratedIcon extends Icon { 8 | /** 9 | * Changes the dimension of this icon. 10 | * 11 | * @param newDimension New dimension for this icon. 12 | */ 13 | void setDimension(Dimension newDimension); 14 | 15 | /** 16 | * Sets a color filter 17 | * 18 | * @param colorFilter 19 | */ 20 | void setColorFilter(Function colorFilter); 21 | } 22 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/Utils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace 11 | 12 | import java.util.* 13 | import java.util.function.Supplier 14 | 15 | object Utils { 16 | val isFireplaceDebug: Boolean 17 | get() = (isFireplaceSwingDebug 18 | || java.lang.Boolean.getBoolean("fireplace.debug")) 19 | val isFireplaceSwingDebug: Boolean 20 | get() = java.lang.Boolean.getBoolean("fireplace.swing.debug") 21 | val isDebugging: Boolean 22 | get() = ProcessHandle.current() 23 | .info() 24 | .arguments() 25 | .map { args: Array? -> 26 | Arrays.stream(args).anyMatch { arg: String -> arg.contains("-agentlib:jdwp") } 27 | } 28 | .orElse(false) 29 | 30 | fun ifDebugging(runnable: Runnable) { 31 | if (isDebugging) { 32 | runnable.run() 33 | } 34 | } 35 | 36 | /** 37 | * Returns a supplier that caches the computation of the given value supplier. 38 | * 39 | * @param valueSupplier the value supplier 40 | * @param the type of the value 41 | * @return a memoized version of the value supplier 42 | * @see [Holger's answer on StackOverflow](https://stackoverflow.com/a/35335467/48136) 43 | */ 44 | fun memoize(valueSupplier: Supplier): Supplier { 45 | return object : Supplier { 46 | var delegate = Supplier { firstTime() } 47 | var initialized = false 48 | override fun get(): T { 49 | return delegate.get() 50 | } 51 | 52 | @Synchronized 53 | private fun firstTime(): T { 54 | if (!initialized) { 55 | val value = valueSupplier.get() 56 | delegate = Supplier { value } 57 | initialized = true 58 | } 59 | return delegate.get() 60 | } 61 | } 62 | } 63 | 64 | inline fun stopWatch(name: String, crossinline block: () -> T): T { 65 | if (!isFireplaceSwingDebug) { 66 | return block() 67 | } 68 | 69 | val start: Long = System.currentTimeMillis() 70 | 71 | try { 72 | return block() 73 | } finally { 74 | val elapsed: Long = System.currentTimeMillis() - start 75 | println("[${Thread.currentThread().name}] $name took $elapsed ms") 76 | } 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/appDebug/FireplaceAppSystemProperties.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.appDebug 11 | 12 | import io.github.bric3.fireplace.Utils 13 | import io.github.bric3.fireplace.ui.ViewPanel 14 | import io.github.bric3.fireplace.ui.toolkit.simpleReadOnlyTable 15 | 16 | class FireplaceAppSystemProperties : ViewPanel { 17 | override val identifier: String = "App System properties" 18 | 19 | override val view by lazy { 20 | simpleReadOnlyTable( 21 | System.getProperties().map { arrayOf(it.key, it.value) }.toTypedArray(), 22 | arrayOf("Key", "Value") 23 | ) 24 | } 25 | 26 | companion object { 27 | fun isActive(): Boolean = Utils.isDebugging || Utils.isFireplaceDebug 28 | } 29 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/appDebug/FireplaceAppUIManagerProperties.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.appDebug 11 | 12 | import io.github.bric3.fireplace.Utils 13 | import io.github.bric3.fireplace.ui.ViewPanel 14 | import io.github.bric3.fireplace.ui.toolkit.simpleReadOnlyTable 15 | import javax.swing.* 16 | 17 | class FireplaceAppUIManagerProperties : ViewPanel { 18 | override val identifier: String = "App UIManager properties" 19 | 20 | override val view by lazy { 21 | // UIManager.getLookAndFeelDefaults() 22 | // .entries 23 | // .stream() 24 | // .filter { (_, value): Entry -> value is Color } 25 | // .map { (key, value): Entry -> 26 | // "$key: " + String.format( 27 | // "#%06X", 28 | // 0xFFFFFF and (value as Color).rgb 29 | // ) 30 | // } 31 | // .sorted() 32 | // .map { } 33 | 34 | simpleReadOnlyTable( 35 | UIManager.getLookAndFeelDefaults() 36 | .entries 37 | .map { arrayOf(it.key, it.value) } 38 | .toTypedArray(), 39 | arrayOf("Key", "Value") 40 | ) 41 | } 42 | 43 | companion object { 44 | fun isActive() = Utils.isFireplaceDebug || Utils.isFireplaceSwingDebug 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/ChartComponent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import java.awt.AWTEvent 13 | import java.awt.Graphics 14 | import java.awt.Graphics2D 15 | import java.awt.Rectangle 16 | import java.awt.event.MouseEvent 17 | import java.beans.PropertyChangeSupport 18 | import javax.swing.* 19 | 20 | /** 21 | * A component that draws an [Chart] within the bounds of the component. 22 | * 23 | * @param chart the chart. 24 | */ 25 | class ChartComponent(chart: Chart? = null) : JComponent() { 26 | private val propertyChangeSupport = PropertyChangeSupport(this) 27 | 28 | var chart: Chart? = chart 29 | set(value) { 30 | val oldChart = field 31 | if (oldChart == value) { 32 | return 33 | } 34 | field = value 35 | propertyChangeSupport.firePropertyChange("chart", oldChart, value) 36 | } 37 | 38 | val toolTipComponent: JComponent? 39 | get() = chart?.createToolTipComponent(getBounds(rect), mousePosition) 40 | 41 | /** A reusable rectangle to avoid creating work for the garbage collector. */ 42 | private val rect = Rectangle() 43 | 44 | init { 45 | enableEvents(AWTEvent.MOUSE_EVENT_MASK or AWTEvent.MOUSE_MOTION_EVENT_MASK) 46 | border = null 47 | propertyChangeSupport.addPropertyChangeListener("chart") { 48 | revalidate() 49 | repaint() 50 | } 51 | } 52 | 53 | /** 54 | * Paints the component. 55 | * The chart will be drawn at a size matching the bounds of the component. 56 | * 57 | * @param g the Java2D graphics target. 58 | */ 59 | override fun paintComponent(g: Graphics) { 60 | super.paintComponent(g) 61 | chart?.let { 62 | val g2 = g as Graphics2D 63 | getBounds(rect) 64 | g.translate(-rect.x, -rect.y) 65 | it.draw(g2, rect, mousePosition) 66 | g.translate(rect.x, rect.y) 67 | } 68 | } 69 | 70 | override fun processMouseEvent(e: MouseEvent) { 71 | // handle clicks? 72 | super.processMouseEvent(e) 73 | } 74 | 75 | override fun processMouseMotionEvent(e: MouseEvent) { 76 | if (e.id == MouseEvent.MOUSE_MOVED) { 77 | repaint() 78 | } 79 | super.processMouseMotionEvent(e) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/ChartDataset.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import io.github.bric3.fireplace.charts.XYDataset.XY 13 | 14 | interface ChartDataset { 15 | val label: String 16 | val rangeOfX: Range 17 | val rangeOfY: Range 18 | val itemCount: Int 19 | fun xAt(index: Int): Long 20 | fun yAt(index: Int): Double? 21 | fun xyAt(index: Int): XY 22 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/ChartRenderer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import java.awt.Graphics2D 13 | import java.awt.Point 14 | import java.awt.geom.Rectangle2D 15 | 16 | /** 17 | * A renderer for an [Chart] that draws data with a particular representation 18 | * (for example, lines or bars). 19 | */ 20 | interface ChartRenderer { 21 | /** 22 | * Draws a representation of the supplied dataset within the plot bounds of the supplied 23 | * Java2D graphics target. The chart can be used to provide some global attributes for the 24 | * rendering (such as the x-range and y-range for display). 25 | * 26 | * @param chart the chart. 27 | * @param dataset the dataset. 28 | * @param g2 the Java2D graphics target. 29 | * @param plotBounds the plot bounds. 30 | */ 31 | fun draw(chart: Chart, dataset: ChartDataset, g2: Graphics2D, plotBounds: Rectangle2D, mousePosition: Point?) 32 | 33 | /** 34 | * Draws a tracker line for the supplied dataset within the plot bounds of the supplied 35 | * Java2D graphics target. The chart can be used to provide some global attributes for the 36 | * rendering (such as the x-range and y-range for display). 37 | * 38 | * @param chart the chart. 39 | * @param dataset the dataset. 40 | * @param g2 the Java2D graphics target. 41 | * @param plotBounds the plot bounds. 42 | */ 43 | fun drawTrackerLine( 44 | chart: Chart, 45 | dataset: ChartDataset, 46 | g2: Graphics2D, 47 | plotBounds: Rectangle2D, 48 | mousePosition: Point? 49 | ) { } 50 | } 51 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/ChartSpecification.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import io.github.bric3.fireplace.charts.XYDataset.XY 13 | import java.awt.Color 14 | import javax.swing.* 15 | 16 | /** 17 | * Specifies the various properties of a chart (dataset, label, how it's rendered). 18 | */ 19 | data class ChartSpecification( 20 | val dataset: XYDataset, 21 | val label: String, 22 | val renderer: RendererDescriptor, 23 | ) { 24 | /** 25 | * Common interface for specifying how chart is rendered. 26 | */ 27 | sealed interface RendererDescriptor 28 | 29 | /** 30 | * Specifies a **line-rendered** chart. 31 | */ 32 | data class LineRendererDescriptor( 33 | val lineColor: Color? = null, 34 | val fillColors: List? = null, 35 | val tooltipFunction: (XY, String) -> JComponent? = { _, _ -> null }, 36 | ) : RendererDescriptor 37 | } 38 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/ChartUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import java.awt.Color 13 | import java.time.Instant 14 | import java.time.ZoneId 15 | import java.time.format.DateTimeFormatter 16 | import java.util.concurrent.TimeUnit.MILLISECONDS 17 | 18 | 19 | fun Color.withAlpha(alpha: Float) = withAlpha((alpha * 255).toInt()) 20 | 21 | fun Color.withAlpha(alpha: Int): Color { 22 | require(alpha in 0..255) { "Alpha value must be between 0 and 255" } 23 | return Color( 24 | red, 25 | green, 26 | blue, 27 | alpha 28 | ) 29 | } 30 | 31 | fun presentableDuration(durationMs: Long): String { 32 | val millis = durationMs % 1000L 33 | val seconds = MILLISECONDS.toSeconds(durationMs) % 60L 34 | val minutes = MILLISECONDS.toMinutes(durationMs) % 60L 35 | val hours = MILLISECONDS.toHours(durationMs) 36 | return String.format( 37 | "%02d:%02d:%02d.%03d", 38 | hours, minutes, seconds, millis 39 | ) 40 | } 41 | 42 | private val timestampFormatter = DateTimeFormatter.ofPattern("LLL d, H:mm:ss") 43 | fun presentableTime(epochMs: Long): String { 44 | return timestampFormatter.format(Instant.ofEpochMilli(epochMs).atZone(ZoneId.systemDefault())) 45 | } 46 | 47 | fun presentablePercentage(value: Double): String { 48 | return String.format("%.2f%%", value * 100) 49 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/Range.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import java.util.* 13 | 14 | /** 15 | * Represents a range of values between min and max. 16 | * 17 | * @param min the minimum value. 18 | * @param max the maximum value. 19 | */ 20 | @JvmRecord 21 | data class Range(val min: T, val max: T) { 22 | val isZeroLength: Boolean 23 | get() = min == max 24 | 25 | /** 26 | * Returns a range based on this range but extended (if necessary) 27 | * to include the specified value. 28 | * 29 | * @param v the value. 30 | * @return The range. 31 | */ 32 | fun include(v: T): Range { 33 | require(!(v is Double && !java.lang.Double.isFinite(v.toDouble()))) { "only finite values permitted" } 34 | if (compare(v, min) < 0) { 35 | return Range(v, max) 36 | } 37 | if (compare(v, max) > 0) { 38 | return Range(min, v) 39 | } 40 | return this 41 | } 42 | 43 | /** 44 | * Calculates a fractional value indicating where [v] lies along the range. 45 | * This will return 0.0 if the range has zero-length. 46 | * 47 | * @param v the value. 48 | * @return The fraction. 49 | */ 50 | fun ratioFor(v: T): Double { 51 | if (compare(max, min) > 0) { 52 | if (min is Double) { 53 | return (v.toDouble() - min.toDouble()) / (max.toDouble() - min.toDouble()) 54 | } 55 | if (min is Long) { 56 | return (v.toLong() - min.toLong()).toDouble() / (max.toLong() - min.toLong()).toDouble() 57 | } 58 | if (min is Int) { 59 | return (v.toInt() - min.toInt()).toDouble() / (max.toInt() - min.toInt()).toDouble() 60 | } 61 | throw IllegalArgumentException("Unsupported type " + min::class.java) 62 | } 63 | return 0.0 64 | } 65 | 66 | override fun equals(other: Any?): Boolean { 67 | if (this === other) return true 68 | if (other == null || javaClass != other.javaClass) return false 69 | val range = other as Range<*> 70 | if (min.javaClass != range.min.javaClass) { 71 | return false 72 | } 73 | return compare(range.min, min) == 0 && compare(range.max, max) == 0 74 | } 75 | 76 | override fun hashCode(): Int { 77 | return Objects.hash(min, max) 78 | } 79 | 80 | override fun toString(): String { 81 | return "[Range: $min, $max]" 82 | } 83 | 84 | init { 85 | check(min, max) 86 | } 87 | 88 | companion object { 89 | private fun check(min: T?, max: T?) { 90 | require(!(min == null || max == null)) { "Null 'min' or 'max' argument." } 91 | require(min.javaClass == max.javaClass) { "min and max must be of the same type, min is " + min.javaClass + ", max is " + max.javaClass } 92 | require(!(min is Double && min.toDouble() > max.toDouble())) { "min must be less than max, min is $min, max is $max" } 93 | require(!(min is Long && min.toLong() > max.toLong())) { "min must be less than max, min is $min, max is $max" } 94 | require(!(min is Int && min.toInt() > max.toInt())) { "min must be less than max, min is $min, max is $max" } 95 | } 96 | 97 | /** 98 | * compare two `Number` of the same type. 99 | * 100 | * @param a the first value. 101 | * @param b the second value. 102 | * @param The type of the number. 103 | * @return the value `0` if `d1` is numerically equal to `d2`; a value less than 104 | * `0` if `d1` is numerically less than `d2`; and a value greater than `0` 105 | * if `d1` is numerically greater than `d2`. 106 | */ 107 | fun compare(a: T, b: T): Int { 108 | if (a is Double) { 109 | return (a as Double).compareTo((b as Double)) 110 | } 111 | if (a is Long) { 112 | return (a as Long).compareTo((b as Long)) 113 | } 114 | if (a is Int) { 115 | return (a as Int).compareTo((b as Int)) 116 | } 117 | throw IllegalArgumentException("Unsupported type " + a.javaClass) 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/RectangleContent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import java.awt.Color 13 | import java.awt.Graphics2D 14 | import java.awt.Point 15 | import java.awt.geom.Rectangle2D 16 | import java.util.function.Supplier 17 | 18 | /** 19 | * A graphical item that can paint itself within an arbitrary two-dimensional 20 | * rectangle using the Java2D API. Implementations of this interface can range 21 | * from simple (for example, filling an area with a single color) to complex 22 | * (for example, drawing a detailed visualisation for a set of data). 23 | */ 24 | interface RectangleContent { 25 | /** 26 | * Draws the item within the specified bounds on the supplied Java2D target. 27 | * 28 | * @param g2 the graphics target (`null` not permitted). 29 | * @param bounds the bounds (`null` not permitted). 30 | */ 31 | fun draw(g2: Graphics2D, bounds: Rectangle2D, mousePosition: Point?) 32 | 33 | companion object { 34 | /** 35 | * An object that can fill an area with a single color. To be used as a background. 36 | * @param color the background color. 37 | * @return The blank rectangle content. 38 | */ 39 | fun blankCanvas(color: Supplier): RectangleContent { 40 | return object : RectangleContent { 41 | override fun draw(g2: Graphics2D, bounds: Rectangle2D, mousePosition: Point?) { 42 | g2.color = color.get() 43 | g2.fill(bounds) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/RectangleMargin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import java.awt.geom.Rectangle2D 13 | 14 | /** 15 | * An object representing rectangular padding or insets. Padding defines additional area 16 | * outside a given rectangle, and insets defines an area inside a given rectangle. 17 | * 18 | * @param top the top margin. 19 | * @param left the left margin. 20 | * @param bottom the bottom margin. 21 | * @param right the right margin. 22 | */ 23 | @JvmRecord 24 | data class RectangleMargin(val top: Double, val left: Double, val bottom: Double, val right: Double) { 25 | /** 26 | * Calculates a new rectangle by applying the margin as insets on the supplied 27 | * source. 28 | * 29 | * @param source the source rectangle (`null` not permitted). 30 | * @return The new rectangle. 31 | */ 32 | @JvmOverloads 33 | fun shrink(source: Rectangle2D, result: Rectangle2D? = null): Rectangle2D { 34 | return (result ?: source.clone() as Rectangle2D).also { 35 | applyInsets(it) 36 | } 37 | } 38 | 39 | /** 40 | * Directly updates the supplied rectangle, reducing its bounds by the margin amounts. 41 | * 42 | * @param rect the rectangle to be updated. 43 | */ 44 | fun applyInsets(rect: Rectangle2D?) { 45 | rect!!.setRect( 46 | rect.x + left, 47 | rect.y + top, 48 | rect.width - left - right, 49 | rect.height - top - bottom 50 | ) 51 | } 52 | 53 | /** 54 | * Increases (directly) the supplied rectangle by applying the margin to the bounds. 55 | * 56 | * @param rect the rectangle. 57 | */ 58 | fun applyMargin(rect: Rectangle2D) { 59 | rect.setRect( 60 | rect.x - left, 61 | rect.y - top, 62 | rect.width + left + right, 63 | rect.height + top + bottom 64 | ) 65 | } 66 | 67 | override fun equals(other: Any?): Boolean { 68 | if (this === other) return true 69 | if (other == null || javaClass != other.javaClass) return false 70 | val that = other as RectangleMargin 71 | return that.left.compareTo(left) == 0 72 | && that.right.compareTo(right) == 0 73 | && that.top.compareTo(top) == 0 74 | && that.bottom.compareTo(bottom) == 0 75 | } 76 | 77 | override fun hashCode(): Int { 78 | var result = top.hashCode() 79 | result = 31 * result + left.hashCode() 80 | result = 31 * result + bottom.hashCode() 81 | result = 31 * result + right.hashCode() 82 | return result 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/ToolTipComponentContributor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import java.awt.Point 13 | import java.awt.geom.Rectangle2D 14 | import javax.swing.* 15 | 16 | interface ToolTipComponentContributor { 17 | fun createToolTipComponent(chart: ChartSpecification, plotArea: Rectangle2D, mousePosition: Point?): JComponent? 18 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/XYDataset.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | import java.util.* 13 | import kotlin.math.max 14 | import kotlin.math.min 15 | 16 | /** 17 | * A dataset containing zero, one or many (x, y) data items. 18 | * 19 | * @param sourceItems the source items (`null` not permitted). 20 | * @param label The label for the dataset. 21 | */ 22 | open class XYDataset( 23 | sourceItems: List>, 24 | final override val label: String 25 | ) : ChartDataset { 26 | private val items: List> 27 | final override val rangeOfX: Range 28 | final override val rangeOfY: Range 29 | 30 | init { 31 | // verify that none of the x-values is null or NaN ot INF 32 | // and while doing that record the mins and maxes 33 | Objects.requireNonNull(sourceItems, "sourceItems must not be null") 34 | Objects.requireNonNull(label, "label must not be null") 35 | require(sourceItems.isNotEmpty()) { "sourceItems must not be empty" } 36 | this.items = ArrayList(sourceItems) 37 | var minX = Long.MAX_VALUE 38 | var maxX = Long.MIN_VALUE 39 | var minY = Double.POSITIVE_INFINITY 40 | var maxY = Double.NEGATIVE_INFINITY 41 | for (sourceItem in sourceItems) { 42 | Objects.requireNonNull(sourceItem, "elements of sourceItems must not be null") 43 | minX = min(minX.toDouble(), sourceItem.x.toDouble()).toLong() 44 | maxX = max(maxX.toDouble(), sourceItem.x.toDouble()).toLong() 45 | 46 | minY = min(minY, sourceItem.y) 47 | maxY = max(maxY, sourceItem.y) 48 | } 49 | this.rangeOfX = Range(minX, maxX) 50 | @Suppress("LeakingThis") 51 | this.rangeOfY = tweakYRange(Range(minY, maxY)) 52 | } 53 | 54 | protected open fun tweakYRange(yRange: Range): Range { 55 | return yRange 56 | } 57 | 58 | override val itemCount: Int 59 | get() = items.size 60 | 61 | override fun xyAt(index: Int): XY { 62 | return items[index] 63 | } 64 | 65 | override fun xAt(index: Int): Long { 66 | return items[index].x 67 | } 68 | 69 | override fun yAt(index: Int): Double { 70 | return items[index].y 71 | } 72 | 73 | /** 74 | * Compare with another instance, ignoring data points. 75 | * 76 | * Only compare the ranges and the label. 77 | * 78 | * @param other the object to compare with this instance. 79 | * @return whether two data set are assumed to be equal. 80 | */ 81 | override fun equals(other: Any?): Boolean { 82 | if (this === other) return true 83 | if (other == null || javaClass != other.javaClass) return false 84 | val xyDataset = other as XYDataset 85 | return rangeOfX == xyDataset.rangeOfX && rangeOfY == xyDataset.rangeOfY && label == xyDataset.label 86 | } 87 | 88 | /** 89 | * Hash this instance, ignoring data points. 90 | * 91 | * Only hashes the ranges and the label. 92 | * @return The hash. 93 | */ 94 | override fun hashCode(): Int { 95 | return Objects.hash(rangeOfX, rangeOfY, label) 96 | } 97 | 98 | /** 99 | * A pair of objects. 100 | * 101 | * @param the type of the first object. 102 | * @param the type of the second object. 103 | */ 104 | @JvmRecord 105 | data class XY(val x: X, val y: Y) 106 | } 107 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/charts/XYPercentageDataset.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.charts 11 | 12 | /** 13 | * A dataset containing zero, one or many (x, y) data items. 14 | * This dataset is specifically for datasets where Y is a percentage between 0 and 1. 15 | * @param sourceItems the source items (`null` not permitted). 16 | * @param label The label for the dataset. 17 | */ 18 | class XYPercentageDataset(sourceItems: List>, label: String) : XYDataset(sourceItems, label) { 19 | override fun tweakYRange(yRange: Range): Range { 20 | return Range(0.0, 1.0) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/ProfileContentPanel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr 11 | 12 | import io.github.bric3.fireplace.appDebug.FireplaceAppSystemProperties 13 | import io.github.bric3.fireplace.appDebug.FireplaceAppUIManagerProperties 14 | import io.github.bric3.fireplace.jfr.support.JFRLoaderBinder 15 | import io.github.bric3.fireplace.ui.ViewPanel 16 | import io.github.bric3.fireplace.ui.ViewPanel.Priority 17 | import io.github.classgraph.ClassGraph 18 | import java.awt.BorderLayout 19 | import javax.swing.* 20 | import javax.swing.tree.DefaultMutableTreeNode 21 | import javax.swing.tree.TreePath 22 | import javax.swing.tree.TreeSelectionModel 23 | 24 | 25 | internal class ProfileContentPanel(private val jfrBinder: JFRLoaderBinder) : JPanel(BorderLayout()) { 26 | private fun listViewPanels() = ClassGraph() 27 | .enableAllInfo() 28 | .acceptPackages(javaClass.packageName) 29 | .scan() 30 | .use { scanResult -> 31 | scanResult.getClassesImplementing(ViewPanel::class.java) 32 | .standardClasses 33 | .filter { !it.isAbstract } 34 | .asSequence() 35 | .map { it.loadClass(ViewPanel::class.java) } 36 | .sortedBy { it.getAnnotation(Priority::class.java)?.value ?: 100_000 } 37 | .map { it.getDeclaredConstructor(JFRLoaderBinder::class.java).newInstance(jfrBinder) } 38 | .toList() 39 | } 40 | 41 | private val views = buildList { 42 | addAll(listViewPanels()) 43 | 44 | if (FireplaceAppSystemProperties.isActive()) { 45 | add(FireplaceAppSystemProperties()) 46 | } 47 | if (FireplaceAppUIManagerProperties.isActive()) { 48 | add(FireplaceAppUIManagerProperties()) 49 | } 50 | }.associateByTo(LinkedHashMap()) { it.identifier } 51 | 52 | init { 53 | val view = JPanel(BorderLayout()) 54 | 55 | val model = DefaultMutableTreeNode().apply { 56 | views.keys.forEach { add(DefaultMutableTreeNode(it)) } 57 | } 58 | val jTree = JTree(model).apply { 59 | selectionModel.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION 60 | addTreeSelectionListener { e -> 61 | val lastPathComponent = e.path.lastPathComponent as DefaultMutableTreeNode 62 | views[lastPathComponent.userObject as String]?.let { 63 | view.removeAll() 64 | view.add(it.view) 65 | view.revalidate() 66 | view.repaint() 67 | } 68 | } 69 | selectionPath = TreePath(model.firstLeaf.path) 70 | minimumSize = preferredSize 71 | } 72 | 73 | JSplitPane(JSplitPane.HORIZONTAL_SPLIT, jTree, view).apply { 74 | }.also { 75 | add(it, BorderLayout.CENTER) 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/support/JFRLoaderBinder.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.support 11 | 12 | import io.github.bric3.fireplace.Utils 13 | import org.openjdk.jmc.common.item.IItemCollection 14 | import org.openjdk.jmc.flightrecorder.CouldNotLoadRecordingException 15 | import org.openjdk.jmc.flightrecorder.JfrLoaderToolkit 16 | import java.io.IOException 17 | import java.io.UncheckedIOException 18 | import java.nio.file.Path 19 | import java.util.concurrent.CompletableFuture 20 | import java.util.function.Consumer 21 | import java.util.function.Function 22 | import java.util.function.Supplier 23 | import java.util.stream.Collectors.toUnmodifiableList 24 | import javax.swing.* 25 | 26 | class JFRLoaderBinder { 27 | private val eventsBinders: MutableList> = mutableListOf() 28 | private val pathsBinders: MutableList>> = mutableListOf() 29 | private lateinit var onLoadStart: Runnable 30 | private lateinit var onLoadEnd: Runnable 31 | private lateinit var eventSupplierFuture: CompletableFuture> 32 | private val jfrPaths = mutableListOf() 33 | 34 | /** 35 | * Bind a component to computed events. 36 | * 37 | * The [provider] function is called with the events as parameter and runs on the fork/join pool. 38 | * The [componentUpdate] function is called with the result of the [provider] function and runs on the EDT. 39 | */ 40 | fun bindEvents(provider: Function, componentUpdate: Consumer) { 41 | val eventBinder: (IItemCollection) -> Unit = { events: IItemCollection -> 42 | CompletableFuture.supplyAsync { provider.apply(events) } 43 | .whenComplete { result, throwable -> 44 | if (throwable != null) { 45 | System.err.println("Error while computing events of $jfrPaths") 46 | throwable.printStackTrace() 47 | } else { 48 | SwingUtilities.invokeLater { componentUpdate.accept(result) } 49 | } 50 | } 51 | } 52 | eventsBinders.add(eventBinder) 53 | 54 | // run immediately if future already done 55 | if ((this::eventSupplierFuture.isInitialized && eventSupplierFuture.isDone)) { 56 | eventSupplierFuture.thenAcceptAsync { eventSupplier -> 57 | eventBinder(eventSupplier.get()) 58 | } 59 | } 60 | } 61 | 62 | fun bindPaths(pathsBinder: Consumer>) { 63 | pathsBinders.add { paths -> SwingUtilities.invokeLater { pathsBinder.accept(paths) } } 64 | } 65 | 66 | internal fun loadJfrFiles(jfrPaths: List) { 67 | if (jfrPaths.isEmpty()) { 68 | return 69 | } 70 | this.jfrPaths.run { 71 | clear() 72 | addAll(jfrPaths) 73 | } 74 | onLoadStart.run() 75 | CompletableFuture.runAsync { 76 | pathsBinders.forEach { it.accept(jfrPaths) } 77 | } 78 | 79 | eventSupplierFuture = CompletableFuture.supplyAsync { 80 | val jfrFiles = jfrPaths.stream() 81 | .peek { path -> println("Loading $path") } 82 | .map { path -> path.toFile() } 83 | .collect(toUnmodifiableList()) 84 | 85 | return@supplyAsync Utils.memoize { 86 | CompletableFuture.supplyAsync { 87 | val events: IItemCollection = try { 88 | JfrLoaderToolkit.loadEvents(jfrFiles) 89 | } catch (ioe: IOException) { 90 | throw UncheckedIOException(ioe) 91 | } catch (e1: CouldNotLoadRecordingException) { 92 | throw RuntimeException(e1) 93 | } 94 | 95 | if (Utils.isDebugging) { 96 | TypeCategoryExtractor.extract(events) 97 | .forEach { (type, category) -> println("$type -> $category") } 98 | } 99 | 100 | events 101 | }.join() 102 | } 103 | } 104 | 105 | eventSupplierFuture.thenAcceptAsync { eventSupplier -> 106 | eventsBinders.forEach { binder -> binder.accept(eventSupplier.get()) } 107 | }.whenCompleteAsync { _, throwable -> 108 | throwable?.printStackTrace() 109 | onLoadEnd.run() 110 | } 111 | } 112 | 113 | internal fun setOnLoadActions(onLoadStart: Runnable?, onLoadEnd: Runnable?) { 114 | this.onLoadStart = Runnable { SwingUtilities.invokeLater(onLoadStart) } 115 | this.onLoadEnd = Runnable { SwingUtilities.invokeLater(onLoadEnd) } 116 | } 117 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/support/JfrAnalyzer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.support 11 | 12 | import org.openjdk.jmc.common.item.IAccessorKey 13 | import org.openjdk.jmc.common.item.IItem 14 | import org.openjdk.jmc.common.item.IItemCollection 15 | import org.openjdk.jmc.common.item.IItemIterable 16 | import org.openjdk.jmc.common.item.ItemFilters 17 | import org.openjdk.jmc.common.item.ItemToolkit 18 | import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes 19 | import org.openjdk.jmc.flightrecorder.jdk.JdkFilters 20 | import java.util.function.Consumer 21 | 22 | object JfrAnalyzer { 23 | @JvmStatic 24 | fun allocInOutTlab(events: IItemCollection): IItemCollection { 25 | return events.apply(JdkFilters.ALLOC_ALL) 26 | } 27 | 28 | @JvmStatic 29 | fun executionSamples(events: IItemCollection): IItemCollection { 30 | return events.apply(JdkFilters.EXECUTION_SAMPLE) 31 | } 32 | 33 | private fun otherEvents(events: IItemCollection) { 34 | events.apply( 35 | ItemFilters.type( 36 | setOf( 37 | "jdk.CPUInformation", 38 | "jdk.OSInformation", 39 | "jdk.ActiveRecording", 40 | // "jdk.ActiveSetting", // async profiler settings ? 41 | "jdk.JVMInformation" 42 | ) 43 | ) 44 | ).forEach { eventsCollection: IItemIterable -> 45 | eventsCollection.stream().limit(10).forEach { event: IItem -> 46 | println( 47 | """ 48 | ${event.type.identifier} 49 | """.trimIndent() 50 | ) 51 | val itemType = ItemToolkit.getItemType(event) 52 | itemType.accessorKeys.keys.forEach(Consumer { accessorKey: IAccessorKey<*> -> 53 | println("${accessorKey.identifier}=${itemType.getAccessor(accessorKey).getMember(event)}") 54 | }) 55 | } 56 | } 57 | } 58 | 59 | @JvmStatic 60 | fun jvmSystemProperties(events: IItemCollection): Map { 61 | return buildMap { 62 | events.apply( 63 | ItemFilters.type( 64 | setOf( 65 | "jdk.InitialSystemProperty" 66 | ) 67 | ) 68 | ).forEach { eventsCollection: IItemIterable -> 69 | val keyAccessor = eventsCollection.type.getAccessor(JdkAttributes.ENVIRONMENT_KEY.key) 70 | val valueAccessor = eventsCollection.type.getAccessor(JdkAttributes.ENVIRONMENT_VALUE.key) 71 | eventsCollection.stream().forEach { event: IItem -> 72 | put( 73 | keyAccessor.getMember(event), 74 | valueAccessor.getMember(event) 75 | ) 76 | } 77 | } 78 | } 79 | } 80 | 81 | @JvmStatic 82 | fun nativeLibraries(events: IItemCollection): List { 83 | return buildList { 84 | events.apply( 85 | ItemFilters.type( 86 | setOf( 87 | "jdk.NativeLibrary" 88 | ) 89 | ) 90 | ).forEach { eventsCollection: IItemIterable -> 91 | val nativeLibNameAccessor = eventsCollection.type.getAccessor( 92 | JdkAttributes.NATIVE_LIBRARY_NAME.key 93 | ) 94 | eventsCollection.stream() 95 | .forEach { event: IItem -> 96 | this.add(nativeLibNameAccessor.getMember(event)) 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/support/JfrFilesDropHandler.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.support 11 | 12 | import io.github.bric3.fireplace.ui.toolkit.DragAndDropTarget 13 | import java.awt.datatransfer.DataFlavor 14 | import java.awt.datatransfer.UnsupportedFlavorException 15 | import java.awt.dnd.DropTarget 16 | import java.awt.dnd.DropTargetAdapter 17 | import java.awt.dnd.DropTargetDragEvent 18 | import java.awt.dnd.DropTargetDropEvent 19 | import java.awt.dnd.DropTargetEvent 20 | import java.io.File 21 | import java.io.IOException 22 | import java.nio.file.Path 23 | import java.util.* 24 | import java.util.function.Consumer 25 | import javax.swing.* 26 | 27 | internal class JfrFilesDropHandler private constructor(private val pathsHandler: Consumer>) : 28 | TransferHandler() { 29 | override fun canImport(support: TransferSupport): Boolean { 30 | return support.dataFlavors.any(DataFlavor::isFlavorJavaFileListType) 31 | } 32 | 33 | override fun importData(support: TransferSupport): Boolean { 34 | if (!this.canImport(support)) { 35 | return false 36 | } 37 | @Suppress("UNCHECKED_CAST") 38 | val files = try { 39 | support.transferable.getTransferData(DataFlavor.javaFileListFlavor) as List 40 | } catch (ex: UnsupportedFlavorException) { 41 | ex.printStackTrace() 42 | return false 43 | } catch (ex: IOException) { 44 | ex.printStackTrace() 45 | return false 46 | } 47 | val isJfrFiles = files.all { it.isFile && it.name.endsWith(".jfr") } 48 | if (!isJfrFiles) { 49 | return false 50 | } 51 | pathsHandler.accept(files.map(File::toPath)) 52 | return true 53 | } 54 | 55 | companion object { 56 | fun install(pathsHandler: Consumer>, parent: JComponent, target: DragAndDropTarget) { 57 | try { 58 | parent.dropTarget = DropTarget(parent, object : DropTargetAdapter() { 59 | override fun dragEnter(dtde: DropTargetDragEvent) { 60 | if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { 61 | target.activate() 62 | } 63 | } 64 | 65 | override fun drop(dtde: DropTargetDropEvent) { 66 | /* no-op */ 67 | } 68 | }) 69 | target.component.apply { 70 | transferHandler = JfrFilesDropHandler(pathsHandler) 71 | dropTarget.addDropTargetListener(object : DropTargetAdapter() { 72 | override fun dragExit(dte: DropTargetEvent) { 73 | target.deactivate() 74 | } 75 | 76 | override fun drop(dtde: DropTargetDropEvent) { 77 | target.deactivate() 78 | } 79 | 80 | override fun dropActionChanged(dtde: DropTargetDragEvent) { 81 | target.deactivate() 82 | } 83 | }) 84 | } 85 | } catch (e: TooManyListenersException) { 86 | e.printStackTrace() 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/support/JfrFrameColorMode.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.support 11 | 12 | import io.github.bric3.fireplace.core.ui.LightDarkColor 13 | import io.github.bric3.fireplace.flamegraph.ColorMapper 14 | import io.github.bric3.fireplace.flamegraph.FrameBox 15 | import org.openjdk.jmc.common.IMCFrame 16 | import org.openjdk.jmc.flightrecorder.stacktrace.tree.Node 17 | import java.awt.Color 18 | import java.util.function.Function 19 | import java.util.regex.Pattern 20 | 21 | /** 22 | * A JFR specific color mapping modes. 23 | */ 24 | enum class JfrFrameColorMode { 25 | BY_PACKAGE { 26 | private val runtimePrefixes = Pattern.compile("(java\\.|javax\\.|sun\\.|com\\.sun\\.|com\\.oracle\\.|com\\.ibm\\.|jdk\\.)") 27 | public override fun getJfrNodeColor(colorMapper: ColorMapper, frameNode: Node): Color { 28 | if (frameNode.isRoot) { 29 | return rootNodeColor 30 | } 31 | val frame = frameNode.frame 32 | if (frame.type === IMCFrame.Type.UNKNOWN) { 33 | return undefinedColor 34 | } 35 | val type = frame.type 36 | if (type == IMCFrame.Type.NATIVE || type == IMCFrame.Type.KERNEL || type == IMCFrame.Type.CPP) { 37 | return runtimeColor 38 | } 39 | val name = frame.method.type.getPackage().name 40 | if (name != null && runtimePrefixes.matcher(name).lookingAt()) { 41 | return runtimeColor 42 | } 43 | return colorMapper.apply(name) 44 | } 45 | }, 46 | BY_MODULE { 47 | public override fun getJfrNodeColor(colorMapper: ColorMapper, frameNode: Node): Color { 48 | return if (frameNode.isRoot) { 49 | rootNodeColor 50 | } else { 51 | colorMapper.apply(frameNode.frame.method.type.getPackage().module) 52 | } 53 | } 54 | }, 55 | BY_FRAME_TYPE { 56 | public override fun getJfrNodeColor(colorMapper: ColorMapper, frameNode: Node): Color { 57 | if (frameNode.isRoot) { 58 | return rootNodeColor 59 | } 60 | return when (frameNode.frame.type) { 61 | IMCFrame.Type.INTERPRETED -> interpretedColor 62 | IMCFrame.Type.INLINED -> inlinedColor 63 | IMCFrame.Type.JIT_COMPILED -> jitCompiledColor 64 | else -> undefinedColor 65 | } 66 | } 67 | }; 68 | 69 | protected abstract fun getJfrNodeColor(colorMapper: ColorMapper, frameNode: Node): Color 70 | fun colorMapperUsing(colorMapper: ColorMapper): Function, Color> { 71 | return Function { frameNode -> getJfrNodeColor(colorMapper, frameNode.actualNode) } 72 | } 73 | 74 | companion object { 75 | var rootNodeColor = LightDarkColor( 76 | 0xFF_77_4A_A4.toInt(), 77 | 0xFF_56_1D_8C.toInt() 78 | ) 79 | var runtimeColor = LightDarkColor( 80 | 0xFF_D1_D4_DE.toInt(), 81 | 0xFF_3B_39_3D.toInt() 82 | ) 83 | var undefinedColor = Color(108, 163, 189) 84 | var jitCompiledColor = Color(21, 110, 64) 85 | var inlinedColor: Color = Color.pink 86 | var interpretedColor: Color = Color.orange 87 | } 88 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/support/JfrFrameNodeConverter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.support 11 | 12 | import io.github.bric3.fireplace.flamegraph.FrameBox 13 | import org.openjdk.jmc.flightrecorder.stacktrace.tree.Node 14 | import org.openjdk.jmc.flightrecorder.stacktrace.tree.StacktraceTreeModel 15 | 16 | /** 17 | * Creates an array of FlameNodes that live in the [0.0, 1.0] world space on the X axis and the depth of the stack representing 18 | * the Y axis. 19 | * A child node will be proportional to its parent's X space according to its proportion of time it took of its parent's time. 20 | * The root of the flame graph will always be full width. 21 | */ 22 | object JfrFrameNodeConverter { 23 | fun convert(model: StacktraceTreeModel): List> { 24 | val nodes = mutableListOf>() 25 | FrameBox.flattenAndCalculateCoordinate( 26 | nodes, 27 | model.root, 28 | Node::getChildren, 29 | Node::getCumulativeWeight, 30 | { it.children.stream().mapToDouble(Node::getCumulativeWeight).sum() }, 31 | 0.0, 32 | 1.0, 33 | 0 34 | ) 35 | assert(nodes[0].actualNode.isRoot) { "First node should be the root node" } 36 | return nodes 37 | } 38 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/views/events/EventTypesByCategoryTreeModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.views.events 11 | 12 | import io.github.bric3.fireplace.jfr.support.TypeCategory 13 | import io.github.bric3.fireplace.jfr.support.TypeCategoryExtractor 14 | import io.github.bric3.fireplace.core.ui.Colors 15 | import org.openjdk.jmc.common.item.IItemCollection 16 | import org.openjdk.jmc.common.item.IType 17 | import java.text.DecimalFormat 18 | import java.util.* 19 | import javax.swing.tree.DefaultMutableTreeNode 20 | import javax.swing.tree.DefaultTreeModel 21 | import javax.swing.tree.MutableTreeNode 22 | 23 | internal class EventTypesByCategoryTreeModel : DefaultTreeModel( 24 | SortedTreeNode(Comparator.comparing(CategoryOrType::category)) 25 | ) { 26 | var typeToCategory: Map, TypeCategory> = mapOf() 27 | 28 | internal class CategoryOrType(val category: String) { 29 | internal var eventCount = 0L 30 | 31 | 32 | override fun toString(): String { 33 | // poor 's man renderer 34 | return if (eventCount > 0) { 35 | "$category (${DF.format(eventCount)})" 38 | } else { 39 | category 40 | } 41 | } 42 | 43 | companion object { 44 | private val DF = DecimalFormat("#,##0") 45 | } 46 | } 47 | 48 | private fun fetchOrAdd(categorisation: List): SortedTreeNode { 49 | @Suppress("UNCHECKED_CAST") // We know the type of the root 50 | var current = getRoot() as SortedTreeNode 51 | for (category in categorisation) { 52 | current = fetchOrAdd(category, current) 53 | } 54 | return current 55 | } 56 | 57 | private fun fetchOrAdd(category: String, node: MutableTreeNode): SortedTreeNode { 58 | for (i in 0 until node.childCount) { 59 | @Suppress("UNCHECKED_CAST") // Only inserting nodes of type CategoryOrType 60 | val child = node.getChildAt(i) as SortedTreeNode 61 | val content = child.userObject 62 | if (category == content.category) { 63 | return child 64 | } 65 | } 66 | val content = CategoryOrType(category) 67 | return SortedTreeNode(Comparator.comparing(CategoryOrType::category), content).also { 68 | node.insert(it, node.childCount) 69 | } 70 | } 71 | 72 | fun populateTree(events: IItemCollection) { 73 | typeToCategory = TypeCategoryExtractor.extract(events) 74 | typeToCategory.forEach { (eventType, eventTypeDetails) -> 75 | if (eventTypeDetails.count == 0L) { 76 | return@forEach 77 | } 78 | val parentCategory = fetchOrAdd(eventTypeDetails.categories) 79 | fetchOrAdd(eventType.name, parentCategory).also { 80 | it.userObject.eventCount = eventTypeDetails.count 81 | } 82 | } 83 | // fire change event 84 | nodeStructureChanged(root) 85 | } 86 | } 87 | 88 | private class SortedTreeNode : DefaultMutableTreeNode { 89 | private val comparator: Comparator? 90 | 91 | constructor(comparator: Comparator? = null) : super() { 92 | this.comparator = makeComparator(comparator) 93 | } 94 | 95 | constructor(comparator: Comparator?, userObject: T) : super(userObject) { 96 | this.comparator = makeComparator(comparator) 97 | } 98 | 99 | constructor(comparator: Comparator?, userObject: T, allowsChildren: Boolean) : super( 100 | userObject, 101 | allowsChildren 102 | ) { 103 | this.comparator = makeComparator(comparator) 104 | } 105 | 106 | override fun insert(newChild: MutableTreeNode, childIndex: Int) { 107 | super.insert(newChild, childIndex) 108 | 109 | comparator?.let { 110 | // This code only inserts `DefaultMutableTreeNode` 111 | @Suppress("UNCHECKED_CAST") 112 | Collections.sort(children as Vector, it) 113 | } 114 | } 115 | 116 | private fun makeComparator(comparator: Comparator?): Comparator? { 117 | comparator ?: return null 118 | // So MutableTreeNode is part of the `insert` signature, but 119 | // `getUserObject` is not part of the `MutableTreeNode` interface. 120 | return Comparator.comparing( 121 | { tn: MutableTreeNode -> 122 | @Suppress("UNCHECKED_CAST") 123 | (tn as DefaultMutableTreeNode).userObject as T 124 | }, 125 | comparator 126 | ) 127 | } 128 | 129 | @Suppress("UNCHECKED_CAST") 130 | override fun getUserObject(): T { 131 | return super.getUserObject() as T 132 | } 133 | } 134 | 135 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/views/events/EventsTableModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.views.events 11 | 12 | import io.github.bric3.fireplace.jfr.support.formatValue 13 | import io.github.bric3.fireplace.jfr.support.getMemberFromEvent 14 | import org.openjdk.jmc.common.IDescribable 15 | import org.openjdk.jmc.common.item.* 16 | import org.openjdk.jmc.flightrecorder.JfrAttributes 17 | import javax.swing.table.AbstractTableModel 18 | 19 | internal class EventsTableModel : AbstractTableModel() { 20 | private data class AttributeDescriptor(val accessorKey: IAccessorKey<*>, val describable: IDescribable) { 21 | override fun hashCode(): Int { 22 | return accessorKey.hashCode() 23 | } 24 | override fun equals(other: Any?): Boolean { 25 | return other is AttributeDescriptor && other.accessorKey == accessorKey 26 | } 27 | } 28 | 29 | private lateinit var commonFields: List 30 | private var singleTypeEvents: List = listOf() 31 | private var showEventTypesColumn: Boolean = false 32 | 33 | var singleTypeEventCollection: IItemCollection = ItemCollectionToolkit.EMPTY 34 | set(events) { 35 | field = events 36 | val eventPerTypeIterable = field.stream().toList() 37 | val eventTypes = eventPerTypeIterable.map { it.type }.distinct() 38 | 39 | this.singleTypeEvents = eventPerTypeIterable.flatten() 40 | 41 | extractEventFields(eventTypes) 42 | fireTableStructureChanged() 43 | } 44 | 45 | override fun getColumnName(column: Int): String? { 46 | if (singleTypeEvents.isEmpty()) { 47 | return null 48 | } 49 | if (column < 0) return null 50 | if (column == commonFields.size && showEventTypesColumn) { 51 | return "Event Type" 52 | } 53 | return commonFields[column].describable.name 54 | } 55 | 56 | private fun extractEventFields(types: List>) { 57 | if (types.isEmpty()) { 58 | return 59 | } 60 | 61 | commonFields = types.map { type -> 62 | type.accessorKeys.filterKeys { 63 | // if (Utils.isDebugging()) { 64 | // println("attr ids: " + it.identifier) 65 | // } 66 | it != JfrAttributes.EVENT_STACKTRACE.key && it != JfrAttributes.EVENT_TYPE.key 67 | }.mapTo(LinkedHashSet()) { AttributeDescriptor(it.key, it.value) } 68 | }.reduce { acc, list -> 69 | acc.also { 70 | it.retainAll(list) 71 | } 72 | }.toList() 73 | 74 | showEventTypesColumn = types.size > 1 75 | } 76 | 77 | override fun getRowCount(): Int { 78 | return singleTypeEvents.size 79 | } 80 | 81 | override fun getColumnCount(): Int { 82 | if (singleTypeEvents.isEmpty()) { 83 | return 0 84 | } 85 | return commonFields.size + if (showEventTypesColumn) 1 else 0 86 | } 87 | 88 | override fun getValueAt(rowIndex: Int, columnIndex: Int): Any? { 89 | singleTypeEvents[rowIndex].let { 90 | if (columnIndex == commonFields.size && showEventTypesColumn) { 91 | return it.type.name 92 | } 93 | 94 | val descriptor = commonFields[columnIndex] 95 | val value = it.type.getAccessor(descriptor.accessorKey).getMemberFromEvent(it) 96 | 97 | // if (Utils.isDebugging()) { 98 | // println("${descriptor.accessorKey.contentType} -> ${if (value == null) null else value::class.java}") 99 | // } 100 | 101 | return descriptor.accessorKey.contentType.defaultFormatter.formatValue(value) 102 | } 103 | } 104 | 105 | fun getAtRow(selectedIndex: Int): IItem? { 106 | return singleTypeEvents.getOrNull(selectedIndex) 107 | } 108 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/views/events/SingleEventAttributesTableModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.views.events 11 | 12 | import io.github.bric3.fireplace.jfr.support.formatValue 13 | import io.github.bric3.fireplace.jfr.support.getMemberFromEvent 14 | import org.openjdk.jmc.common.IDescribable 15 | import org.openjdk.jmc.common.item.IAccessorKey 16 | import org.openjdk.jmc.common.item.IItem 17 | import org.openjdk.jmc.flightrecorder.JfrAttributes 18 | import javax.swing.table.AbstractTableModel 19 | 20 | internal class SingleEventAttributesTableModel(private var event: IItem?) : AbstractTableModel() { 21 | private var fields: List, IDescribable>> = listOf() 22 | 23 | init { 24 | refreshFields() 25 | } 26 | 27 | private fun refreshFields() { 28 | val event = event ?: return 29 | 30 | // if (Utils.isDebugging()) { 31 | // event.type.accessorKeys.entries.forEach { 32 | // it.key.identifier 33 | // val valueDescriptor = it.value.name 34 | // println("${it.key.identifier} + $valueDescriptor") 35 | // } 36 | // } 37 | 38 | fields = event.type.accessorKeys.filterKeys { 39 | //if (Utils.isDebugging()) { 40 | // println(it.identifier) 41 | //} 42 | it != JfrAttributes.EVENT_STACKTRACE.key && it != JfrAttributes.EVENT_TYPE.key 43 | }.toList() 44 | } 45 | 46 | fun setRecordedEvent(event: IItem?) { 47 | this.event = event 48 | refreshFields() 49 | fireTableDataChanged() 50 | } 51 | 52 | override fun getRowCount(): Int { 53 | return if (event == null) 0 else fields.size 54 | } 55 | 56 | override fun getColumnCount(): Int { 57 | return 2 58 | } 59 | 60 | override fun getValueAt(rowIndex: Int, columnIndex: Int): Any? { 61 | val event = event ?: return "" 62 | val descriptor = fields[rowIndex] 63 | if (columnIndex == 0) { 64 | return descriptor.second.name 65 | } else if (columnIndex == 1) { 66 | val value = event.type.getAccessor(descriptor.first).getMemberFromEvent(event) 67 | return descriptor.first.contentType.defaultFormatter.formatValue(value) 68 | } 69 | return null 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/views/events/StackFrameTableModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.views.events 11 | 12 | import io.github.bric3.fireplace.jfr.support.getMemberFromEvent 13 | import org.openjdk.jmc.common.IMCStackTrace 14 | import org.openjdk.jmc.common.item.IItem 15 | import org.openjdk.jmc.flightrecorder.JfrAttributes 16 | import javax.swing.table.AbstractTableModel 17 | 18 | internal class StackFrameTableModel : AbstractTableModel() { 19 | private var stackTrace: IMCStackTrace? = null 20 | fun setRecordedStackTrace(event: IItem?) { 21 | stackTrace = if (event != null) { 22 | event.type 23 | .getAccessor(JfrAttributes.EVENT_STACKTRACE.key) 24 | ?.getMemberFromEvent(event) as? IMCStackTrace 25 | } else { 26 | null 27 | } 28 | fireTableDataChanged() 29 | } 30 | 31 | override fun getRowCount(): Int { 32 | return stackTrace?.frames?.size ?: return 0 33 | } 34 | 35 | override fun getColumnCount(): Int { 36 | return 5 37 | } 38 | 39 | override fun getColumnName(column: Int): String { 40 | return when(column) { 41 | 0 -> "Frame" 42 | 1 -> "Line" 43 | 2 -> "Byte Code Index" 44 | 3 -> "Frame Type" 45 | 4 -> "Hidden" 46 | else -> throw IllegalArgumentException("Unknown column $column") 47 | } 48 | } 49 | 50 | override fun getValueAt(rowIndex: Int, columnIndex: Int): Any? { 51 | val imcStackTrace = stackTrace ?: return null 52 | val frame = imcStackTrace.frames[rowIndex] 53 | 54 | return when (columnIndex) { 55 | 0 -> "${frame.method.type.typeName}.${frame.method.methodName}" 56 | 1 -> frame.frameLineNumber 57 | 2 -> frame.bci 58 | 3 -> frame.type 59 | 4 -> frame.method.isHidden 60 | else -> throw IllegalArgumentException("Unknown column $columnIndex") 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/views/general/NativeLibraries.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.views.general 11 | 12 | import io.github.bric3.fireplace.jfr.support.JFRLoaderBinder 13 | import io.github.bric3.fireplace.jfr.support.JfrAnalyzer 14 | import io.github.bric3.fireplace.ui.PROCESS_INFO_BASE 15 | import io.github.bric3.fireplace.ui.ViewPanel 16 | import io.github.bric3.fireplace.ui.ViewPanel.Priority 17 | import io.github.bric3.fireplace.ui.toolkit.simpleReadOnlyTable 18 | import io.github.bric3.fireplace.ui.toolkit.unwrappedTable 19 | 20 | @Priority(PROCESS_INFO_BASE + 2) 21 | class NativeLibraries(private val jfrBinder: JFRLoaderBinder) : ViewPanel { 22 | override val identifier = "Native libraries" 23 | 24 | override val view by lazy { 25 | simpleReadOnlyTable( 26 | arrayOf(), 27 | arrayOf("Path") 28 | ).apply { 29 | jfrBinder.bindEvents( 30 | JfrAnalyzer::nativeLibraries 31 | ) { libs -> 32 | unwrappedTable().model.setData( 33 | libs.map { arrayOf(it) }.toTypedArray() 34 | ) 35 | } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/views/general/SystemProperties.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.views.general 11 | 12 | import io.github.bric3.fireplace.jfr.support.JFRLoaderBinder 13 | import io.github.bric3.fireplace.jfr.support.JfrAnalyzer 14 | import io.github.bric3.fireplace.ui.PROCESS_INFO_BASE 15 | import io.github.bric3.fireplace.ui.ViewPanel 16 | import io.github.bric3.fireplace.ui.ViewPanel.Priority 17 | import io.github.bric3.fireplace.ui.toolkit.simpleReadOnlyTable 18 | import io.github.bric3.fireplace.ui.toolkit.unwrappedTable 19 | import javax.swing.* 20 | 21 | @Priority(PROCESS_INFO_BASE + 1) 22 | class SystemProperties(private val jfrBinder: JFRLoaderBinder) : ViewPanel { 23 | override val identifier = "System properties" 24 | 25 | override val view: JComponent by lazy { 26 | simpleReadOnlyTable( 27 | arrayOf(), 28 | arrayOf("Key", "Value") 29 | ).apply { 30 | jfrBinder.bindEvents( 31 | JfrAnalyzer::jvmSystemProperties 32 | ) { props -> 33 | unwrappedTable().model.setData( 34 | props.map { arrayOf(it.key, it.value) }.toTypedArray() 35 | ) 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/jfr/views/memory/Allocations.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.jfr.views.memory 11 | 12 | import io.github.bric3.fireplace.jfr.support.JFRLoaderBinder 13 | import io.github.bric3.fireplace.jfr.support.JfrAnalyzer 14 | import io.github.bric3.fireplace.ui.MEMORY_BASE 15 | import io.github.bric3.fireplace.ui.ThreadFlamegraphView 16 | import io.github.bric3.fireplace.ui.ViewPanel.Priority 17 | 18 | @Priority(MEMORY_BASE + 1) 19 | class Allocations(jfrBinder: JFRLoaderBinder) : ThreadFlamegraphView(jfrBinder) { 20 | override val identifier = "Allocations" 21 | 22 | override val eventSelector = JfrAnalyzer::allocInOutTlab 23 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/ViewPanel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui 11 | 12 | import javax.swing.* 13 | 14 | interface ViewPanel { 15 | annotation class Priority(val value: Int) 16 | val identifier: String 17 | val view: JComponent 18 | } 19 | 20 | const val CPU_BASE = 100 21 | const val MEMORY_BASE = 200 22 | const val PROCESS_INFO_BASE = 300 23 | const val JVM_BASE = 400 24 | const val JFR_BASE = 900 25 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/ColorIcon.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import io.github.bric3.fireplace.charts.withAlpha 13 | import java.awt.Color 14 | import java.awt.Component 15 | import java.awt.Graphics 16 | import java.awt.Graphics2D 17 | import java.awt.RenderingHints 18 | import javax.swing.* 19 | 20 | open class ColorIcon( 21 | private val width: Int, 22 | private val height: Int, 23 | private val colorWidth: Int, 24 | private val colorHeight: Int, 25 | private val color: Color, 26 | private val isPaintBorder: Boolean, 27 | private val arc: Int = 6 28 | ) : Icon { 29 | constructor(size: Int, colorSize: Int, color: Color, border: Boolean) : this( 30 | size, 31 | size, 32 | colorSize, 33 | colorSize, 34 | color, 35 | border, 36 | 6 37 | ) 38 | 39 | constructor(size: Int, color: Color, border: Boolean = false) : this(size, size, color, border) 40 | 41 | override fun paintIcon(component: Component, g: Graphics, i: Int, j: Int) { 42 | val g2d = g.create() as Graphics2D 43 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) 44 | g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE) 45 | g2d.color = color 46 | 47 | val width = colorWidth 48 | val height = colorHeight 49 | val arc = arc 50 | val x = i + (this.width - width) / 2 51 | val y = j + (this.height - height) / 2 52 | 53 | g2d.fillRoundRect(x, y, width, height, arc, arc) 54 | 55 | if (isPaintBorder) { 56 | g2d.color = Color(0, true).withAlpha(40) // TODO put in Colors 57 | g2d.drawRoundRect(x, y, width, height, arc, arc) 58 | } 59 | 60 | g2d.dispose() 61 | } 62 | 63 | override fun getIconWidth(): Int { 64 | return width 65 | } 66 | 67 | override fun getIconHeight(): Int { 68 | return height 69 | } 70 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/DragAndDropTarget.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import javax.swing.JComponent 13 | 14 | interface DragAndDropTarget { 15 | val component: JComponent 16 | fun activate() 17 | fun deactivate() 18 | } 19 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/FrameResizeLabel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import io.github.bric3.fireplace.core.ui.Colors 13 | import io.github.bric3.fireplace.core.ui.LightDarkColor 14 | import java.awt.BorderLayout 15 | import java.awt.Dimension 16 | import java.awt.event.ComponentAdapter 17 | import java.awt.event.ComponentEvent 18 | import javax.swing.* 19 | 20 | class FrameResizeLabel { 21 | private val dimensionLabel: JLabel = JLabel("hello").apply { 22 | verticalAlignment = JLabel.CENTER 23 | horizontalAlignment = JLabel.CENTER 24 | isOpaque = true 25 | border = BorderFactory.createLineBorder( 26 | LightDarkColor( 27 | Colors.panelForeground, 28 | Colors.panelBackground 29 | ) 30 | ) 31 | } 32 | private val dimensionOverlayPanel: JPanel = JPanel(BorderLayout()).apply { 33 | add(dimensionLabel, BorderLayout.CENTER) 34 | background = LightDarkColor( 35 | Colors.translucent_white_D0, 36 | Colors.translucent_black_80 37 | ) 38 | isOpaque = false 39 | isVisible = false 40 | } 41 | private val panelHider: Timer 42 | 43 | init { 44 | dimensionOverlayPanel.maximumSize = dimensionLabel.getFontMetrics(dimensionLabel.font).let { 45 | val border = 10 46 | val textWidth = it.stringWidth("1000 x 1000") + 10 47 | val textHeight = it.height 48 | Dimension(textWidth + border, textHeight + border) 49 | } 50 | 51 | panelHider = Timer(2000) { _ -> dimensionOverlayPanel.isVisible = false }.apply { 52 | isCoalesce = true 53 | } 54 | } 55 | 56 | fun installListener(frame: JFrame) { 57 | frame.addComponentListener(object : ComponentAdapter() { 58 | override fun componentResized(e: ComponentEvent) { 59 | val height = frame.height 60 | val width = frame.width 61 | dimensionLabel.text = "$height x $width" 62 | dimensionOverlayPanel.isVisible = true 63 | panelHider.restart() 64 | } 65 | }) 66 | } 67 | 68 | val component: JComponent 69 | get() = dimensionOverlayPanel 70 | } 71 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/Hud.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import java.awt.BorderLayout 13 | import java.awt.GridBagLayout 14 | import javax.swing.* 15 | 16 | 17 | class Hud(private val mainComponent: JComponent) { 18 | private val backgroundPainter = Painter.compose( 19 | Painter.blurOf(mainComponent), 20 | Painter.translucent() 21 | ) 22 | 23 | private val dndPanel: JPanel = JPanelWithPainter( 24 | GridBagLayout(), 25 | backgroundPainter 26 | ).apply { 27 | add(JLabel("Drag and drop JFR file here")) 28 | isOpaque = false 29 | isVisible = false 30 | } 31 | 32 | private val progressPanel: JPanel = JPanelWithPainter( 33 | BorderLayout(), 34 | backgroundPainter 35 | ).apply { 36 | add( 37 | JLabel("Loading in progress", SwingConstants.CENTER), 38 | BorderLayout.CENTER 39 | ) 40 | val progress = JProgressBar().apply { 41 | isIndeterminate = true 42 | } 43 | add(progress, BorderLayout.SOUTH) 44 | } 45 | 46 | private val hudPanel: JPanel = JPanel().apply { 47 | layout = BoxLayout(this, BoxLayout.Y_AXIS) 48 | add(dndPanel) 49 | add(progressPanel) 50 | isOpaque = false 51 | } 52 | 53 | private val frameResizeLabel = FrameResizeLabel() 54 | val component: JComponent 55 | 56 | init { 57 | component = JLayeredPane().apply { 58 | layout = OverlayLayout(this) 59 | isOpaque = false 60 | isVisible = true 61 | addLayer(mainComponent, JLayeredPane.DEFAULT_LAYER) 62 | addLayer(hudPanel, JLayeredPane.MODAL_LAYER) 63 | addLayer(frameResizeLabel.component, JLayeredPane.POPUP_LAYER) 64 | } 65 | } 66 | 67 | val dnDTarget: DragAndDropTarget 68 | get() = object : DragAndDropTarget { 69 | override val component = hudPanel 70 | 71 | override fun activate() { 72 | progressPanel.isVisible = false 73 | dndPanel.isVisible = true 74 | } 75 | 76 | override fun deactivate() { 77 | dndPanel.isVisible = false 78 | } 79 | } 80 | 81 | fun setProgressVisible(visible: Boolean) { 82 | if (visible) { 83 | dndPanel.isVisible = false 84 | } 85 | progressPanel.isVisible = visible 86 | } 87 | 88 | fun installResizeListener(frame: JFrame) = frameResizeLabel.installListener(frame) 89 | } 90 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/JPanelWithPainter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import java.awt.Graphics 13 | import java.awt.Graphics2D 14 | import java.awt.GridBagLayout 15 | import java.awt.LayoutManager 16 | import javax.swing.* 17 | 18 | /** 19 | * A JPanel that paints its background using a [Painter]. 20 | * 21 | * @see Painter 22 | */ 23 | class JPanelWithPainter @JvmOverloads constructor( 24 | layout: LayoutManager = GridBagLayout(), 25 | private val backgroundPainter: Painter 26 | ) : JPanel(layout) { 27 | override fun paintComponent(g: Graphics) { 28 | backgroundPainter.paint(g as Graphics2D, this) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/Painter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import io.github.bric3.fireplace.core.ui.Colors 13 | import io.github.bric3.fireplace.core.ui.LightDarkColor 14 | import io.github.bric3.fireplace.ui.toolkit.Painter.Companion.blurOf 15 | import io.github.bric3.fireplace.ui.toolkit.Painter.Companion.translucent 16 | import java.awt.Color 17 | import java.awt.Graphics2D 18 | import java.awt.image.BufferedImage 19 | import java.awt.image.BufferedImageOp 20 | import java.awt.image.ConvolveOp 21 | import java.awt.image.Kernel 22 | import javax.swing.* 23 | 24 | /** 25 | * Experimental interface to paint something on a component. 26 | * 27 | * @see JPanelWithPainter 28 | * @see translucent 29 | * @see blurOf 30 | */ 31 | @FunctionalInterface 32 | interface Painter{ 33 | fun paint(g2: Graphics2D, c: JComponent) 34 | 35 | companion object { 36 | fun compose(vararg painters: Painter) = object : Painter { 37 | override fun paint(g2: Graphics2D, c: JComponent) { 38 | painters.forEach { it.paint(g2, c) } 39 | } 40 | } 41 | 42 | /** 43 | * A translucent painter. 44 | */ 45 | fun translucent( 46 | translucentColor: Color = LightDarkColor( 47 | Colors.translucent_white_B0, 48 | Colors.translucent_black_80 49 | ) 50 | ) = object : Painter { 51 | override fun paint(g2: Graphics2D, c: JComponent) { 52 | g2.color = translucentColor 53 | g2.fillRect(0, 0, c.width, c.height) 54 | } 55 | } 56 | 57 | /** 58 | * A painter that blurs the background of a component, using passed component as the source. 59 | */ 60 | fun blurOf(bgComp: JComponent) = object : Painter { 61 | private lateinit var mOffscreenImage: BufferedImage 62 | private val mOperation: BufferedImageOp = kotlin.run { 63 | // matrix explained here https://www.jhlabs.com/ip/blurring.html 64 | val ninth = 1.0f / 9.0f 65 | val blurKernel = floatArrayOf( 66 | ninth, ninth, ninth, 67 | ninth, ninth, ninth, 68 | ninth, ninth, ninth 69 | ) 70 | ConvolveOp( 71 | Kernel(3, 3, blurKernel), 72 | ConvolveOp.EDGE_NO_OP, null 73 | ) 74 | } 75 | 76 | override fun paint(g2: Graphics2D, c: JComponent) { 77 | // Only create the offscreen image if the one we have 78 | // is the wrong size. 79 | if (bgComp.width == 0 || bgComp.height == 0) { 80 | return 81 | } 82 | 83 | if (!this::mOffscreenImage.isInitialized 84 | || mOffscreenImage.width != bgComp.width 85 | || mOffscreenImage.height != bgComp.height 86 | ) { 87 | mOffscreenImage = BufferedImage(bgComp.width, bgComp.height, BufferedImage.TYPE_INT_ARGB) 88 | } 89 | val captureG2 = mOffscreenImage.createGraphics() 90 | captureG2.clip = g2.clip 91 | bgComp.paint(captureG2) 92 | captureG2.dispose() 93 | 94 | g2.drawImage(mOffscreenImage, mOperation, 0, 0) 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/SwingUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import java.awt.Component 13 | import java.awt.event.ComponentAdapter 14 | import java.awt.event.ComponentEvent 15 | import javax.swing.JLayeredPane 16 | import javax.swing.JSplitPane 17 | 18 | fun JSplitPane.autoSize(proportionalLocation: Double) { 19 | this.addComponentListener(object : ComponentAdapter() { 20 | private var firstResize = true 21 | override fun componentResized(e: ComponentEvent? ) { 22 | if (firstResize) { 23 | this@autoSize.setDividerLocation(proportionalLocation) 24 | firstResize = false 25 | } 26 | } 27 | }) 28 | } 29 | 30 | /** 31 | * This function is required in Kotlin for JLayeredPane to properly add components as layer. 32 | * 33 | * Otherwise, kotlin will treat [JLayeredPane.DEFAULT_LAYER] constants 34 | * as `int` and will instead call [`JLayeredPane.add(Component comp, int index)`][JLayeredPane.add] 35 | * instead of [`JLayeredPane.add(Component comp, Object constraints)`][JLayeredPane.add]. 36 | * 37 | * Java will generate the following byt code 38 | * 39 | * ``` 40 | * GETSTATIC javax/swing/JLayeredPane.MODAL_LAYER : Ljava/lang/Integer; 41 | * INVOKEVIRTUAL javax/swing/JLayeredPane.add (Ljava/awt/Component;Ljava/lang/Object;)V 42 | * ``` 43 | * 44 | * While Kotlin will generate 45 | * ``` 46 | * INVOKEVIRTUAL java/lang/Integer.intValue ()I 47 | * INVOKEVIRTUAL javax/swing/JLayeredPane.add (Ljava/awt/Component;I)Ljava/awt/Component; 48 | * ``` 49 | * 50 | * These methods make sure the constraint is treated as an [Object]. 51 | */ 52 | fun JLayeredPane.addLayer(c: Component, constraint: Int) { 53 | add(c, constraint as Any) 54 | } 55 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/Tables.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import io.github.bric3.fireplace.core.ui.Colors 13 | import java.awt.Color 14 | import java.awt.Component 15 | import javax.swing.JComponent 16 | import javax.swing.JScrollPane 17 | import javax.swing.JTable 18 | import javax.swing.ListSelectionModel 19 | import javax.swing.RowSorter 20 | import javax.swing.SortOrder 21 | import javax.swing.SwingUtilities 22 | import javax.swing.border.Border 23 | import javax.swing.border.CompoundBorder 24 | import javax.swing.border.EmptyBorder 25 | import javax.swing.border.MatteBorder 26 | import javax.swing.table.DefaultTableModel 27 | import javax.swing.table.TableCellRenderer 28 | import javax.swing.table.TableRowSorter 29 | 30 | 31 | fun simpleReadOnlyTable( 32 | data: Array>, 33 | cols: Array 34 | ): JScrollPane { 35 | val model = ReadOnlyUpdatableTableModel(data, cols) 36 | 37 | return ReadOnlyTable(model).apply { 38 | setSelectionMode(ListSelectionModel.SINGLE_SELECTION) 39 | columnSelectionAllowed = false 40 | rowSelectionAllowed = true 41 | setShowGrid(false) 42 | gridColor = Colors.panelBackground 43 | rowSorter = model.getRowSorter().apply { 44 | sortKeys = listOf(RowSorter.SortKey(0, SortOrder.ASCENDING)) 45 | sort() 46 | } 47 | dropTarget = null 48 | }.let { 49 | JScrollPane(it) 50 | } 51 | } 52 | 53 | fun JScrollPane.unwrappedTable(): ReadOnlyTable = (SwingUtilities.getUnwrappedView(viewport) as ReadOnlyTable) 54 | 55 | class ReadOnlyUpdatableTableModel( 56 | data: Array>, 57 | private val columnNames: Array 58 | ) : DefaultTableModel(data, columnNames) { 59 | override fun isCellEditable(row: Int, column: Int): Boolean = false 60 | fun getRowSorter(): TableRowSorter = 61 | TableRowSorter(this).apply { 62 | sortsOnUpdates = true 63 | } 64 | 65 | // https://typealias.com/guides/star-projections-and-how-they-work/#how-differs-from-any 66 | // on why we need to use Array 67 | fun setData(data: Array>) { 68 | this.setDataVector(data, columnNames) 69 | } 70 | } 71 | 72 | class ReadOnlyTable(model: DefaultTableModel) : JTable(model) { 73 | private val outside: Border = MatteBorder(1, 0, 1, 0, Color.RED) 74 | private val inside: Border = EmptyBorder(0, 1, 0, 1) 75 | private val highlight: Border = CompoundBorder(outside, inside) 76 | 77 | override fun prepareRenderer(renderer: TableCellRenderer, row: Int, column: Int): Component { 78 | val jComponent = super.prepareRenderer(renderer, row, column) as JComponent 79 | // Striped row color 80 | if (!isRowSelected(row)) { 81 | jComponent.background = if (row % 2 == 0) background else Colors.translucent_black_10 82 | } 83 | 84 | // Add a border to the selected row 85 | if (isRowSelected(row)) { 86 | jComponent.border = highlight 87 | } 88 | return jComponent 89 | } 90 | 91 | override fun getModel(): ReadOnlyUpdatableTableModel { 92 | return super.getModel() as ReadOnlyUpdatableTableModel 93 | } 94 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/TitleBar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import com.github.weisj.darklaf.platform.SystemInfo 13 | import com.github.weisj.darklaf.platform.decorations.ExternalLafDecorator 14 | import java.awt.BorderLayout 15 | import java.awt.Dimension 16 | import java.awt.Rectangle 17 | import javax.swing.JComponent 18 | import javax.swing.JPanel 19 | 20 | class TitleBar(component: JComponent) : JPanel(BorderLayout()) { 21 | init { 22 | add(component, BorderLayout.CENTER) 23 | } 24 | 25 | override fun doLayout() { 26 | if (SystemInfo.isMac) { 27 | add(WindowButtonSpace.INSTANCE, BorderLayout.WEST) 28 | } 29 | if (SystemInfo.isWindows || SystemInfo.isLinux) { 30 | add(WindowButtonSpace.INSTANCE) 31 | } 32 | super.doLayout() 33 | } 34 | 35 | private class WindowButtonSpace private constructor() : JComponent() { 36 | private val windowButtonRect: Rectangle by lazy { 37 | ExternalLafDecorator.instance() 38 | .decorationsManager() 39 | .titlePaneLayoutInfo(rootPane) 40 | .windowButtonRect() 41 | } 42 | 43 | override fun getPreferredSize(): Dimension { 44 | val size = windowButtonRect.size 45 | if (SystemInfo.isMac) { 46 | size.width = size.width + windowButtonRect.x 47 | } 48 | if (SystemInfo.isWindows || SystemInfo.isLinux) { 49 | val rightAdjustment = rootPane.width - windowButtonRect.x - windowButtonRect.width 50 | size.width = size.width + rightAdjustment 51 | } 52 | return size 53 | } 54 | 55 | override fun getMinimumSize(): Dimension { 56 | return preferredSize 57 | } 58 | 59 | override fun getMaximumSize(): Dimension { 60 | return preferredSize 61 | } 62 | 63 | companion object { 64 | private const val serialVersionUID = 1L 65 | val INSTANCE = WindowButtonSpace() 66 | } 67 | } 68 | 69 | companion object { 70 | private const val serialVersionUID = 1L 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/ToolTipListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.ui.toolkit 11 | 12 | import java.awt.Component 13 | import java.awt.event.* 14 | import javax.swing.JScrollBar 15 | import javax.swing.JScrollPane 16 | import javax.swing.ToolTipManager 17 | 18 | /** 19 | * This class is used to help generate tooltips on components added to a 20 | * scrollpane. Generally tooltips are generated as the mouse if moved over 21 | * components that display tooltips. On complex component, like a JTable 22 | * the component can generate multiple tooltips depending on which cell the 23 | * mouse is positioned over. 24 | * 25 | * However, when the viewport of the scroll-pane is moved and the mouse is 26 | * not moved the tooltip is not updated even though the mouse is positioned 27 | * over a different cell. This might happen for example when the mouse wheel 28 | * is used to scroll the viewport. 29 | * 30 | * To force updating of the tooltip, this class will generate a phantom 31 | * mouseMoved event which is passed to the ToolTipManager. 32 | * 33 | * This class is actually a 3 in 1 listener and will work slightly different 34 | * depending on how it is being used. When used as a: 35 | * 36 | * 1. `MouseWheelListener` - it is added to the scrollpane. In this case the 37 | * mouseMoved events are only generated by scrolling of the mouse wheel 38 | * and therefore only supports vertical movement of the viewport 39 | * 40 | * 2. `AdjustmentListener` - is added to the vertical and/or horizontal scrollbar. 41 | * In this case the viewport can be scrolled by using the mouse wheel or 42 | * the keyboard and mouseMoved events will be generated. 43 | * 44 | * 3. `ComponentListener` - it is added to the component. In this case all forms 45 | * of viewport movement as well as changes in the component size will cause 46 | * the mouseMoved event to be generated. 47 | * 48 | * Source: https://tips4java.wordpress.com/2009/11/08/tooltips-and-scrollpanes/ 49 | */ 50 | class ToolTipListener : ComponentListener, MouseWheelListener, AdjustmentListener { 51 | /** 52 | * Create a mouseMoved event to pass to the ToolTipManager. 53 | */ 54 | private fun phantomMouseMoved(component: Component?) { 55 | if (component == null) return 56 | 57 | // Mouse is in the bounds of the component, generate phantom 58 | // mouseMoved event for the ToolTipManager 59 | val mouseLocation = component.mousePosition 60 | if (mouseLocation != null) { 61 | val phantom = MouseEvent( 62 | component, 63 | MouseEvent.MOUSE_MOVED, 64 | System.currentTimeMillis(), 65 | 0, 66 | mouseLocation.x, 67 | mouseLocation.y, 68 | 0, 69 | false 70 | ) 71 | ToolTipManager.sharedInstance().mouseMoved(phantom) 72 | } 73 | } 74 | 75 | // Implement ComponentListener 76 | override fun componentMoved(e: ComponentEvent) { 77 | val component = e.component 78 | phantomMouseMoved(component) 79 | } 80 | 81 | override fun componentResized(e: ComponentEvent) { 82 | val component = e.component 83 | phantomMouseMoved(component) 84 | } 85 | 86 | override fun componentHidden(e: ComponentEvent) {} 87 | override fun componentShown(e: ComponentEvent) {} 88 | 89 | // Implement MouseWheelListener 90 | override fun mouseWheelMoved(e: MouseWheelEvent) { 91 | val scrollPane = e.source as JScrollPane 92 | val component = scrollPane.viewport.view 93 | phantomMouseMoved(component) 94 | } 95 | 96 | // Implement AdjustmentListener 97 | override fun adjustmentValueChanged(e: AdjustmentEvent) { 98 | val scrollBar = e.source as JScrollBar 99 | val scrollPane = scrollBar.parent as JScrollPane 100 | val component = scrollPane.viewport.view 101 | phantomMouseMoved(component) 102 | } 103 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/kotlin/io/github/bric3/fireplace/ui/toolkit/UIUtil.kt: -------------------------------------------------------------------------------- 1 | package io.github.bric3.fireplace.ui.toolkit 2 | 3 | import java.awt.Color 4 | import javax.swing.* 5 | 6 | object UIUtil { 7 | 8 | // Some properties come from FlatLaf 9 | // https://www.formdev.com/flatlaf/components/borders/ 10 | // https://www.formdev.com/flatlaf/components/ 11 | object Colors { 12 | val borderColor: Color 13 | get() = UIManager.getColor("Component.borderColor") 14 | val borderWidth: Color 15 | get() = UIManager.getColor("Component.borderWidth") 16 | 17 | val backgroundColor: Color 18 | get() = UIManager.getColor("Panel.background") 19 | 20 | val foregroundColor: Color 21 | get() = UIManager.getColor("Panel.background") 22 | } 23 | } -------------------------------------------------------------------------------- /fireplace-app/src/main/resources/dardMode-moon.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /fireplace-app/src/main/resources/dardMode-sun.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /fireplace-app/src/main/resources/fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bric3/fireplace/6f004162f361b2310a52064849698205414256d3/fireplace-app/src/main/resources/fire.png -------------------------------------------------------------------------------- /fireplace-app/src/main/resources/fire.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bric3/fireplace/6f004162f361b2310a52064849698205414256d3/fireplace-app/src/main/resources/fire.pxm -------------------------------------------------------------------------------- /fireplace-swing-animation/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | plugins { 11 | id("fireplace.published-java-library") 12 | } 13 | 14 | description = "Animate zoom transitions of a flamegraph or iciclegraph swing component" 15 | 16 | dependencies { 17 | api(projects.fireplaceSwing) 18 | implementation(libs.radiance.animation) 19 | } 20 | -------------------------------------------------------------------------------- /fireplace-swing-animation/src/main/java/io/github/bric3/fireplace/flamegraph/animation/ZoomAnimation.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.flamegraph.animation; 11 | 12 | import io.github.bric3.fireplace.flamegraph.FlamegraphView; 13 | import io.github.bric3.fireplace.flamegraph.FlamegraphView.ZoomAction; 14 | import io.github.bric3.fireplace.flamegraph.FlamegraphView.ZoomableComponent; 15 | import io.github.bric3.fireplace.flamegraph.ZoomTarget; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.pushingpixels.radiance.animation.api.Timeline; 18 | import org.pushingpixels.radiance.animation.api.ease.Sine; 19 | import org.pushingpixels.radiance.animation.api.swing.EventDispatchThreadTimelineCallbackAdapter; 20 | 21 | /** 22 | * A zoom action that incorporates animation using Radiance Animation 23 | */ 24 | public class ZoomAnimation implements ZoomAction { 25 | 26 | private static final long ZOOM_ANIMATION_DURATION = 400L; 27 | 28 | /** 29 | * A key for a system property that can be used to disable zoom animations. Used to set the 30 | * initial state of the `animateZoomTransitions` flag. 31 | */ 32 | private static final String ZOOM_ANIMATION_DISABLED_KEY = "fireplace.zoom.animation.disabled"; 33 | 34 | /** 35 | * A flag controlling whether zoom transitions are animated. Defaults to true unless a 36 | * system property is set to disable it (`-Dfireplace.zoom.animation.disabled=true`). 37 | */ 38 | private boolean animateZoomTransitions = !Boolean.getBoolean(ZOOM_ANIMATION_DISABLED_KEY); 39 | 40 | public void install(final FlamegraphView flameGraph) { 41 | flameGraph.overrideZoomAction(this); 42 | } 43 | 44 | /** 45 | * Returns the flag that controls whether zoom transitions are animated. The default 46 | * value is {@code true} unless the System property {@code fireplace.zoom.animation.disabled} 47 | * is set to {@code true} (this provides a way to switch off the feature if required). 48 | * 49 | * @return A boolean. 50 | */ 51 | public boolean isAnimateZoomTransitions() { 52 | return animateZoomTransitions; 53 | } 54 | 55 | /** 56 | * Sets the flag that controls whether zoom transitions are animated. 57 | * 58 | * @param animateZoomTransitions the new flag value. 59 | */ 60 | public void setAnimateZoomTransitions(boolean animateZoomTransitions) { 61 | this.animateZoomTransitions = animateZoomTransitions; 62 | } 63 | 64 | @Override 65 | public boolean zoom( 66 | @NotNull ZoomableComponent zoomableComponent, 67 | @NotNull ZoomTarget zoomTarget 68 | ) { 69 | System.getLogger(zoomableComponent.getClass().getName()).log(System.Logger.Level.DEBUG, () -> "zoom to " + zoomTarget); 70 | if (!isAnimateZoomTransitions()) { 71 | return false; 72 | } 73 | int startW = zoomableComponent.getWidth(); 74 | int startH = zoomableComponent.getHeight(); 75 | double deltaW = zoomTarget.getWidth() - startW; 76 | double deltaH = zoomTarget.getHeight() - startH; 77 | 78 | var location = zoomableComponent.getLocation(); 79 | int startX = location.x; 80 | int startY = location.y; 81 | double deltaX = zoomTarget.getX() - startX; 82 | double deltaY = zoomTarget.getY() - startY; 83 | 84 | 85 | Timeline.builder() 86 | .setDuration(ZOOM_ANIMATION_DURATION) 87 | .setEase(new Sine()) 88 | .addCallback(new EventDispatchThreadTimelineCallbackAdapter() { 89 | @Override 90 | public void onTimelineStateChanged( 91 | Timeline.TimelineState oldState, 92 | Timeline.TimelineState newState, 93 | float durationFraction, 94 | float timelinePosition 95 | ) { 96 | if (newState.equals(Timeline.TimelineState.DONE)) { 97 | // throw in a final update to the target position, because the last pulse 98 | // might not have reached exactly timelinePosition = 1.0... 99 | zoomableComponent.zoom(zoomTarget); 100 | } 101 | } 102 | 103 | @Override 104 | public void onTimelinePulse( 105 | float durationFraction, 106 | float timelinePosition 107 | ) { 108 | zoomableComponent.zoom(new ZoomTarget<>( 109 | startX + (int) (timelinePosition * deltaX), 110 | startY + (int) (timelinePosition * deltaY), 111 | (int) (startW + timelinePosition * deltaW), 112 | (int) (startH + timelinePosition * deltaH), 113 | zoomTarget.targetFrame 114 | )); 115 | } 116 | }) 117 | .build() 118 | .playSkipping(3L); 119 | return true; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /fireplace-swing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | plugins { 12 | id("fireplace.published-java-library") 13 | } 14 | 15 | description = "Flamegraph or iciclegraph swing component" 16 | 17 | dependencies { 18 | testImplementation(libs.bundles.batik) 19 | } 20 | 21 | tasks { 22 | withType(Javadoc::class) { 23 | options.overview = "src/main/javadoc/overview.html" 24 | (options as StandardJavadocDocletOptions).linkSource(true) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/core/ui/LightDarkColor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.core.ui; 11 | 12 | import java.awt.*; 13 | import java.awt.color.ColorSpace; 14 | import java.awt.geom.AffineTransform; 15 | import java.awt.geom.Rectangle2D; 16 | import java.awt.image.ColorModel; 17 | 18 | /** 19 | * Represents a color that can return two values depending on the {@link Colors#isDarkMode()} 20 | */ 21 | public class LightDarkColor extends Color { 22 | private final Color dark; 23 | 24 | /** 25 | * Creates a new instance. 26 | * 27 | * @param light the light color. 28 | * @param dark the dark color. 29 | */ 30 | public LightDarkColor(Color light, Color dark) { 31 | super(light.getRGB(), light.getAlpha() != 255); 32 | this.dark = dark; 33 | } 34 | 35 | /** 36 | * Creates a new instance. 37 | * 38 | * @param light_rgba the light color. 39 | * @param dark_rgba the dark color. 40 | */ 41 | public LightDarkColor(int light_rgba, int dark_rgba) { 42 | super(light_rgba, true); 43 | this.dark = new Color(dark_rgba, true); 44 | } 45 | 46 | /** 47 | * Returns the color object corresponding to the current mode (see {@link Colors#isDarkMode()}). 48 | * 49 | * @return The color object. 50 | */ 51 | private Color currentColor() { 52 | return Colors.isDarkMode() ? dark : this; 53 | } 54 | 55 | @Override 56 | public int getRed() { 57 | var current = currentColor(); 58 | return current == this ? super.getRed() : current.getRed(); 59 | } 60 | 61 | @Override 62 | public int getGreen() { 63 | var current = currentColor(); 64 | return current == this ? super.getGreen() : current.getGreen(); 65 | } 66 | 67 | @Override 68 | public int getBlue() { 69 | var current = currentColor(); 70 | return current == this ? super.getBlue() : current.getBlue(); 71 | } 72 | 73 | @Override 74 | public int getAlpha() { 75 | var current = currentColor(); 76 | return current == this ? super.getAlpha() : current.getAlpha(); 77 | } 78 | 79 | @Override 80 | public int getRGB() { 81 | var current = currentColor(); 82 | return current == this ? super.getRGB() : current.getRGB(); 83 | } 84 | 85 | @Override 86 | public Color brighter() { 87 | var current = currentColor(); 88 | return current == this ? super.brighter() : current.brighter(); 89 | } 90 | 91 | @Override 92 | public Color darker() { 93 | var current = currentColor(); 94 | return current == this ? super.darker() : current.darker(); 95 | } 96 | 97 | @Override 98 | public float[] getRGBComponents(float[] compArray) { 99 | var current = currentColor(); 100 | return current == this ? super.getRGBComponents(compArray) : current.getRGBComponents(compArray); 101 | } 102 | 103 | @Override 104 | public float[] getRGBColorComponents(float[] compArray) { 105 | var current = currentColor(); 106 | return current == this ? super.getRGBColorComponents(compArray) : current.getRGBColorComponents(compArray); 107 | } 108 | 109 | @Override 110 | public float[] getComponents(float[] compArray) { 111 | var current = currentColor(); 112 | return current == this ? super.getComponents(compArray) : current.getComponents(compArray); 113 | } 114 | 115 | @Override 116 | public float[] getColorComponents(float[] compArray) { 117 | var current = currentColor(); 118 | return current == this ? super.getColorComponents(compArray) : current.getColorComponents(compArray); 119 | } 120 | 121 | @Override 122 | public float[] getComponents(ColorSpace cspace, float[] compArray) { 123 | var current = currentColor(); 124 | return current == this ? super.getComponents(cspace, compArray) : current.getComponents(cspace, compArray); 125 | } 126 | 127 | @Override 128 | public float[] getColorComponents(ColorSpace cspace, float[] compArray) { 129 | var current = currentColor(); 130 | return current == this ? super.getColorComponents(cspace, compArray) : current.getColorComponents(cspace, compArray); 131 | } 132 | 133 | @Override 134 | public ColorSpace getColorSpace() { 135 | var current = currentColor(); 136 | return current == this ? super.getColorSpace() : current.getColorSpace(); 137 | } 138 | 139 | @Override 140 | public synchronized PaintContext createContext(ColorModel cm, Rectangle r, Rectangle2D r2d, AffineTransform xform, RenderingHints hints) { 141 | var current = currentColor(); 142 | return current == this ? super.createContext(cm, r, r2d, xform, hints) : current.createContext(cm, r, r2d, xform, hints); 143 | } 144 | 145 | @Override 146 | public int getTransparency() { 147 | var current = currentColor(); 148 | return current == this ? super.getTransparency() : current.getTransparency(); 149 | } 150 | 151 | @Override 152 | public int hashCode() { 153 | var current = currentColor(); 154 | return current == this ? super.hashCode() : current.hashCode(); 155 | } 156 | 157 | @Override 158 | public boolean equals(Object obj) { 159 | var current = currentColor(); 160 | return current == this ? super.equals(obj) : current.equals(obj); 161 | } 162 | 163 | @Override 164 | public String toString() { 165 | var current = currentColor(); 166 | return current == this ? super.toString() : current.toString(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/core/ui/MouseInputListenerWorkaroundForToolTipEnabledComponent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.core.ui; 11 | 12 | import javax.swing.*; 13 | import java.awt.event.MouseAdapter; 14 | import java.awt.event.MouseEvent; 15 | import java.awt.event.MouseWheelEvent; 16 | 17 | /** 18 | * Workaround for container such as JScrollPane not receiving mouse events 19 | * when it is view has display tooltip. 20 | *

21 | * This listener's job is to propagate the mouse input events to the 22 | * target container. 23 | *

24 | *

25 | * This happens on {@link JScrollPane}, when the component is presented, eg on 26 | * the official javadoc: 27 | * 28 | * JScrollPane javadoc image 29 | * 30 | * The image suggests that content of the scroll pane is behind. This is a 31 | * conceptual view, in practice the content is actually on the top of the scroll 32 | * pane (its coordinate and its visible size will just be adjusted to match 33 | * the scroll pane) and as it is over the scroll pane it will be the one that 34 | * gets the mouse events. 35 | *

36 | *

37 | * When a component does not have mouse listeners they are propagated to the 38 | * parent component that is behind. But when a component has mouse listeners 39 | * the events are trapped by the top component's listener. 40 | * That means that if the parent is also interested by mouse events 41 | * they need to be propagated, that is the goal of this class. 42 | *

43 | */ 44 | public class MouseInputListenerWorkaroundForToolTipEnabledComponent extends MouseAdapter { 45 | private final JComponent destination; 46 | 47 | public MouseInputListenerWorkaroundForToolTipEnabledComponent(JComponent destination) { 48 | this.destination = destination; 49 | } 50 | 51 | public void install(JComponent jComponentWithToolTip) { 52 | jComponentWithToolTip.addMouseMotionListener(this); 53 | jComponentWithToolTip.addMouseListener(this); 54 | } 55 | 56 | private void dispatch(MouseEvent e) { 57 | destination.dispatchEvent( 58 | SwingUtilities.convertMouseEvent(e.getComponent(), e, destination) 59 | ); 60 | } 61 | 62 | @Override 63 | public void mouseClicked(MouseEvent e) { 64 | dispatch(e); 65 | } 66 | 67 | @Override 68 | public void mousePressed(MouseEvent e) { 69 | dispatch(e); 70 | } 71 | 72 | @Override 73 | public void mouseReleased(MouseEvent e) { 74 | dispatch(e); 75 | } 76 | 77 | @Override 78 | public void mouseEntered(MouseEvent e) { 79 | dispatch(e); 80 | } 81 | 82 | @Override 83 | public void mouseExited(MouseEvent e) { 84 | dispatch(e); 85 | } 86 | 87 | @Override 88 | public void mouseWheelMoved(MouseWheelEvent e) { 89 | dispatch(e); 90 | } 91 | 92 | @Override 93 | public void mouseDragged(MouseEvent e) { 94 | dispatch(e); 95 | } 96 | 97 | @Override 98 | public void mouseMoved(MouseEvent e) { 99 | dispatch(e); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/core/ui/StringClipper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.core.ui; 11 | 12 | import java.awt.*; 13 | 14 | /** 15 | * A utility class to clip strings to fit a given width. 16 | *

17 | * The clipping strategy is defined by the enum value. 18 | * The clipping strategy can be changed by implementing the {@link #clipString(Font, FontMetrics, double, String, String)} method. 19 | * 20 | * @see #NONE 21 | * @see #RIGHT 22 | */ 23 | @FunctionalInterface 24 | public interface StringClipper { 25 | /** 26 | * No clipping is performed. 27 | */ 28 | StringClipper NONE = (font, metrics, availTextWidth, text, clipString) -> text; 29 | 30 | /** 31 | * Clips the string from the left. 32 | */ 33 | StringClipper RIGHT = (font, metrics, availTextWidth, text, clipString) -> { 34 | availTextWidth -= font.getStringBounds(clipString, metrics.getFontRenderContext()).getWidth(); 35 | if (availTextWidth <= 0) { 36 | // cannot fit any characters 37 | return clipString; 38 | } 39 | 40 | int stringLength = text.length(); 41 | int width = 0; 42 | for (int nChars = 0; nChars < stringLength; nChars++) { 43 | width += metrics.charWidth(text.charAt(nChars)); 44 | if (width > availTextWidth) { 45 | text = text.substring(0, nChars); 46 | break; 47 | } 48 | } 49 | 50 | return text + clipString; 51 | }; 52 | 53 | /** 54 | * A short string to display in place of labels that are too long to fit the 55 | * available space. 56 | */ 57 | String LONG_TEXT_PLACEHOLDER = "…"; 58 | 59 | /** 60 | * Clip the string to fit the available width according to the font metrics. 61 | * 62 | * @param font the font used to render the text 63 | * (used to calculate the width of the text to clip) 64 | * @param metrics the font metrics (usually obtained from {@link Graphics#getFontMetrics(Font)} 65 | * @param availTextWidth the available width for the text 66 | * @param text the text to clip 67 | * @param clipString the string to indicating where the clip happened, according to the clipping strategy 68 | * @return the clipped string 69 | * @see #clipString(Font, FontMetrics, double, String) 70 | */ 71 | String clipString(Font font, FontMetrics metrics, double availTextWidth, String text, String clipString); 72 | 73 | /** 74 | * Clip the string to fit the available width according to the font metrics. 75 | *

76 | * This method uses {@link #LONG_TEXT_PLACEHOLDER} as the clip string. 77 | * 78 | * @param font the font used to render the text 79 | * (used to calculate the width of the text to clip) 80 | * @param metrics the font metrics (usually obtained from {@link Graphics#getFontMetrics(Font)} 81 | * @param availTextWidth the available width for the text 82 | * @param text the text to clip 83 | * @return the clipped string 84 | * @see #clipString(Font, FontMetrics, double, String, String) 85 | */ 86 | default String clipString(Font font, FontMetrics metrics, double availTextWidth, String text) { 87 | return clipString(font, metrics, availTextWidth, text, LONG_TEXT_PLACEHOLDER); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/core/ui/SwingUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.bric3.fireplace.core.ui; 2 | 3 | import javax.swing.*; 4 | import java.lang.reflect.InvocationTargetException; 5 | 6 | public class SwingUtils { 7 | public static void invokeLater(Runnable runnable) { 8 | if (SwingUtilities.isEventDispatchThread()) { 9 | runnable.run(); 10 | } else { 11 | SwingUtilities.invokeLater(runnable); 12 | } 13 | } 14 | 15 | public static void invokeAndWait(Runnable runnable) throws InterruptedException, InvocationTargetException { 16 | if (SwingUtilities.isEventDispatchThread()) { 17 | runnable.run(); 18 | } else { 19 | SwingUtilities.invokeAndWait(runnable); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/core/ui/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Core classes for the user interface. 3 | */ 4 | package io.github.bric3.fireplace.core.ui; -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/ColorMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.flamegraph; 11 | 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | import java.awt.*; 16 | import java.util.Objects; 17 | import java.util.function.Function; 18 | 19 | /** 20 | * Named function to map a value to a color. 21 | * 22 | * @param The type of the object to map to a color. 23 | */ 24 | public interface ColorMapper extends Function<@Nullable T, @NotNull Color> { 25 | /** 26 | * Returns a {@code ColorMapper} instance that maps objects (using the hashCode) to colors 27 | * in the supplied palette. 28 | * 29 | * @param palette the palette. 30 | * 31 | * @return A color. 32 | */ 33 | @NotNull 34 | static ColorMapper<@Nullable T> ofObjectHashUsing(@NotNull Color... palette) { 35 | return o -> o == null ? 36 | palette[0] : 37 | palette[Math.abs(Objects.hashCode(o)) % palette.length]; 38 | } 39 | 40 | /** 41 | * Returns the color that is mapped to the specified object. 42 | * 43 | * @param o the object ({@code null} permitted). 44 | * 45 | * @return A color. 46 | */ 47 | @NotNull 48 | default Color apply(@Nullable T o) { 49 | return mapToColor(o); 50 | } 51 | 52 | /** 53 | * Returns the color that is mapped to the specified object. This is the same as the {@link #apply(Object)} 54 | * method but has a more descriptive name. 55 | * 56 | * @param o the object ({@code null} permitted). 57 | * 58 | * @return The color. 59 | */ 60 | @NotNull 61 | Color mapToColor(@Nullable T o); 62 | } 63 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameFontProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | package io.github.bric3.fireplace.flamegraph; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.awt.*; 17 | 18 | import static io.github.bric3.fireplace.flamegraph.FrameRenderingFlags.isFocusing; 19 | import static io.github.bric3.fireplace.flamegraph.FrameRenderingFlags.isHighlightedFrame; 20 | import static io.github.bric3.fireplace.flamegraph.FrameRenderingFlags.isInFocusedFlame; 21 | import static io.github.bric3.fireplace.flamegraph.FrameRenderingFlags.isPartialFrame; 22 | 23 | /** 24 | * Strategy for choosing the font of a frame. 25 | * 26 | * @param The type of the frame node (depends on the source of profiling data). 27 | */ 28 | @FunctionalInterface 29 | public interface FrameFontProvider { 30 | 31 | /** 32 | * Returns a font according to the frame and flags parameters. 33 | * 34 | *

35 | * An implementation should return the base font if the frame 36 | * parameter is null. Possibly honoring the flags. 37 | *

38 | * 39 | * @param frame The frame to get the font for, can be null. 40 | * @param flags The flags 41 | * @return The font to use for the frame and flags. 42 | */ 43 | @NotNull 44 | Font getFont(@Nullable FrameBox<@NotNull T> frame, int flags); 45 | 46 | @NotNull 47 | static FrameFontProvider<@NotNull T> defaultFontProvider() { 48 | return new FrameFontProvider<>() { 49 | /** 50 | * The font used to display frame labels 51 | */ 52 | private final Font regular = new Font(Font.SANS_SERIF, Font.PLAIN, 12); 53 | 54 | /** 55 | * If a frame is clipped, we'll shift the label to make it visible but show it with 56 | * a modified (italicised by default) font to highlight that the frame is only partially 57 | * visible. 58 | */ 59 | private final Font italic = new Font(Font.SANS_SERIF, Font.ITALIC, 12); 60 | 61 | /** 62 | * The font used to display frame labels 63 | */ 64 | private final Font bold = new Font(Font.SANS_SERIF, Font.PLAIN | Font.BOLD, 12); 65 | 66 | /** 67 | * If a frame is clipped, we'll shift the label to make it visible but show it with 68 | * a modified (italicized by default) font to highlight that the frame is only partially 69 | * visible. 70 | */ 71 | private final Font italicBold = new Font(Font.SANS_SERIF, Font.ITALIC | Font.BOLD, 12); 72 | 73 | @Override 74 | @NotNull 75 | public Font getFont(@Nullable FrameBox<@NotNull T> frame, int flags) { 76 | if (frame != null && frame.isRoot()) { 77 | return bold; 78 | } 79 | 80 | // if no focused frame, highlight any frame matching isHighlightedFrame 81 | // else if focused frame, highlight the frame matching only if isFocusing 82 | if (isHighlightedFrame(flags)) { 83 | if (!isFocusing(flags) || isInFocusedFlame(flags)) { 84 | return isPartialFrame(flags) ? italicBold : bold; 85 | } 86 | } 87 | 88 | // when parent frames are larger than view port 89 | return isPartialFrame(flags) ? italic : regular; 90 | } 91 | }; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | package io.github.bric3.fireplace.flamegraph; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.util.Collections; 17 | import java.util.List; 18 | import java.util.Objects; 19 | 20 | /** 21 | * Represent the model of the flamegraph. 22 | * 23 | * @param The type of the actual node object. 24 | * @see FrameBox 25 | */ 26 | public class FrameModel { 27 | public static final FrameModel EMPTY = new FrameModel<>(Collections.emptyList()); 28 | 29 | @NotNull 30 | public final String title; 31 | @NotNull 32 | public final List<@NotNull FrameBox<@NotNull T>> frames; 33 | @NotNull 34 | public final FrameEquality<@NotNull T> frameEquality; 35 | @Nullable 36 | public String description; 37 | 38 | /** 39 | * Creates model of the flamegraph frames. 40 | * 41 | *

It takes a list of {@link FrameBox} objects that wraps the actual data, 42 | * which is referred to as node. 43 | *

. 44 | * 45 | *

46 | * The equality applies equals on the actual node object {@link T}. 47 | *

48 | * 49 | * @param frames The list of {@code FrameBox} objects. 50 | */ 51 | public FrameModel(@NotNull List<@NotNull FrameBox<@NotNull T>> frames) { 52 | this("", (a, b) -> Objects.equals(a.actualNode, b.actualNode), frames); 53 | } 54 | 55 | /** 56 | * Creates model of the flamegraph frames. 57 | * 58 | *

It takes a list of {@link FrameBox} objects that wraps the actual data, 59 | * which is referred to as node. 60 | *

. 61 | * 62 | * @param title The title of the flamegraph, used in the root frame. 63 | * @param frameEquality Custom equality code for the actual node object {@code T}. 64 | * @param frames The list of {@code FrameBox} objects. 65 | */ 66 | public FrameModel( 67 | @NotNull String title, 68 | @NotNull FrameEquality<@NotNull T> frameEquality, 69 | @NotNull List<@NotNull FrameBox<@NotNull T>> frames 70 | ) { 71 | this.title = Objects.requireNonNull(title, "title"); 72 | this.frames = Objects.requireNonNull(frames, "frames"); 73 | this.frameEquality = Objects.requireNonNull(frameEquality, "frameEquality"); 74 | } 75 | 76 | @SuppressWarnings("unchecked") 77 | @NotNull 78 | public static FrameModel<@NotNull T> empty() { 79 | return (FrameModel) EMPTY; 80 | } 81 | 82 | /** 83 | * Returns whether two frames are considered equal. 84 | * 85 | * @param The type of the actual node object. 86 | */ 87 | public interface FrameEquality { 88 | boolean equal(FrameBox<@NotNull T> a, FrameBox<@NotNull T> b); 89 | } 90 | 91 | /** 92 | * Text that describes the flamegraph. 93 | * 94 | *

Tooltip function could access that to render a specific tooltip 95 | * on the root node.

96 | * 97 | * @param description The text that describes the flamegraph. 98 | * @return this 99 | */ 100 | @NotNull 101 | public FrameModel<@NotNull T> withDescription(@NotNull String description) { 102 | this.description = Objects.requireNonNull(description, "description"); 103 | return this; 104 | } 105 | 106 | @Override 107 | public boolean equals(Object o) { 108 | if (this == o) return true; 109 | if (o == null || getClass() != o.getClass()) return false; 110 | FrameModel that = (FrameModel) o; 111 | return Objects.equals(title, that.title) && Objects.equals(frames, that.frames) && Objects.equals(frameEquality, that.frameEquality) && Objects.equals(description, that.description); 112 | } 113 | 114 | @Override 115 | public int hashCode() { 116 | return Objects.hash(title, frames, frameEquality, description); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderer.java: -------------------------------------------------------------------------------- 1 | package io.github.bric3.fireplace.flamegraph; 2 | 3 | import org.jetbrains.annotations.ApiStatus.Experimental; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.awt.*; 7 | import java.awt.geom.Rectangle2D; 8 | import java.awt.geom.RectangularShape; 9 | 10 | /** 11 | * Single frame renderer. 12 | * 13 | * @param The type of the frame node (depends on the source of profiling data). 14 | * @see FlamegraphView 15 | */ 16 | // TODO root frame renderer ? 17 | @Experimental 18 | public interface FrameRenderer { 19 | /** 20 | * The size of the gap at between each side of a frame. 21 | * 22 | * @return the size of the gap at between each side of a frame. 23 | * @see #isDrawingFrameGap() 24 | */ 25 | default int getFrameGapWidth() {return 1;} 26 | 27 | /** 28 | * Whether a gap is shown between each frame. 29 | * 30 | * @return true if a gap is shown between each frame. 31 | * @see #getFrameGapWidth() 32 | */ 33 | default boolean isDrawingFrameGap() {return true;} 34 | 35 | /** 36 | * Compute the height of the frame box according to the passed {@link Graphics2D}. 37 | * 38 | * @param g2 the graphics context 39 | * @return the height of the frame box 40 | */ 41 | int getFrameBoxHeight(@NotNull Graphics2D g2); 42 | 43 | /** 44 | * Paint the frame. 45 | * The {@code renderFlags} parameter can be used to alter the rendering of the frame, 46 | * this value is computed by the {@link FlamegraphRenderEngine} method, and 47 | * depends on which settings are used, and the context of the frame. This value can be decoded by 48 | * using the {@link FrameRenderingFlags} methods. 49 | * 50 | * @param g2 the graphics context 51 | * @param frame the frame to paint 52 | * @param frameModel the frame model 53 | * @param frameRect the frame region (may fall outside visible area). 54 | * @param paintableIntersection the intersection between the frame rectangle and the visible region 55 | * (can be used to position the text label). 56 | * @param renderFlags the rendering flags (minimap, selection, hovered, highlight, etc.) 57 | * @see FrameRenderingFlags 58 | */ 59 | void paintFrame( 60 | @NotNull Graphics2D g2, 61 | @NotNull FrameBox frame, 62 | @NotNull FrameModel frameModel, 63 | @NotNull RectangularShape frameRect, 64 | @NotNull Rectangle2D paintableIntersection, 65 | int renderFlags 66 | ); 67 | 68 | /** 69 | * A factory for the frame rectangle shape, this shape is reused. 70 | * Usually the frame will be a standard rectangle; however, the implementation may want to 71 | * use a different shape (e.g., a rounded rectangle). 72 | * This instance is likely to be reused for each frame. 73 | * @return a new instance of the frame shape. 74 | */ 75 | default RectangularShape reusableFrameShape() { 76 | return new Rectangle2D.Double(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameRenderingFlags.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.flamegraph; 11 | 12 | import java.util.StringJoiner; 13 | 14 | /** 15 | * Flags that can be used to alter some elements of the frame rendering. 16 | * 17 | *

18 | * In particular these flags will be passed to strategies like {@link FrameFontProvider}. 19 | * Note these flags are currently incubating. 20 | *

21 | * 22 | * 23 | * @see FrameFontProvider 24 | */ 25 | public abstract class FrameRenderingFlags { 26 | /** 27 | * Indicate the renderer is actually rendering a minimap. 28 | */ 29 | public static final int MINIMAP_MODE = 1; 30 | 31 | /** 32 | * The renderer is currently highlighting some frames 33 | */ 34 | public static final int HIGHLIGHTING = 1 << 1; 35 | 36 | /** 37 | * The renderer is currently rendering a highlighted frame. 38 | */ 39 | public static final int HIGHLIGHTED_FRAME = 1 << 2; 40 | 41 | /** 42 | * The renderer is currently rendering a hovered frame 43 | */ 44 | public static final int HOVERED = 1 << 3; 45 | 46 | /** 47 | * The renderer is currently rendering the sibling of a hovered frame 48 | */ 49 | public static final int HOVERED_SIBLING = 1 << 4; 50 | 51 | /** 52 | * The renderer is currently focusing some frames (a "sub-flame") 53 | */ 54 | public static final int FOCUSING = 1 << 5; 55 | 56 | /** 57 | * The renderer is currently rendering a focused frame. 58 | */ 59 | public static final int FOCUSED_FRAME = 1 << 6; 60 | 61 | /** 62 | * The renderer is currently rendering a partial frame, e.g., it is larger 63 | * that the painting area. 64 | */ 65 | public static final int PARTIAL_FRAME = 1 << 7; 66 | 67 | 68 | static int toFlags( 69 | boolean minimapMode, 70 | boolean highlightingOn, 71 | boolean highlighted, 72 | boolean hovered, 73 | boolean hoveredSibling, 74 | boolean focusing, 75 | boolean focusedFrame, 76 | boolean partialFrame 77 | ) { 78 | return (minimapMode ? MINIMAP_MODE : 0) 79 | | (highlightingOn ? HIGHLIGHTING : 0) 80 | | (highlighted ? HIGHLIGHTED_FRAME : 0) 81 | | (hovered ? HOVERED : 0) 82 | | (hoveredSibling ? HOVERED_SIBLING : 0) 83 | | (focusing ? FOCUSING : 0) 84 | | (focusedFrame ? FOCUSED_FRAME : 0) 85 | | (partialFrame ? PARTIAL_FRAME : 0); 86 | } 87 | 88 | public static String toString(int flags) { 89 | var sb = new StringJoiner(", ", "[", "]"); 90 | if ((flags & MINIMAP_MODE) != 0) sb.add("minimapMode"); 91 | if ((flags & HIGHLIGHTING) != 0) sb.add("highlighting"); 92 | if ((flags & HIGHLIGHTED_FRAME) != 0) sb.add("highlighted"); 93 | if ((flags & HOVERED) != 0) sb.add("hovered"); 94 | if ((flags & HOVERED_SIBLING) != 0) sb.add("hovered sibling"); 95 | if ((flags & FOCUSING) != 0) sb.add("focusing"); 96 | if ((flags & FOCUSED_FRAME) != 0) sb.add("focused"); 97 | if ((flags & PARTIAL_FRAME) != 0) sb.add("partial"); 98 | return sb.toString(); 99 | } 100 | 101 | public static boolean isMinimapMode(int flags) { 102 | return (flags & MINIMAP_MODE) != 0; 103 | } 104 | 105 | public static boolean isHighlighting(int flags) { 106 | return (flags & HIGHLIGHTING) != 0; 107 | } 108 | 109 | public static boolean isHighlightedFrame(int flags) { 110 | return (flags & HIGHLIGHTED_FRAME) != 0; 111 | } 112 | 113 | public static boolean isHovered(int flags) { 114 | return (flags & HOVERED) != 0; 115 | } 116 | 117 | public static boolean isHoveredSibling(int flags) { 118 | return (flags & HOVERED_SIBLING) != 0; 119 | } 120 | 121 | public static boolean isFocusing(int flags) { 122 | return (flags & FOCUSING) != 0; 123 | } 124 | 125 | public static boolean isInFocusedFlame(int flags) { 126 | return (flags & FOCUSED_FRAME) != 0; 127 | } 128 | 129 | public static boolean isPartialFrame(int flags) { 130 | return (flags & PARTIAL_FRAME) != 0; 131 | } 132 | 133 | 134 | private FrameRenderingFlags() {} 135 | } 136 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/FrameTextsProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | package io.github.bric3.fireplace.flamegraph; 12 | 13 | import io.github.bric3.fireplace.core.ui.StringClipper; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.util.List; 17 | import java.util.function.Function; 18 | 19 | /** 20 | * Provides a list of functions to convert a frame to a text. 21 | * 22 | *

23 | * Those are used to render the frame labels. It is up to the renderer to choose which function 24 | * will be used. Implementors can assume the renderer will try the text providers, 25 | * and choose the text that fits the best. The renderer is likely to clip the text, 26 | * according to {@link #clipStrategy()}, if it is too long. 27 | * 28 | * @param The type of the frame node (depends on the source of profiling data). 29 | */ 30 | @FunctionalInterface 31 | public interface FrameTextsProvider { 32 | /** 33 | * Returns a list of functions to convert a frame to a text. 34 | * 35 | * @return a list of functions to convert a frame to a text. 36 | */ 37 | @NotNull 38 | List<@NotNull Function<@NotNull FrameBox<@NotNull T>, @NotNull String>> frameToTextCandidates(); 39 | 40 | /** 41 | * Factory method to create a {@link FrameTextsProvider} from a list of functions. 42 | * @param frameToTextCandidates a list of functions to convert a frame to a text. 43 | * @return a new {@link FrameTextsProvider} instance. 44 | * @param The type of the frame node (depends on the source of profiling data). 45 | */ 46 | @NotNull 47 | static FrameTextsProvider<@NotNull T> of(@NotNull List<@NotNull Function<@NotNull FrameBox<@NotNull T>, @NotNull String>> frameToTextCandidates) { 48 | return () -> frameToTextCandidates; 49 | } 50 | 51 | /** 52 | * Factory method to create a {@link FrameTextsProvider} from a list of functions. 53 | * @param frameToTextCandidates a list of functions to convert a frame to a text. 54 | * @return a new {@link FrameTextsProvider} instance. 55 | * @param The type of the frame node (depends on the source of profiling data). 56 | */ 57 | @NotNull 58 | @SafeVarargs 59 | static FrameTextsProvider<@NotNull T> of(@NotNull Function<@NotNull FrameBox<@NotNull T>, @NotNull String>... frameToTextCandidates) { 60 | return of(List.of(frameToTextCandidates)); 61 | } 62 | 63 | /** 64 | * Factory method to create an empty {@link FrameTextsProvider}. 65 | * @return a new empty {@link FrameTextsProvider} instance. 66 | * @param The type of the frame node (depends on the source of profiling data). 67 | */ 68 | @NotNull 69 | static FrameTextsProvider<@NotNull T> empty() { 70 | return of(List.of()); 71 | } 72 | 73 | /** 74 | * Returns the strategy to use to clip the text. 75 | * @return the strategy to use to clip the text. 76 | */ 77 | @NotNull 78 | default StringClipper clipStrategy() { 79 | return StringClipper.RIGHT; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/ZoomTarget.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | package io.github.bric3.fireplace.flamegraph; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.awt.*; 17 | import java.util.Objects; 18 | 19 | /** 20 | * Represents a target for zooming. 21 | */ 22 | public class ZoomTarget { 23 | private final Rectangle targetBounds = new Rectangle(); 24 | 25 | @Nullable 26 | public final FrameBox<@NotNull T> targetFrame; 27 | 28 | /** 29 | * Creates a new zoom target. 30 | * 31 | * @param x The x coordinate of the target. 32 | * @param y The y coordinate of the target. 33 | * @param width The width of the target. 34 | * @param height The height of the target. 35 | * @param targetFrame The target frame. 36 | */ 37 | public ZoomTarget(int x, int y, int width, int height, @Nullable FrameBox<@NotNull T> targetFrame) { 38 | this.targetBounds.setBounds(x, y, width, height); 39 | this.targetFrame = targetFrame; 40 | } 41 | 42 | /** 43 | * Creates a new zoom target. 44 | * 45 | * @param bounds The target canvas bounds. 46 | */ 47 | public ZoomTarget(@NotNull Rectangle bounds, @Nullable FrameBox<@NotNull T> targetFrame) { 48 | this.targetBounds.setBounds(bounds); 49 | this.targetFrame = targetFrame; 50 | } 51 | 52 | /** 53 | * Returns the target bounds. 54 | * 55 | * @return The target bounds. 56 | */ 57 | public Rectangle getTargetBounds() { 58 | return targetBounds.getBounds(); 59 | } 60 | 61 | /** 62 | * Returns the target frame. 63 | * 64 | * @param rect The target bounds. 65 | * @return The target frame. 66 | */ 67 | public Rectangle getTargetBounds(@NotNull Rectangle rect) { 68 | rect.setBounds(targetBounds); 69 | return rect; 70 | } 71 | 72 | public double getWidth() { 73 | return targetBounds.width; 74 | } 75 | 76 | public double getHeight() { 77 | return targetBounds.height; 78 | } 79 | 80 | public double getX() { 81 | return targetBounds.x; 82 | } 83 | 84 | public double getY() { 85 | return targetBounds.y; 86 | } 87 | 88 | @Override 89 | public boolean equals(Object o) { 90 | if (this == o) return true; 91 | if (o == null || getClass() != o.getClass()) return false; 92 | ZoomTarget that = (ZoomTarget) o; 93 | return Objects.equals(targetBounds, that.targetBounds) && Objects.equals(targetFrame, that.targetFrame); 94 | } 95 | 96 | @Override 97 | public int hashCode() { 98 | return Objects.hash(targetBounds, targetFrame); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /fireplace-swing/src/main/java/io/github/bric3/fireplace/flamegraph/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The {@link io.github.bric3.fireplace.flamegraph.FlamegraphView} component and related classes. 3 | */ 4 | package io.github.bric3.fireplace.flamegraph; -------------------------------------------------------------------------------- /fireplace-swing/src/main/javadoc/overview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fireplace Swing 6 | 7 | 8 | Fireplace Swing is a library for drawing Flame graphs in Java Swing applications. The project is hosted at 9 | https://github.com/bric3/fireplace 10 | 11 | -------------------------------------------------------------------------------- /fireplace-swing/src/test/java/io/github/bric3/fireplace/flamegraph/FlamegraphImageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | 11 | package io.github.bric3.fireplace.flamegraph; 12 | 13 | 14 | import org.apache.batik.dom.GenericDOMImplementation; 15 | import org.apache.batik.svggen.SVGGraphics2D; 16 | import org.junit.jupiter.api.Tag; 17 | import org.junit.jupiter.api.Test; 18 | import org.junit.jupiter.api.TestInfo; 19 | import org.junit.jupiter.api.io.TempDir; 20 | 21 | import java.awt.*; 22 | import java.io.IOException; 23 | import java.io.StringWriter; 24 | import java.io.UncheckedIOException; 25 | import java.net.URISyntaxException; 26 | import java.nio.file.Files; 27 | import java.nio.file.Path; 28 | import java.util.List; 29 | import java.util.Objects; 30 | 31 | import static io.github.bric3.fireplace.flamegraph.ImageTestUtils.assertImageEquals; 32 | import static io.github.bric3.fireplace.flamegraph.ImageTestUtils.dumpPng; 33 | import static io.github.bric3.fireplace.flamegraph.ImageTestUtils.readImage; 34 | import static io.github.bric3.fireplace.flamegraph.ImageTestUtils.testReportDir; 35 | import static org.assertj.core.api.Assertions.assertThat; 36 | 37 | @Tag("public-api") 38 | class FlamegraphImageTest { 39 | @Test 40 | void exercise_saving_graph_to_image(@TempDir Path tempDir, TestInfo testInfo) { 41 | var flamegraphView = new FlamegraphImage( 42 | FrameTextsProvider.of(f -> f.actualNode), 43 | FrameColorProvider.defaultColorProvider(__ -> Color.ORANGE), 44 | FrameFontProvider.defaultFontProvider() 45 | ); 46 | 47 | 48 | var image = flamegraphView.generate( 49 | simpleFrameModel(), 50 | FlamegraphView.Mode.ICICLEGRAPH, 51 | 200 52 | ); 53 | 54 | dumpPng(image, testReportDir().resolve(testInfo.getDisplayName() + "-output.png")); 55 | assertImageEquals( 56 | testInfo.getDisplayName(), 57 | readImage(asset_fg_ak_200x72("png")), 58 | image 59 | ); 60 | } 61 | 62 | private static FrameModel simpleFrameModel() { 63 | return new FrameModel<>(List.of( 64 | new FrameBox<>("root", 0, 1, 0), 65 | new FrameBox<>("A", 0, 0.2, 1), 66 | new FrameBox<>("B", 0.20000000001, 0.40, 1), 67 | new FrameBox<>("C", 0.40000000001, 1, 1), 68 | new FrameBox<>("D", 0.020001, 0.10, 2), 69 | new FrameBox<>("E", 0.11, 0.18, 2), 70 | new FrameBox<>("F", 0.21, 0.30, 2), 71 | new FrameBox<>("G", 0.41, 0.50, 2), 72 | new FrameBox<>("H", 0.51, 0.99, 2), 73 | new FrameBox<>("I", 0.111, 0.15, 3), 74 | new FrameBox<>("J", 0.43, 0.46, 3), 75 | new FrameBox<>("K", 0.53, 0.80, 3) 76 | )); 77 | } 78 | 79 | @Test 80 | void exercise_saving_by_passing_custom_graphics_egofor_SVG_with_batik(TestInfo testInfo) throws IOException { 81 | var flamegraphView = new FlamegraphImage( 82 | FrameTextsProvider.of(f -> f.actualNode), 83 | FrameColorProvider.defaultColorProvider(__ -> Color.ORANGE), 84 | FrameFontProvider.defaultFontProvider() 85 | ); 86 | 87 | var document = GenericDOMImplementation.getDOMImplementation().createDocument( 88 | "http://www.w3.org/2000/svg", 89 | "svg", 90 | null 91 | ); 92 | var svgGraphics2D = new SVGGraphics2D(document); 93 | 94 | var wantedWidth = 200; 95 | flamegraphView.generate( 96 | simpleFrameModel(), 97 | FlamegraphView.Mode.FLAMEGRAPH, 98 | wantedWidth, 99 | svgGraphics2D, 100 | height -> { 101 | svgGraphics2D.setSVGCanvasSize(new Dimension(wantedWidth, height)); 102 | // svgGraphics2D.getRoot().setAttributeNS( 103 | // null, 104 | // "viewBox", 105 | // "0 0 " + wantedWidth + " " + height 106 | // ); 107 | } 108 | ); 109 | 110 | var stringWriter = new StringWriter(); 111 | svgGraphics2D.stream(stringWriter, true); 112 | 113 | Files.writeString(testReportDir().resolve(testInfo.getDisplayName() + "-output.svg"), stringWriter.getBuffer()); 114 | var type = "svg"; 115 | assertThat(stringWriter.toString()).isEqualTo(content(asset_fg_ak_200x72(type))); 116 | } 117 | 118 | private static String asset_fg_ak_200x72(String type) { 119 | return "/fg-ak-200x72" + platform() + 120 | "." + type; 121 | } 122 | 123 | private static String platform() { 124 | var osName = System.getProperty("os.name"); 125 | if (osName.startsWith("Mac")) { 126 | return "-macOs"; 127 | } else if (osName.startsWith("Linux")) { 128 | return Objects.equals(System.getenv("CI"), "true") ? "-gha-linux" : "linux"; 129 | } 130 | return ""; 131 | } 132 | 133 | private static String content(String name) { 134 | try { 135 | return Files.readString(Path.of(Objects.requireNonNull(ImageTestUtils.class.getResource(name)).toURI())); 136 | } catch (IOException e) { 137 | throw new UncheckedIOException(e); 138 | } catch (URISyntaxException e) { 139 | throw new RuntimeException(e); 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /fireplace-swing/src/test/java/io/github/bric3/fireplace/flamegraph/FlamegraphViewTest.java: -------------------------------------------------------------------------------- 1 | package io.github.bric3.fireplace.flamegraph; 2 | 3 | import io.github.bric3.fireplace.flamegraph.FlamegraphView.Mode; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | import java.util.List; 9 | import java.util.Objects; 10 | 11 | import static org.assertj.core.api.SoftAssertions.assertSoftly; 12 | 13 | class FlamegraphViewTest { 14 | 15 | @Test 16 | void basicApi() { 17 | var fg = new FlamegraphView(); 18 | 19 | assertSoftly(softly -> { 20 | var component = fg.component; 21 | softly.assertThat(FlamegraphView.from(component)).contains(fg); 22 | softly.assertThat(FlamegraphView.from(new JPanel())).isEmpty(); 23 | }); 24 | // non configured 25 | assertSoftly(softly -> { 26 | softly.assertThat(fg.getFrameModel()).isEqualTo(FrameModel.empty()); 27 | softly.assertThat(fg.getFrames()).isEmpty(); 28 | softly.assertThat(fg.isFrameGapEnabled()).isTrue(); 29 | 30 | softly.assertThat(fg.getMode()).isEqualTo(Mode.ICICLEGRAPH); 31 | softly.assertThat(fg.isShowMinimap()).isTrue(); 32 | softly.assertThat(fg.isShowHoveredSiblings()).isTrue(); 33 | 34 | softly.assertThat(fg.getFrameColorProvider()).isNotNull(); 35 | softly.assertThat(fg.getFrameFontProvider()).isNotNull(); 36 | softly.assertThat(fg.getFrameTextsProvider()).isNotNull(); 37 | }); 38 | 39 | // after configuration 40 | assertSoftly(softly -> { 41 | var frameTextsProvider = FrameTextsProvider.empty(); 42 | var frameColorProvider = FrameColorProvider.defaultColorProvider(box -> Color.BLACK); 43 | var frameFontProvider = FrameFontProvider.defaultFontProvider(); 44 | fg.setRenderConfiguration( 45 | frameTextsProvider, 46 | frameColorProvider, 47 | frameFontProvider 48 | ); 49 | softly.assertThat(fg.getFrameTextsProvider()).isEqualTo(frameTextsProvider); 50 | softly.assertThat(fg.getFrameColorProvider()).isEqualTo(frameColorProvider); 51 | softly.assertThat(fg.getFrameFontProvider()).isEqualTo(frameFontProvider); 52 | 53 | 54 | 55 | var frameTextsProvider2 = FrameTextsProvider.empty(); 56 | fg.setFrameTextsProvider(frameTextsProvider2); 57 | softly.assertThat(fg.getFrameTextsProvider()).isEqualTo(frameTextsProvider2); 58 | 59 | var frameColorProvider2 = FrameColorProvider.defaultColorProvider(box -> Color.BLACK); 60 | fg.setFrameColorProvider(frameColorProvider2); 61 | softly.assertThat(fg.getFrameColorProvider()).isEqualTo(frameColorProvider2); 62 | 63 | var frameFontProvider2 = FrameFontProvider.defaultFontProvider(); 64 | fg.setFrameFontProvider(frameFontProvider2); 65 | softly.assertThat(fg.getFrameFontProvider()).isEqualTo(frameFontProvider2); 66 | 67 | 68 | var frameModel = new FrameModel<>( 69 | "title", 70 | (a, b) -> Objects.equals(a.actualNode, b.actualNode), 71 | List.of(new FrameBox<>("frame1", 0.0, 0.5, 1)) 72 | ); 73 | fg.setModel(frameModel); 74 | softly.assertThat(fg.getFrameModel()).isEqualTo(frameModel); 75 | softly.assertThat(fg.getFrames()).isEqualTo(frameModel.frames); 76 | 77 | 78 | // non configured 79 | fg.setFrameGapEnabled(false); 80 | softly.assertThat(fg.isFrameGapEnabled()).isFalse(); 81 | 82 | fg.setMode(Mode.FLAMEGRAPH); 83 | softly.assertThat(fg.getMode()).isEqualTo(Mode.FLAMEGRAPH); 84 | 85 | fg.setShowMinimap(false); 86 | softly.assertThat(fg.isShowMinimap()).isFalse(); 87 | 88 | fg.setShowHoveredSiblings(false); 89 | softly.assertThat(fg.isShowHoveredSiblings()).isFalse(); 90 | }); 91 | } 92 | } -------------------------------------------------------------------------------- /fireplace-swing/src/test/resources/fg-ak-200x72-gha-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bric3/fireplace/6f004162f361b2310a52064849698205414256d3/fireplace-swing/src/test/resources/fg-ak-200x72-gha-linux.png -------------------------------------------------------------------------------- /fireplace-swing/src/test/resources/fg-ak-200x72-gha-linux.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | rootABCDEFGHK 46 | -------------------------------------------------------------------------------- /fireplace-swing/src/test/resources/fg-ak-200x72-macOs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bric3/fireplace/6f004162f361b2310a52064849698205414256d3/fireplace-swing/src/test/resources/fg-ak-200x72-macOs.png -------------------------------------------------------------------------------- /fireplace-swing/src/test/resources/fg-ak-200x72-macOs.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | rootABCDEFGHK 46 | -------------------------------------------------------------------------------- /fireplace-swt-awt-bridge/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | plugins { 11 | id("fireplace.published-java-library") 12 | id("fireplace.local-eclipse-swt-platform") 13 | } 14 | 15 | description = "SWT-AWT utils that bridge the two toolkits" 16 | 17 | dependencies { 18 | implementation(libs.eclipse.swt) // don't use api, the consuming platform will provide it 19 | } 20 | -------------------------------------------------------------------------------- /fireplace-swt-experiment-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | plugins { 11 | id("fireplace.application") 12 | id("fireplace.local-eclipse-swt-platform") 13 | } 14 | 15 | description = "SWT app that uses fireplace-swing" 16 | 17 | dependencies { 18 | implementation(projects.fireplaceSwtAwtBridge) 19 | implementation(projects.fireplaceSwing) 20 | implementation(projects.fireplaceSwingAnimation) 21 | implementation(libs.flightrecorder) 22 | } 23 | 24 | application { 25 | mainClass.set("io.github.bric3.fireplace.swt.FirePlaceSwtMain") 26 | } 27 | -------------------------------------------------------------------------------- /fireplace-swt-experiment-app/src/main/java/io/github/bric3/fireplace/swt/StyledToolTip.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | package io.github.bric3.fireplace.swt; 11 | 12 | import org.eclipse.jface.layout.GridLayoutFactory; 13 | import org.eclipse.jface.window.DefaultToolTip; 14 | import org.eclipse.swt.SWT; 15 | import org.eclipse.swt.widgets.Composite; 16 | import org.eclipse.swt.widgets.Control; 17 | import org.eclipse.swt.widgets.Event; 18 | import org.eclipse.ui.forms.widgets.FormText; 19 | 20 | /** 21 | * This tool tip extends the JFace implementation and relies on the {@link FormText} control 22 | * to render the text. 23 | * 24 | * @author brice.dutheil 25 | * @see FormText 26 | */ 27 | public class StyledToolTip extends DefaultToolTip { 28 | public StyledToolTip(Control control) { 29 | super(control); 30 | } 31 | 32 | public StyledToolTip(Control control, int style, boolean manualActivation) { 33 | super(control, style, manualActivation); 34 | } 35 | 36 | @Override 37 | protected Composite createToolTipContentArea(Event event, Composite parent) { 38 | final Composite container = setColorsAndFont(new Composite(parent, SWT.NULL), event); 39 | GridLayoutFactory.fillDefaults().margins(2, 2).generateLayout(container); 40 | var formText = setColorsAndFont(new FormText(container, SWT.NONE), event); 41 | 42 | String pseudoHtml = getText(event); 43 | 44 | formText.setText(pseudoHtml, true, false); 45 | return parent; 46 | } 47 | 48 | private T setColorsAndFont(T control, Event event) { 49 | control.setBackground(getBackgroundColor(event)); 50 | control.setForeground(getForegroundColor(event)); 51 | control.setFont(getFont(event)); 52 | return control; 53 | } 54 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | semver.tagPrefix=v 2 | # This property is necessary because the graalvm plugin is getting the version early 3 | # in the configuration phase, which causes some problesm for the plugin to read the version 4 | # from the tag, this property help the semver plugin to achiave that 5 | semver.project.tagPrefix=v 6 | semver.checkClean=false -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | # 2 | # Fireplace 3 | # 4 | # Copyright (c) 2021, Today - Brice Dutheil 5 | # 6 | # This Source Code Form is subject to the terms of the Mozilla Public 7 | # License, v. 2.0. If a copy of the MPL was not distributed with this 8 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | # 10 | 11 | [versions] 12 | jetbrains-annotations = "26.0.2" 13 | flatlaf = "3.6" 14 | darklaf = "3.0.2" 15 | classgraph = "4.8.179" 16 | 17 | radiance-animation = "7.5.0" 18 | 19 | jmc-flightrecorder = "9.1.0" 20 | 21 | junit-jupiter = "5.13.0" 22 | assertj = "3.27.3" 23 | 24 | # Used for SVG export 25 | batik = "1.19" 26 | 27 | eclipse-swt = "3.129.0" 28 | eclipse-jface = "3.36.0" 29 | eclipse-ui-forms = "3.13.400" 30 | 31 | # Kotlin 32 | kotlin = "2.1.21" 33 | kotlin-coroutines = "1.10.2" 34 | 35 | # Gradle plugins 36 | bnd = "7.1.0" 37 | test-logger = "4.0.0" 38 | semver = "0.8.0" 39 | license-report = "2.9" 40 | 41 | [libraries] 42 | jetbrains-annotations = { group = "org.jetbrains", name = "annotations", version.ref = "jetbrains-annotations" } 43 | flatlaf = { module = "com.formdev:flatlaf", version.ref = "flatlaf" } 44 | flatlaf-extras = { module = "com.formdev:flatlaf-extras", version.ref = "flatlaf" } 45 | darklaf-platform-preferences = { module = "com.github.weisj:darklaf-platform-preferences", version.ref = "darklaf" } 46 | darklaf-platform-decorations = { module = "com.github.weisj:darklaf-platform-decorations", version.ref = "darklaf" } 47 | classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } 48 | radiance-animation = { module = "org.pushing-pixels:radiance-animation", version.ref = "radiance-animation" } 49 | 50 | flightrecorder = { module = "org.openjdk.jmc:flightrecorder", version.ref = "jmc-flightrecorder" } 51 | 52 | junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" } 53 | junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" } 54 | assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" } 55 | 56 | kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref ="kotlin-coroutines" } 57 | kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref ="kotlin-coroutines" } 58 | 59 | batik-svggen = { module = "org.apache.xmlgraphics:batik-svggen", version.ref = "batik" } 60 | batik-dom = { module = "org.apache.xmlgraphics:batik-dom", version.ref = "batik" } 61 | 62 | eclipse-swt = { module = "org.eclipse.platform:org.eclipse.swt", version.ref = "eclipse-swt" } 63 | eclipse-jface = { module = "org.eclipse.platform:org.eclipse.jface", version.ref = "eclipse-jface" } 64 | eclipse-ui-forms = { module = "org.eclipse.platform:org.eclipse.ui.forms", version.ref = "eclipse-ui-forms" } 65 | 66 | # Gradle plugins for build-logic 67 | gradlePlugin-kotlin-jvm = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 68 | gradlePlugin-testLogger = { module = "com.adarshr:gradle-test-logger-plugin", version.ref = "test-logger" } 69 | gradlePlugin-bnd = { module = "biz.aQute.bnd.builder:biz.aQute.bnd.builder.gradle.plugin", version.ref = "bnd" } 70 | gradlePlugin-semver = { module = "com.javiersc.semver:semver-gradle-plugin", version.ref = "semver" } 71 | gradlePlugin-licenceReport = { module = " com.github.jk1.dependency-license-report:com.github.jk1.dependency-license-report.gradle.plugin", version.ref = "license-report" } 72 | 73 | [bundles] 74 | junit-jupiter = ["junit-jupiter-api", "junit-jupiter-engine"] 75 | flatlaf = ["flatlaf", "flatlaf-extras"] 76 | darklaf = ["darklaf-platform-preferences", "darklaf-platform-decorations"] 77 | kotlinx-coroutines = ["kotlin-coroutines-core", "kotlin-coroutines-test"] 78 | batik = ["batik-svggen", "batik-dom"] 79 | eclipse-swt = ["eclipse-swt", "eclipse-jface", "eclipse-ui-forms"] -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bric3/fireplace/6f004162f361b2310a52064849698205414256d3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Fireplace 3 | * 4 | * Copyright (c) 2021, Today - Brice Dutheil 5 | * 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | */ 10 | plugins { 11 | `gradle-enterprise` 12 | id("org.gradle.toolchains.foojay-resolver-convention") version ("1.0.0") 13 | } 14 | 15 | rootProject.name = "fireplace" 16 | includeBuild("build-logic") 17 | include( 18 | "fireplace-swing", 19 | "fireplace-swing-animation", 20 | "fireplace-app", 21 | "fireplace-swt-awt-bridge", 22 | "fireplace-swt-experiment-app", 23 | ) 24 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 25 | 26 | gradleEnterprise { 27 | if (providers.environmentVariable("CI").isPresent) { 28 | println("CI") 29 | buildScan { 30 | termsOfServiceUrl = "https://gradle.com/terms-of-service" 31 | termsOfServiceAgree = "yes" 32 | publishAlways() 33 | tag("CI") 34 | 35 | if (providers.environmentVariable("GITHUB_ACTIONS").isPresent) { 36 | link("GitHub Repository", "https://github.com/" + System.getenv("GITHUB_REPOSITORY")) 37 | link( 38 | "GitHub Commit", 39 | "https://github.com/" + System.getenv("GITHUB_REPOSITORY") + "/commits/" + System.getenv("GITHUB_SHA") 40 | ) 41 | 42 | 43 | listOf( 44 | "GITHUB_ACTION_REPOSITORY", 45 | "GITHUB_EVENT_NAME", 46 | "GITHUB_ACTOR", 47 | "GITHUB_BASE_REF", 48 | "GITHUB_HEAD_REF", 49 | "GITHUB_JOB", 50 | "GITHUB_REF", 51 | "GITHUB_REF_NAME", 52 | "GITHUB_REPOSITORY", 53 | "GITHUB_RUN_ID", 54 | "GITHUB_RUN_NUMBER", 55 | "GITHUB_SHA", 56 | "GITHUB_WORKFLOW" 57 | ).forEach { e -> 58 | val v = System.getenv(e) 59 | if (v != null) { 60 | value(e, v) 61 | } 62 | } 63 | 64 | providers.environmentVariable("GITHUB_SERVER_URL").orNull?.let { ghUrl -> 65 | val ghRepo = System.getenv("GITHUB_REPOSITORY") 66 | val ghRunId = System.getenv("GITHUB_RUN_ID") 67 | link("Summary", "$ghUrl/$ghRepo/actions/runs/$ghRunId") 68 | link("PRs", "$ghUrl/$ghRepo/pulls") 69 | 70 | // see .github/workflows/build.yaml 71 | providers.environmentVariable("GITHUB_PR_NUMBER") 72 | .orNull 73 | .takeUnless { it.isNullOrBlank() } 74 | .let { prNumber -> 75 | link("PR", "$ghUrl/$ghRepo/pulls/$prNumber") 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } --------------------------------------------------------------------------------