├── .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 |
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 |
--------------------------------------------------------------------------------
/fireplace-app/src/main/resources/dardMode-sun.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 | *
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 |
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 |
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 | }
--------------------------------------------------------------------------------