[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\"@babel/preset-env\", \"@babel/preset-react\"]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: http://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n[*]\ncharset = utf-8\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\n\n[*.java]\nindent_size = 4\ncurly_bracket_next_line = true"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  push:\n    branches: \"**\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          java-version: 17\n          distribution: temurin\n      - name: Build with SonarCloud\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n        run: |\n          mvn -B clean\n          mvn -B jacoco:prepare-agent install jacoco:report\n          mvn -B -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=porscheinformatik sonar:sonar\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags: [\"v*\"]\n  release:\n    types: [created]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n      - name: Set Release Version\n        run: |\n          mvn -B clean\n          mvn -B versions:set -DnewVersion=${GITHUB_REF:11} -DgenerateBackupPoms=false\n      - name: Build\n        run: mvn -B install\n      - name: Release\n        uses: softprops/action-gh-release@v2.5.0\n        if: startsWith(github.ref, 'refs/tags/')\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          prerelease: contains(github.ref, 'beta')\n          files: target/sonarqube-licensecheck-plugin-*.jar\n"
  },
  {
    "path": ".github/workflows/verify.yml",
    "content": "name: Verify\n\non:\n  pull_request:\n    branches: [master]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        java: [17, 21]\n    steps:\n      - uses: actions/checkout@v6\n      - name: Setup java\n        uses: actions/setup-java@v5\n        with:\n          java-version: ${{ matrix.java }}\n          distribution: temurin\n      - name: Build with Maven\n        run: |\n          mvn -B clean\n          mvn -B verify\n"
  },
  {
    "path": ".gitignore",
    "content": ".classpath\n.project\n.factorypath\n.settings/\ntarget/\nnode_modules/\nnode/\n.idea/\n*.iml\n.vscode/\n.yarn/\ndocker/plugins/*.jar\n.env\n"
  },
  {
    "path": ".prettierignore",
    "content": "pnpm-lock.yaml"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"plugins\": [\"prettier-plugin-java\"],\n  \"printWidth\": 100\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [7.1.0](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v7.0.3...v7.1.0) - 2026-02-20\n\n### Bug Fixes\n\n- Improve layout and scrolling behavior of the license dashboard (License Check page could not be scrolled after update to SonarQube Community 26.1.0) (#464, #467)\n\n### Other Changes\n\n- Update dependencies\n- Switch from yarn to pnpm for package management\n\n## [7.0.3](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v7.0.2...v7.0.3) - 2025-07-09\n\n### Bug Fixes\n\n- fix: skip artifacts with \"data\" classifier (#456, #457)  \n  Some Maven dependencies have an additional artifact with \"data\" classifiers which leads to the issue that in some cases the license cannot be found correctly.\n\n### Other Changes\n\n- chore: fix dependencies for echoes-react and bump minor versions in package.json (#455)\n\n## [7.0.2](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v7.0.1...v7.0.2) - 2025-06-23\n\n### Bug Fixes\n\n- Also check maven.repo.local is set via MAVEN_OPTS in License finding (#451)\n\n## [7.0.1](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v7.0.0...v7.0.1) - 2025-06-23\n\n### Bug Fixes\n\n- When maven.repo.local is set via MAVEN_OPTS it is not considered (#449)\n\n## [7.0.0](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v6.0.1...v7.0.0) - 2025-05-15\n\n### BREAKING CHANGES\n\nThis version is not compatible with SonarQube < 2025.0 / 25.x\n\n### Features\n\n- Update to SonarQube 2025.x/25.x in (#435)\n- Switch UI from Vue to React (#435)\n\n### Bug Fixes\n\n- Fix #409 - Switched back to baseDir by @tgwbean in #416\n\n### Other Changes\n\n- Update dependencies\n\n## [6.0.1](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v6.0.0...v6.0.1) - 2024-01-26\n\n### Bug Fixes\n\n- Dependency mapping \"overwrite\" should default to true (#413)\n\n## [6.0.0](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v5.1.1...v6.0.0) - 2023-12-15\n\n### BREAKING CHANGES\n\nThis version is not compatible with SonarQube < 9.5\n\n### Features\n\n- Compatibility with Sonar 10.x (#375)\n- Support for Scala (#352)\n- Feature to import SPDX license list (fa68e04422bdd12d05e78c72ee0a49224f6b8741)\n- Resolve node_modules relative to package.json (#380)\n- Make report path configurable in Gradle scanner (#397)\n\nOther Changes:\n\n- Use Prettier for code formatting #399\n\n## [5.1.1](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v5.1.0...v5.1.1) - 2022-11-04\n\n### Bug Fixes\n\n- Fix Kotlin seems to not be supported on 5.1.0 (#350, #351)\n- Improving NPM scan resilience (#347)\n\n### Other Changes\n\n- Fix some critical and major code smells (#353)\n- New and shiny README (#343)\n\n## [5.1.0](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v5.0.1...v5.1.0) - 2022-08-25\n\n### Bug Fixes\n\n- Rule repository for TypeScript was not registered (#315)\n- Show correct measures for branches and pull requests (#325)\n\n### Other Changes\n\n- Add defined order to settings (#342)\n\n## [5.0.1](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v5.0.0..v5.0.1) - 2022-02-24\n\n### Bug Fixes\n\n- Fix: rule repository for Kotlin was not registered (#311, #310)\n- Fix: project licenses page call (#291, #296)\n\n## [5.0.0](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v4.0.2..v5.0.0) - 2021-12-20\n\n### BREAKING CHANGES\n\n- This version uses a different format for storing the settings. The old settings will be migrated (and deleted) on the first start of SonarQube with the new version. You cannot go back to a previous version! With this change you can edit the settings via the default SonarQube UI or via the custom License Check settings. This change was necessary to remove the dependency on the internal SonarQube API. [#240, #244]\n\n### Features\n\n- Create issues on relevant dependency files (#285)\n- License and dependency mapping now available for all dependency mechanisms (Groovy, Maven, NPM). Dependency mapping has now an attribute to toggle forced mapping. [#257]\n- Support for JavaScript and Groovy projects (without any Java files) (#247, #241, #182)\n\n### Bug Fixes\n\n- Status in license report should be \"Allowed\" and \"Disallowed\" not true/false (#262)\n\n### Other Changes\n\n- Dependency updates and increase test coverage\n\n## [4.0.2](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v4.0.1..v4.0.2) - 2021-05-04\n\n### Bug Fixes\n\n- Create license with status disallowed (#230), fixes #209\n\n### Other Changes\n\n- Update SonarQube API to 8.8 (#229)\n\n## [4.0.1](https://github.com/porscheinformatik/sonarqube-licensecheck/compare/v4.0.0..v4.0.1) - 2021-05-04\n\n### Bug Fixes\n\n- Icons in UI are missing #217\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   Copyright 2015-2018 Porsche Informatik\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# SonarQube License-Check\n\n[![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=at.porscheinformatik.sonarqube.licensecheck:sonarqube-licensecheck-plugin&metric=alert_status)](https://sonarcloud.io/dashboard?id=at.porscheinformatik.sonarqube.licensecheck:sonarqube-licensecheck-plugin)\n\nThis [SonarQube](http://www.sonarqube.org/) plugin ensures that projects use dependencies with compliant licenses. All dependencies and licenses can be viewed per projects and exported to Excel 2003 XML Format. This enables a simple governance of dependencies and licenses for the whole organization.\n\n## License\n\nThis software is licensed under the [Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt)\n\n## Table of Contents\n\n<!-- TOC -->\n\n- [Features](#features)\n- [Compatibility](#compatibility)\n- [Installation](#installation)\n- [General Configuration](#configuration)\n  - [General Configuration via Administration Tab](#configuration-via-administration-tab)\n  - [General Configuration via License Menu](#configuration-via-license-menu)\n  - [Activation rules in Quality Profile](#activation-rules-in-quality-profile)\n- [Execution](#execution)\n- [Supported Languages](#supported-languages)\n- [Supported Project Types](#supported-project-types)\n  - [Maven](#maven)\n  - [NPM](#npm)\n  - [Gradle](#gradle)\n- [Configuration via Sonar API](#configuration-via-sonar-api)\n<!-- TOC -->\n\n## Features\n\n### Analysis\n\nThe plugin scans for dependencies defined in your project including all transitive dependencies.\n\nCurrently, supported formats are:\n\n- Maven POM files - all dependencies with scope \"compile\" and \"runtime\" are checked\n- Gradle projects which use JK1 plugin\n- NPM package.json files - all dependencies (except \"devDependencies\") are checked\n  - Note that transitive dependencies are _not_ scanned unless `licensecheck.npm.resolvetransitive` is set to `true`.\n\n    ![Transitive](docs/Administration_General_Settings_License_Check_2.png)\n\n### Project Dashboard\n\nThe plugin contains a project dashboard showing a list of dependencies with version and a list of all used licences. Each table shows the status of the license\n(allowed, not allowed, not found). You can also export the data to Excel.\n\n![Project Dashboard](docs/License_Check_dashboard.png)\n\n## Compatibility\n\nThis plugin is compatible:\n\n- 7.x is compatible with SonarQube 25.x / 2025 LTS and 26.x / 2026 LTS version\n- 6.x is compatible with SonarQube 9 LTS (>= 9.5) and 10.x\n- 5.x is compatible with SonarQube 8.9 LTS and < 10 (9.x is compatible)\n- 4.x is compatible with SonarQube 8.x\n\nFor all changes see [CHANGELOG.md](CHANGELOG.md)\n\n## Installation\n\nPut the pre-built jar-file (from release downloads) in the directory `$SONARQUBE_HOME/extensions/plugins` and\nrestart the server to install the plugin. Activate the rules of this plugin (\"License is not allowed\", \"Dependency has unknown license\") in your SonarQube quality profiles - otherwise the plugin is not executed.\n\n## Configuration\n\nAfter booting the SonarQube Server with the License-Check Plugin be found in the tab <b>Administration</b> or also in the <b>Configuration -> LicenseCheck</b> drop down menu.\n\n### Configuration via Administration Tab\n\n- Within the <b>General Settings</b> and <b>License Check</b> you find the settings for the plugin.\n- Within the general settings the plugin can be manually enabled or disabled. By default, it is enabled.\n  - Under \"Dependency Mapping\" you can map a dependency name/key (with regex) to a license, e.g. `^asm:asm$` to \"BSD-3-Clause\"\n  - Under \"License Mapping\" you can map a license name (with regex) to a license, e.g. `.*Apache.*2.*` to \"Apache-2.0\".\n\n    ![License Configuration1](docs/Administration_General_Settings_License_Check_1.png)\n\n  - Under \"Licenses\" you can allow or disallow licenses globally and add/edit the list of known licenses.\n\n    ![License Configuration2](docs/Administration_General_Settings_License_Check_3.png)\n\n  - Under \"Project Licenses\" you can allow and disallow licenses for a specific project.\n\n    ![License Configuration2](docs/Administration_General_Settings_License_Check_2.png)\n\n### Configuration via License Menu\n\nAdministration -> Configuration(dropdown) -> License Check\n\n![alternative License Configuration1](docs/1-nice-General%20Settings%20-%20Administration.png)\n\n- Under \"Licenses\" you can allow or disallow licenses globally and add/edit the list of known licenses.\n\n  ![alternative License Configuration2](docs/2-nice-License%20Check%20-%20Administration.png)\n\n  ![alternative License Configuration3](docs/3-nice-License%20Check%20-%20Administration.png)\n\n- Under \"Project Licenses\" you can allow and disallow licenses for a specific project.\n\n  ![alternative License Configuration4](docs/4-nice-License%20Check%20-%20Administration.png)\n\n  ![alternative License Configuration5](docs/5-nice-License%20Check%20-%20Administration.png)\n\n- Under \"Dependency Mapping\" you can map a dependency name/key (with regex) to a license, e.g. `^asm:asm$` to \"BSD-3-Clause\"\n\n  ![alternative License Configuration6](docs/6-nice-License%20Check%20-%20Administration.png)\n\n  ![alternative License Configuration7](docs/7-nice-License%20Check%20-%20Administration.png)\n\n- Under \"License Mappings\" you can map a license name (with regex) to a license, e.g. `.*Apache.*2.*` to \"Apache-2.0\".\n\n  ![alternative License Configuration8](docs/8-nice-License%20Check%20-%20Administration.png)\n\n  ![alternative License Configuration9](docs/9-nice-License%20Check%20-%20Administration.png)\n\n### Activation rules in Quality Profile\n\nYou have to activate the new rules in a (new) quality profile, for each supported language (Groovy, Kotlin, Java, JavaScript, TypeScript) And you have to use this profile for your project.\n\n1. Step 1\n\n   ![activate 1](docs/profile/activate_profile1.png)\n\n2. Step 2\n\n   ![activate 2](docs/profile/activate_profile2.png)\n\n3. Step 3\n\n   ![activate 3](docs/profile/activate_profile3.png)\n\n4. Step 4\n\n   ![activate 4](docs/profile/activate_profile4.png)\n\n5. Step 5\n\n   ![activate 5](docs/profile/activate_profile5.png)\n\n6. Step 6\n\n   ![activate 6](docs/profile/activate_profile6.png)\n\n7. Step 7\n\n   ![activate 7](docs/profile/activate_profile7.png)\n\n## Execution\n\nWhen a project is analyzed using the `mvn sonar:sonar` in command line the extension is started automatically.\n\nPlease make sure to have all dependencies installed before launching the SonarQube analysis. So your complete build\nshould look something like this:\n\n    mvn -B org.jacoco:jacoco-maven-plugin:prepare-agent install org.jacoco:jacoco-maven-plugin:report\n    mvn -B sonar:sonar\n\n## Supported Languages\n\nGroovy, Kotlin, Java, JavaScript, TypeScript\n\n## Supported Project Types\n\n### Maven + NPM\n\nWhen using Maven and a Javascript Package Manager, define the `sonar.sources` property to point to the files which contain dependency information.\n\n```xml\n...\n<properties>\n  <sonar.sources>pom.xml,package.json</sonar.sources>\n<properties>\n...\n```\n\n### Maven\n\nMaven works if your project/module has a `pom.xml` on its root level (running with Maven, Gradle or SonarScanner).\n\n### NPM\n\nNPM works if your project/module has a `package.json` on its root level (running with Maven, Gradle or SonarScanner).\n\n### Gradle\n\nGradle project should use JK1 plugin https://github.com/jk1/Gradle-License-Report\n\nNote: Please check above link for instructions or follow as mentioned below\n\n**Step1:** Update `build.gradle` file with following code for using JK1 plugin\n\n    import com.github.jk1.license.filter.LicenseBundleNormalizer\n    import com.github.jk1.license.render.JsonReportRenderer\n\n    plugins {\n      id 'com.github.jk1.dependency-license-report' version '1.13'\n    }\n\n    licenseReport {\n        allowedLicensesFile = new File(\"$projectDir/src/main/resources/licenses/allowed-licenses.json\")\n        renderers = new JsonReportRenderer('license-details.json', false)\n        filters = [new LicenseBundleNormalizer()]\n    }\n\n**Step 2:** Update `build.gradle` file with following code for using SonarQube plugin\n\n    plugins {\n        id 'org.sonarqube' version \"3.0\"\n    }\n\n    jar {\n        enabled = true\n    }\n\n    sonarqube {\n        properties {\n            property \"sonar.host.url\", \"http://localhost:9000\"\n        }\n    }\n\n**Step 3:** run following command to generate your report `license-details.json` in `build/reports/dependency-license`\n\n    > gradle generateLicenseReport\n\n**Step 4:** run following command for SonarQube\n\n    > gradle sonarqube\n\n### Configuration via Sonar API\n\nYou can also use the [Sonar API](https://docs.sonarqube.org/latest/extend/web-api/) to configure the plugin.\n\n#### Plugin Activation\n\n- Get the setting\n\n  ```\n  curl -X GET -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/values?keys=licensecheck.activation\"\n  ```\n\n- Enable\n\n  ```\n  curl -X POST -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/set?key=licensecheck.activation&value=true\"\n  ```\n\n- Disable\n  ```\n  curl -X POST -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/set?key=licensecheck.activation&value=false\"\n  ```\n\n#### Global License Settings\n\n- Get the setting\n  ```\n  curl -X GET -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/values?keys=licensecheck.license-set\"\n  ```\n\n#### Project License Settings\n\n- Get the setting\n\n```\ncurl -X GET -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/values?keys=licensecheck.project-license-set\"\n```\n\n#### License Mapping\n\n- Get the setting\n\n  ```\n  curl -X GET -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/values?keys=licensecheck.license-mapping\"\n  ```\n\n#### Dependency Mapping\n\n- Get the setting\n\n  ```\n  curl -X GET -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/values?keys=licensecheck.dep-mapping\"\n  ```\n\n#### NPM Transitive setting\n\n- Get the setting\n\n  ```\n  curl -X GET -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/values?keys=licensecheck.npm.resolvetransitive\"\n  ```\n\n- Enable\n\n  ```\n  curl -X POST -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/set?key=licensecheck.npm.resolvetransitive&value=true\"\n  ```\n\n- Disable\n\n  ```\n  curl -X POST -v -u USERNAME:PASSWORD \"http://localhost:9000/api/settings/set?key=licensecheck.npm.resolvetransitive&value=false\"\n  ```\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: \"3\"\n\nservices:\n  sonarqube:\n    image: sonarqube:25.3.0.104237-community\n    depends_on:\n      - db\n    environment:\n      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar\n      SONAR_JDBC_USERNAME: sonar\n      SONAR_JDBC_PASSWORD: sonar\n    volumes:\n      - sonarqube_data:/opt/sonarqube/data\n      - ./target/sonarqube-licensecheck-plugin-7.0.0-SNAPSHOT.jar:/opt/sonarqube/extensions/plugins/sonarqube-licensecheck-plugin-7.0.0-SNAPSHOT.jar\n      - sonarqube_logs:/opt/sonarqube/logs\n    ports:\n      - \"9100:9000\"\n  db:\n    image: postgres:17-alpine\n    environment:\n      POSTGRES_USER: sonar\n      POSTGRES_PASSWORD: sonar\n    volumes:\n      - postgresql:/var/lib/postgresql\n      - postgresql_data:/var/lib/postgresql/data\n\nvolumes:\n  sonarqube_data:\n  sonarqube_logs:\n  postgresql:\n  postgresql_data:\n"
  },
  {
    "path": "jsconfig.json",
    "content": "{\n  \"include\": [\"./src/main/web/**/*\"]\n}\n"
  },
  {
    "path": "mise.toml",
    "content": "[tools]\njava = \"temurin-21\"\nmaven = \"3\"\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"sonarqube-licensecheck\",\n  \"version\": \"7.0.0\",\n  \"description\": \"License Check Plugin for SonarQube\",\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"webpack --mode production\",\n    \"dev\": \"webpack --mode development --watch\",\n    \"test-server\": \"node test-server.js\",\n    \"generate-icons\": \"vsvg -s ./svg-icons -t ./src/compiled-icons\",\n    \"prettier\": \"prettier --write '**/*.{js,jsx,java,html,css,json,md,yml,yaml}'\",\n    \"prettier-check\": \"prettier --check '**/*.{js,jsx,java,html,css,json,md,yml,yaml}'\"\n  },\n  \"author\": \"Christian Köberl\",\n  \"license\": \"Apache-2.0\",\n  \"dependencies\": {\n    \"@emotion/react\": \"^11.14.0\",\n    \"@sonarsource/echoes-react\": \"^1.12.0\",\n    \"file-saverjs\": \"^1.3.6\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\",\n    \"react-intl\": \"^6.8.9\",\n    \"react-router-dom\": \"^6.30.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.28.0\",\n    \"@babel/preset-env\": \"^7.28.0\",\n    \"@babel/preset-react\": \"^7.27.1\",\n    \"@types/react\": \"^19.2.14\",\n    \"babel-loader\": \"^9.2.1\",\n    \"buble\": \"0.20.0\",\n    \"buble-loader\": \"0.5.1\",\n    \"concurrently\": \"9.2.1\",\n    \"css-loader\": \"^7.1.4\",\n    \"http-proxy\": \"1.18.1\",\n    \"prettier\": \"3.8.1\",\n    \"prettier-plugin-java\": \"2.8.1\",\n    \"style-loader\": \"^4.0.0\",\n    \"webpack\": \"^5.99.9\",\n    \"webpack-cli\": \"^6.0.1\"\n  },\n  \"packageManager\": \"pnpm@10.29.3\"\n}\n"
  },
  {
    "path": "pom.xml",
    "content": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n  xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n  <modelVersion>4.0.0</modelVersion>\n\n  <groupId>at.porscheinformatik.sonarqube.licensecheck</groupId>\n  <artifactId>sonarqube-licensecheck-plugin</artifactId>\n  <version>7.0.0-SNAPSHOT</version>\n  <name>SonarQube License Check Plugin</name>\n  <packaging>sonar-plugin</packaging>\n\n  <description>Checks Maven and NPM dependencies against a defined set of allowed licenses and reports any unknown or\n    not allowed licenses as an issue.\n  </description>\n  <url>https://github.com/porscheinformatik/sonarqube-licensecheck</url>\n\n  <organization>\n    <name>Porsche Informatik</name>\n    <url>https://www.porscheinformatik.at</url>\n  </organization>\n\n  <developers>\n    <developer>\n      <id>derkoe</id>\n      <name>Christian Köberl</name>\n    </developer>\n    <developer>\n      <name>Stephanie Kaschnitz</name>\n    </developer>\n  </developers>\n\n  <issueManagement>\n    <system>github</system>\n    <url>https://github.com/porscheinformatik/sonarqube-licensecheck/issues</url>\n  </issueManagement>\n\n  <licenses>\n    <license>\n      <name>Apache License, Version 2.0</name>\n      <url>https://www.apache.org/licenses/LICENSE-2.0</url>\n    </license>\n  </licenses>\n\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    <sonar.sources>src/main,pom.xml,package.json</sonar.sources>\n    <sonar.pluginTermsConditionsUrl>\n      https://github.com/porscheinformatik/sonarqube-licensecheck/blob/master/LICENSE</sonar.pluginTermsConditionsUrl>\n    <sonar.apiVersion>11.0.0.2664</sonar.apiVersion>\n    <sonar.apiImplVersion>25.1.0.102122</sonar.apiImplVersion>\n    <java.version>17</java.version>\n  </properties>\n\n  <dependencies>\n    <dependency>\n      <groupId>org.glassfish</groupId>\n      <artifactId>javax.json</artifactId>\n      <version>1.1.4</version>\n    </dependency>\n    <dependency>\n      <groupId>junit</groupId>\n      <artifactId>junit</artifactId>\n      <version>4.13.2</version>\n      <scope>test</scope>   \n    </dependency>\n    <dependency>\n      <groupId>org.hamcrest</groupId>\n      <artifactId>hamcrest</artifactId>\n      <version>3.0</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.hamcrest</groupId>\n      <artifactId>hamcrest-library</artifactId>\n      <version>3.0</version>\n      <scope>test</scope> \n    </dependency>\n    <dependency>\n      <groupId>org.mockito</groupId>\n      <artifactId>mockito-core</artifactId>\n      <version>5.17.0</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.commons</groupId>\n      <artifactId>commons-lang3</artifactId>\n      <version>3.18.0</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.api.plugin</groupId>\n      <artifactId>sonar-plugin-api</artifactId>\n      <version>${sonar.apiVersion}</version>\n      <scope>provided</scope>\n    </dependency>\n    <dependency>\n      <groupId>org.sonarsource.sonarqube</groupId>\n      <artifactId>sonar-plugin-api-impl</artifactId>\n      <version>${sonar.apiImplVersion}</version>\n      <scope>test</scope>\n    </dependency>\n    <dependency>\n      <groupId>xpp3</groupId>\n      <artifactId>xpp3</artifactId>\n      <version>1.1.4c</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.maven</groupId>\n      <artifactId>maven-model</artifactId>\n      <version>3.9.9</version>\n    </dependency>\n    <dependency>\n      <groupId>org.apache.maven.shared</groupId>\n      <artifactId>maven-invoker</artifactId>\n      <version>3.3.0</version>\n    </dependency>\n  </dependencies>\n\n  <scm>\n    <connection>scm:git:https://github.com/porscheinformatik/sonarqube-licensecheck.git</connection>\n    <developerConnection>https://github.com/porscheinformatik/sonarqube-licensecheck.git</developerConnection>\n    <url>https://github.com/porscheinformatik/sonarqube-licensecheck</url>\n    <tag>HEAD</tag>\n  </scm>\n\n  <build>\n    <plugins>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-enforcer-plugin</artifactId>\n        <version>3.5.0</version>\n        <executions>\n          <execution>\n            <id>enforce-maven</id>\n            <goals>\n              <goal>enforce</goal>\n            </goals>\n            <configuration>\n              <rules>\n                <requireMavenVersion>\n                  <version>3.7</version>\n                </requireMavenVersion>\n              </rules>    \n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>\n        <artifactId>sonar-packaging-maven-plugin</artifactId>\n        <version>1.23.0.740</version>\n        <extensions>true</extensions>\n        <configuration>\n          <pluginKey>licensecheck</pluginKey>\n          <pluginClass>at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPlugin</pluginClass>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.14.0</version>\n        <configuration>\n          <source>${java.version}</source>\n          <target>${java.version}</target>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-release-plugin</artifactId>\n        <version>3.1.1</version>\n      </plugin>\n      <plugin>\n        <artifactId>maven-surefire-plugin</artifactId>\n        <version>3.5.3</version>\n        <configuration>\n          <systemPropertyVariables>\n            <maven.home>${maven.home}</maven.home>\n          </systemPropertyVariables>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.jacoco</groupId>\n        <artifactId>jacoco-maven-plugin</artifactId>\n        <version>0.8.13</version>\n        <executions>\n          <execution>\n            <id>prepare-agent</id>\n            <goals>\n              <goal>prepare-agent</goal>\n            </goals>\n          </execution>\n          <execution>\n            <id>report</id>\n            <goals>\n              <goal>report</goal>\n            </goals>\n          </execution>\n        </executions>\n      </plugin>\n      <plugin>\n        <groupId>com.github.eirslett</groupId>\n        <artifactId>frontend-maven-plugin</artifactId>\n        <version>1.15.1</version>\n        <executions>\n          <execution>\n            <id>install-node-and-corepack</id>\n            <goals>\n              <goal>install-node-and-corepack</goal>\n            </goals>\n            <phase>generate-resources</phase>\n          </execution>\n          <execution>\n            <id>pnpm install</id>\n            <goals>\n              <goal>corepack</goal>\n            </goals>\n            <configuration>\n              <arguments>pnpm install</arguments>\n            </configuration>\n          </execution>\n          <execution>\n            <id>pnpm run prettier-check</id>\n            <goals>\n              <goal>corepack</goal>\n            </goals>\n            <configuration>\n              <arguments>pnpm run prettier-check</arguments>\n            </configuration>\n            <phase>test</phase>\n          </execution>\n          <execution>\n            <id>pnpm run build</id>\n            <goals>\n              <goal>corepack</goal>\n            </goals>\n            <configuration>\n              <arguments>pnpm run build</arguments>\n            </configuration>\n          </execution>\n        </executions>\n        <configuration>\n          <nodeVersion>v24.13.1</nodeVersion>\n        </configuration>\n      </plugin>\n      <plugin>\n        <artifactId>maven-resources-plugin</artifactId>\n        <version>3.3.1</version>\n        <executions>\n          <execution>\n            <id>copy-resources</id>\n            <phase>install</phase>\n            <goals>\n              <goal>copy-resources</goal>\n            </goals>\n            <configuration>\n              <outputDirectory>${project.basedir}/docker/plugins</outputDirectory>\n              <resources>\n                <resource>\n                  <directory>${project.build.directory}</directory>\n                  <includes>\n                    <include>${project.artifactId}-${project.version}.jar</include>\n                  </includes>\n                </resource>\n              </resources>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n\n\n</project>"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\"config:base\", \"group:allNonMajor\"],\n  \"labels\": [\"dependencies\"],\n  \"packageRules\": [\n    {\n      \"description\": \"org.sonarsource.sonarqube packages\",\n      \"packagePatterns\": [\"^org.sonarsource.sonarqube:\"],\n      \"groupName\": \"org.sonarsource.sonarqube\",\n      \"enabled\": false\n    },\n    {\n      \"groupName\": \"Docker\",\n      \"matchDatasources\": [\"docker\"]\n    },\n    {\n      \"groupName\": \"Maven non-major dependencies\",\n      \"matchDatasources\": [\"maven\"],\n      \"matchUpdateTypes\": [\"minor\", \"patch\"]\n    },\n    {\n      \"groupName\": \"NPM non-major dependencies\",\n      \"matchDatasources\": [\"npm\"],\n      \"matchUpdateTypes\": [\"minor\", \"patch\"]\n    }\n  ]\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/Dependency.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.LANG_JAVA;\n\nimport java.io.StringWriter;\nimport java.util.Collection;\nimport java.util.Objects;\nimport java.util.TreeSet;\nimport javax.json.Json;\nimport javax.json.stream.JsonGenerator;\nimport org.sonar.api.batch.fs.InputComponent;\nimport org.sonar.api.batch.fs.TextRange;\n\npublic class Dependency implements Comparable<Dependency> {\n\n    private String name;\n    private String version;\n    private String license;\n    private String lang;\n    private Status status;\n    private String pomPath;\n    private InputComponent inputComponent;\n    private TextRange textRange;\n\n    public Dependency(String name, String version, String license, String lang) {\n        super();\n        this.name = name;\n        this.version = version;\n        this.license = license;\n        this.lang = lang;\n    }\n\n    public Dependency(String name, String version, String license) {\n        this(name, version, license, LANG_JAVA);\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getVersion() {\n        return version;\n    }\n\n    public void setVersion(String version) {\n        this.version = version;\n    }\n\n    public String getLicense() {\n        return license;\n    }\n\n    public void setLicense(String license) {\n        this.license = license;\n    }\n\n    public String getLang() {\n        return lang;\n    }\n\n    public void setLang(String lang) {\n        this.lang = lang;\n    }\n\n    public void setStatus(final Status status) {\n        this.status = status;\n    }\n\n    public Status getStatus() {\n        return status;\n    }\n\n    public String getPomPath() {\n        return pomPath;\n    }\n\n    public void setPomPath(String pomPath) {\n        this.pomPath = pomPath;\n    }\n\n    public InputComponent getInputComponent() {\n        return inputComponent;\n    }\n\n    public void setInputComponent(InputComponent inputComponent) {\n        this.inputComponent = inputComponent;\n    }\n\n    public TextRange getTextRange() {\n        return textRange;\n    }\n\n    public void setTextRange(TextRange textRange) {\n        this.textRange = textRange;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        Dependency that = (Dependency) o;\n        return (\n            Objects.equals(name, that.name) &&\n            Objects.equals(version, that.version) &&\n            Objects.equals(license, that.license)\n        );\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(name, version, license);\n    }\n\n    @Override\n    public String toString() {\n        return \"{name:\" + name + \", version:\" + version + \", license:\" + license + \"}\";\n    }\n\n    @Override\n    public int compareTo(Dependency o) {\n        if ((o == null) || (o.name == null)) {\n            return 1;\n        } else if (this.name == null) {\n            return -1;\n        }\n\n        return this.name.compareTo(o.name);\n    }\n\n    public static String createString(Collection<Dependency> dependencies) {\n        TreeSet<Dependency> sortedDependencies = new TreeSet<>(dependencies);\n\n        StringWriter jsonString = new StringWriter();\n        JsonGenerator generator = Json.createGenerator(jsonString);\n        generator.writeStartArray();\n        for (Dependency dependency : sortedDependencies) {\n            String license = dependency.getLicense();\n            generator.writeStartObject();\n            generator.write(\"name\", dependency.getName());\n            generator.write(\"version\", dependency.getVersion());\n            generator.write(\"license\", license != null ? license : \" \");\n            generator.write(\"lang\", dependency.getLang());\n            generator.writeEnd();\n        }\n        generator.writeEnd();\n        generator.close();\n        return jsonString.toString();\n    }\n\n    public enum Status {\n        Allowed,\n        Forbidden,\n        Unknown,\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckMetrics.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport org.sonar.api.measures.CoreMetrics;\nimport org.sonar.api.measures.Metric;\nimport org.sonar.api.measures.Metrics;\n\npublic class LicenseCheckMetrics implements Metrics {\n\n    public static final String LICENSE_CHECK_DEPENDENCY_KEY = \"licensecheck.dependency\";\n    private static final String LICENSE_CHECK_LICENSE_KEY = \"licensecheck.license\";\n    public static final String DEPENDENCIES = \"Dependencies\";\n\n    public static final Metric<String> DEPENDENCY = new Metric.Builder(\n        LICENSE_CHECK_DEPENDENCY_KEY,\n        \"License Check - Dependencies\",\n        Metric.ValueType.DATA\n    )\n        .setDescription(\"Used Dependencies\")\n        .setDirection(Metric.DIRECTION_WORST)\n        .setDomain(CoreMetrics.DOMAIN_GENERAL)\n        .create();\n\n    public static final Metric<String> LICENSE = new Metric.Builder(\n        LICENSE_CHECK_LICENSE_KEY,\n        \"License Check - Licenses\",\n        Metric.ValueType.DATA\n    )\n        .setDescription(\"Used Libraries\")\n        .setDirection(Metric.DIRECTION_WORST)\n        .setDomain(CoreMetrics.DOMAIN_GENERAL)\n        .create();\n\n    public static final Metric<Integer> NO_LICENSES = new Metric.Builder(\n        \"licensecheck.no_licenses\",\n        \"Number of Licenses\",\n        Metric.ValueType.INT\n    )\n        .setDescription(\"Number of licences of all used dependencies\")\n        .setDomain(DEPENDENCIES)\n        .create();\n\n    public static final Metric<Integer> NO_LICENSES_FORBIDDEN = new Metric.Builder(\n        \"licensecheck.no_licenses_forbidden\",\n        \"Number of Forbidden Licenses\",\n        Metric.ValueType.INT\n    )\n        .setDescription(\"Number of forbidden licences (of all used dependencies)\")\n        .setDomain(DEPENDENCIES)\n        .create();\n\n    public static final Metric<Integer> NO_DEPENDENCIES = new Metric.Builder(\n        \"licensecheck.no_dependencies\",\n        \"Number of Dependencies\",\n        Metric.ValueType.INT\n    )\n        .setDescription(\"Number of used dependencies\")\n        .setDomain(DEPENDENCIES)\n        .create();\n\n    public static final Metric<Integer> NO_DEPENDENCIES_WITH_FORBIDDEN_LICENSE = new Metric.Builder(\n        \"licensecheck.no_dependencies_forbidden\",\n        \"Dependencies with Forbidden License\",\n        Metric.ValueType.INT\n    )\n        .setDescription(\"Number of used dependencies with forbidden licenses\")\n        .setDomain(DEPENDENCIES)\n        .create();\n\n    public static final Metric<Integer> NO_DEPENDENCIES_WITH_UNKNOWN_LICENSE = new Metric.Builder(\n        \"licensecheck.no_dependencies_unknown\",\n        \"Dependencies with Unknown License\",\n        Metric.ValueType.INT\n    )\n        .setDescription(\"Number of used dependencies with unknown licenses\")\n        .setDomain(DEPENDENCIES)\n        .create();\n\n    @Override\n    public List<Metric> getMetrics() {\n        return Arrays.asList(\n            DEPENDENCY,\n            LICENSE,\n            NO_DEPENDENCIES,\n            NO_LICENSES,\n            NO_LICENSES_FORBIDDEN,\n            NO_DEPENDENCIES_WITH_FORBIDDEN_LICENSE,\n            NO_DEPENDENCIES_WITH_UNKNOWN_LICENSE\n        );\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckPageDefinition.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport org.sonar.api.web.page.Context;\nimport org.sonar.api.web.page.Page;\nimport org.sonar.api.web.page.PageDefinition;\n\npublic class LicenseCheckPageDefinition implements PageDefinition {\n\n    @Override\n    public void define(Context context) {\n        context.addPage(\n            Page.builder(\"licensecheck/configuration\")\n                .setName(\"License Check\")\n                .setAdmin(true)\n                .build()\n        );\n\n        context.addPage(\n            Page.builder(\"licensecheck/dashboard\")\n                .setName(\"License Check\")\n                .setScope(Page.Scope.COMPONENT)\n                .setComponentQualifiers(Page.Qualifier.PROJECT)\n                .build()\n        );\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckPlugin.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMapping;\nimport at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMappingService;\nimport at.porscheinformatik.sonarqube.licensecheck.license.License;\nimport at.porscheinformatik.sonarqube.licensecheck.license.LicenseService;\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMapping;\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;\nimport at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicense;\nimport at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicenseService;\nimport java.util.Arrays;\nimport java.util.List;\nimport org.sonar.api.Plugin;\nimport org.sonar.api.PropertyType;\nimport org.sonar.api.config.PropertyDefinition;\nimport org.sonar.api.config.PropertyFieldDefinition;\n\npublic class LicenseCheckPlugin implements Plugin {\n\n    private static final String LICENSE_ID_DESCRIPTION =\n        \"The identifier of the license (e.g. GPL-3.0)\";\n    public static final String LICENSE_IDENTIFIER = \"License Identifier\";\n\n    @Override\n    public void define(Context context) {\n        context.addExtensions(getExtensions());\n    }\n\n    private List<?> getExtensions() {\n        return Arrays.asList(\n            ValidateLicenses.class,\n            LicenseCheckSensor.class,\n            LicenseCheckMetrics.class,\n            LicenseCheckPageDefinition.class,\n            LicenseCheckRulesDefinition.class,\n            LicenseService.class,\n            DependencyMappingService.class,\n            LicenseMappingService.class,\n            ProjectLicenseService.class,\n            PropertyDefinition.builder(LicenseCheckPropertyKeys.LICENSE_SET)\n                .category(LicenseCheckPropertyKeys.CATEGORY)\n                .type(PropertyType.PROPERTY_SET)\n                .name(\"Licenses\")\n                .description(\"List of known licenses with allowed status\")\n                .fields(\n                    PropertyFieldDefinition.build(License.FIELD_ID)\n                        .name(\"Identifier\")\n                        .description(LICENSE_ID_DESCRIPTION)\n                        .type(PropertyType.STRING)\n                        .build(),\n                    PropertyFieldDefinition.build(License.FIELD_NAME)\n                        .name(\"Name\")\n                        .description(\n                            \"The name of the license (e.g. GNU General Public License v3.0 only).\"\n                        )\n                        .type(PropertyType.STRING)\n                        .build(),\n                    PropertyFieldDefinition.build(License.FIELD_ALLOWED)\n                        .name(\"Allowed\")\n                        .description(\"If the license is allowed to use\")\n                        .type(PropertyType.BOOLEAN)\n                        .build()\n                )\n                .index(3)\n                .build(),\n            PropertyDefinition.builder(LicenseCheckPropertyKeys.DEPENDENCY_MAPPING)\n                .category(LicenseCheckPropertyKeys.CATEGORY)\n                .type(PropertyType.PROPERTY_SET)\n                .name(\"Dependency Mapping\")\n                .description(\"Maps a dependency name/key (with regex) to a license\")\n                .fields(\n                    PropertyFieldDefinition.build(DependencyMapping.FIELD_KEY)\n                        .name(\"Dependency\")\n                        .description(\"A regular expression to match against the dependency key.\")\n                        .type(PropertyType.REGULAR_EXPRESSION)\n                        .build(),\n                    PropertyFieldDefinition.build(DependencyMapping.FIELD_LICENSE)\n                        .name(LICENSE_IDENTIFIER)\n                        .description(LICENSE_ID_DESCRIPTION)\n                        .type(PropertyType.STRING)\n                        .build(),\n                    PropertyFieldDefinition.build(DependencyMapping.FIELD_OVERWRITE)\n                        .name(\"Overwrite License\")\n                        .description(\"Overwrite the license defined by the dependency.\")\n                        .type(PropertyType.BOOLEAN)\n                        .build()\n                )\n                .index(5)\n                .build(),\n            PropertyDefinition.builder(LicenseCheckPropertyKeys.LICENSE_MAPPING)\n                .category(LicenseCheckPropertyKeys.CATEGORY)\n                .type(PropertyType.PROPERTY_SET)\n                .name(\"License Mapping\")\n                .description(\"Maps a license name (with regex) to a license\")\n                .fields(\n                    PropertyFieldDefinition.build(LicenseMapping.FIELD_REGEX)\n                        .name(\"License name\")\n                        .description(\"A regular expression to match against the license name.\")\n                        .type(PropertyType.REGULAR_EXPRESSION)\n                        .build(),\n                    PropertyFieldDefinition.build(LicenseMapping.FIELD_LICENSE)\n                        .name(LICENSE_IDENTIFIER)\n                        .description(LICENSE_ID_DESCRIPTION)\n                        .type(PropertyType.STRING)\n                        .build()\n                )\n                .index(4)\n                .build(),\n            PropertyDefinition.builder(LicenseCheckPropertyKeys.PROJECT_LICENSE_SET)\n                .category(LicenseCheckPropertyKeys.CATEGORY)\n                .type(PropertyType.PROPERTY_SET)\n                .name(\"Project Licenses\")\n                .description(\"Allow/disallow licenses for specific projects.\")\n                .fields(\n                    PropertyFieldDefinition.build(ProjectLicense.FIELD_PROJECT_KEY)\n                        .name(\"Project key\")\n                        .description(\"The project key\")\n                        .type(PropertyType.REGULAR_EXPRESSION)\n                        .build(),\n                    PropertyFieldDefinition.build(ProjectLicense.FIELD_LICENSE)\n                        .name(LICENSE_IDENTIFIER)\n                        .description(LICENSE_ID_DESCRIPTION)\n                        .type(PropertyType.STRING)\n                        .build(),\n                    PropertyFieldDefinition.build(ProjectLicense.FIELD_ALLOWED)\n                        .name(\"Allowed\")\n                        .description(\"If the license is allowed to use\")\n                        .type(PropertyType.BOOLEAN)\n                        .build()\n                )\n                .index(6)\n                .build(),\n            PropertyDefinition.builder(LicenseCheckPropertyKeys.NPM_RESOLVE_TRANSITIVE_DEPS)\n                .category(LicenseCheckPropertyKeys.CATEGORY)\n                .name(\"NPM Transitive Dependencies\")\n                .description(\"Scan transitive dependencies for NPM packages\")\n                .type(PropertyType.BOOLEAN)\n                .defaultValue(\"false\")\n                .index(2)\n                .build(),\n            PropertyDefinition.builder(LicenseCheckPropertyKeys.ACTIVATION_KEY)\n                .category(LicenseCheckPropertyKeys.CATEGORY)\n                .name(\"Activate\")\n                .description(\"Activate license check\")\n                .type(PropertyType.BOOLEAN)\n                .defaultValue(\"true\")\n                .index(1)\n                .build(),\n            PropertyDefinition.builder(LicenseCheckPropertyKeys.GRADLE_JSON_REPORT_PATH)\n                .category(LicenseCheckPropertyKeys.CATEGORY)\n                .name(\"Gradle report path\")\n                .description(\"Path to search for the license report in gradle scanner\")\n                .type(PropertyType.STRING)\n                .index(7)\n                .build()\n        );\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckPropertyKeys.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\npublic class LicenseCheckPropertyKeys {\n\n    /**\n     * Category for all settings.\n     */\n    public static final String CATEGORY = \"License Check\";\n\n    /**\n     * Config key for activating/deactivating the plugin\n     */\n    public static final String ACTIVATION_KEY = \"licensecheck.activation\";\n\n    /**\n     * Config key for the list of licenses\n     */\n    public static final String LICENSE_SET = \"licensecheck.license-set\";\n\n    /**\n     * Config key for the list of project specific allowed/disallowed licenses\n     */\n    public static final String PROJECT_LICENSE_SET = \"licensecheck.project-license-set\";\n\n    /**\n     * Config key the Maven license name mapping\n     */\n    public static final String LICENSE_MAPPING = \"licensecheck.license-mapping\";\n\n    /**\n     * Config key the Maven dependency to license mapping\n     */\n    public static final String DEPENDENCY_MAPPING = \"licensecheck.dep-mapping\";\n\n    /**\n     * Config key to enable/disable transitive dependencies for NPM\n     */\n    public static final String NPM_RESOLVE_TRANSITIVE_DEPS = \"licensecheck.npm.resolvetransitive\";\n\n    /**\n     * Config key to configure the path to look for the license report in the gradle scanner.\n     */\n    public static final String GRADLE_JSON_REPORT_PATH = \"licenseCheck.gradle-json-report-path\";\n\n    private LicenseCheckPropertyKeys() {}\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinition.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport org.sonar.api.rule.Severity;\nimport org.sonar.api.server.rule.RulesDefinition;\n\n/**\n * Repository for the rules used in the plugin\n */\npublic final class LicenseCheckRulesDefinition implements RulesDefinition {\n\n    public static final String LANG_JAVA = \"java\";\n    public static final String LANG_JS = \"js\";\n    public static final String LANG_TS = \"ts\";\n    public static final String LANG_GROOVY = \"grvy\";\n    public static final String LANG_KOTLIN = \"kotlin\";\n    public static final String LANG_SCALA = \"scala\";\n\n    public static final String RULE_REPO_KEY = \"licensecheck\";\n    public static final String RULE_REPO_KEY_JS = \"licensecheck-js\";\n    public static final String RULE_REPO_KEY_TS = \"licensecheck-ts\";\n    public static final String RULE_REPO_KEY_GROOVY = \"licensecheck-groovy\";\n    public static final String RULE_REPO_KEY_KOTLIN = \"licensecheck-kotlin\";\n    public static final String RULE_REPO_KEY_SCALA = \"licensecheck-scala\";\n\n    public static final String RULE_UNLISTED_KEY = \"licensecheck.unlisted\";\n    public static final String RULE_NOT_ALLOWED_LICENSE_KEY = \"licensecheck.notallowedlicense\";\n\n    @Override\n    public void define(Context context) {\n        NewRepository[] repos = new NewRepository[] {\n            context.createRepository(RULE_REPO_KEY, LANG_JAVA),\n            context.createRepository(RULE_REPO_KEY_JS, LANG_JS),\n            context.createRepository(RULE_REPO_KEY_TS, LANG_TS),\n            context.createRepository(RULE_REPO_KEY_GROOVY, LANG_GROOVY),\n            context.createRepository(RULE_REPO_KEY_KOTLIN, LANG_KOTLIN),\n            context.createRepository(RULE_REPO_KEY_SCALA, LANG_SCALA),\n        };\n\n        for (NewRepository repo : repos) {\n            repo.setName(\"License Check\");\n\n            repo\n                .createRule(RULE_UNLISTED_KEY)\n                .setName(\"Dependency has unknown license [license-check]\")\n                .setHtmlDescription(\"The dependencies license could not be determined!\")\n                .setSeverity(Severity.BLOCKER);\n\n            repo\n                .createRule(RULE_NOT_ALLOWED_LICENSE_KEY)\n                .setName(\"License is not allowed [license-check]\")\n                .setHtmlDescription(\n                    \"Violation because the license of the dependency is not allowed.\"\n                )\n                .setSeverity(Severity.BLOCKER);\n\n            repo.done();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensor.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY;\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_GROOVY;\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_JS;\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_KOTLIN;\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_TS;\nimport static java.util.function.Predicate.not;\n\nimport at.porscheinformatik.sonarqube.licensecheck.gradle.GradleDependencyScanner;\nimport at.porscheinformatik.sonarqube.licensecheck.license.License;\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;\nimport at.porscheinformatik.sonarqube.licensecheck.maven.MavenDependencyScanner;\nimport at.porscheinformatik.sonarqube.licensecheck.npm.PackageJsonDependencyScanner;\nimport java.util.Set;\nimport java.util.TreeSet;\nimport java.util.concurrent.ConcurrentHashMap;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.sonar.api.batch.sensor.Sensor;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.scanner.fs.InputProject;\n\npublic class LicenseCheckSensor implements Sensor {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(LicenseCheckSensor.class);\n    private static final Set<License> AGGREGATED_LICENSES = ConcurrentHashMap.newKeySet();\n    private static final Set<Dependency> AGGREGATED_DEPENDENCIES = ConcurrentHashMap.newKeySet();\n    private final Configuration configuration;\n    private final ValidateLicenses validateLicenses;\n    private final Scanner[] scanners;\n\n    public LicenseCheckSensor(\n        Configuration configuration,\n        ValidateLicenses validateLicenses,\n        LicenseMappingService licenseMappingService\n    ) {\n        this.configuration = configuration;\n        this.validateLicenses = validateLicenses;\n        this.scanners = new Scanner[] {\n            new PackageJsonDependencyScanner(\n                licenseMappingService,\n                configuration\n                    .getBoolean(LicenseCheckPropertyKeys.NPM_RESOLVE_TRANSITIVE_DEPS)\n                    .orElse(false)\n            ),\n            new MavenDependencyScanner(licenseMappingService),\n            new GradleDependencyScanner(licenseMappingService),\n        };\n    }\n\n    private static void saveDependencies(\n        SensorContext sensorContext,\n        Set<Dependency> dependencies\n    ) {\n        LOGGER.debug(\n            \"Saving dependencies for module {}: {}\",\n            sensorContext.project(),\n            dependencies\n        );\n\n        if (!dependencies.isEmpty()) {\n            sensorContext\n                .<String>newMeasure()\n                .forMetric(LicenseCheckMetrics.DEPENDENCY)\n                .withValue(Dependency.createString(dependencies))\n                .on(sensorContext.project())\n                .save();\n        }\n    }\n\n    private static void saveLicenses(SensorContext sensorContext, Set<License> licenses) {\n        LOGGER.debug(\"Saving licenses for project {}: {}\", sensorContext.project(), licenses);\n\n        if (!licenses.isEmpty()) {\n            sensorContext\n                .<String>newMeasure()\n                .forMetric(LicenseCheckMetrics.LICENSE)\n                .withValue(License.createJsonString(licenses))\n                .on(sensorContext.project())\n                .save();\n        }\n    }\n\n    private static void saveMeasures(\n        SensorContext sensorContext,\n        Set<License> licenses,\n        Set<Dependency> dependencies\n    ) {\n        sensorContext\n            .<Integer>newMeasure()\n            .forMetric(LicenseCheckMetrics.NO_LICENSES)\n            .withValue(licenses.size())\n            .on(sensorContext.project())\n            .save();\n\n        sensorContext\n            .<Integer>newMeasure()\n            .forMetric(LicenseCheckMetrics.NO_LICENSES_FORBIDDEN)\n            .withValue((int) licenses.stream().filter(not(License::getAllowed)).count())\n            .on(sensorContext.project())\n            .save();\n\n        sensorContext\n            .<Integer>newMeasure()\n            .forMetric(LicenseCheckMetrics.NO_DEPENDENCIES)\n            .withValue(dependencies.size())\n            .on(sensorContext.project())\n            .save();\n\n        sensorContext\n            .<Integer>newMeasure()\n            .forMetric(LicenseCheckMetrics.NO_DEPENDENCIES_WITH_FORBIDDEN_LICENSE)\n            .withValue(\n                (int) dependencies\n                    .stream()\n                    .filter(d -> Dependency.Status.Forbidden.equals(d.getStatus()))\n                    .count()\n            )\n            .on(sensorContext.project())\n            .save();\n\n        sensorContext\n            .<Integer>newMeasure()\n            .forMetric(LicenseCheckMetrics.NO_DEPENDENCIES_WITH_UNKNOWN_LICENSE)\n            .withValue(\n                (int) dependencies\n                    .stream()\n                    .filter(d -> Dependency.Status.Unknown.equals(d.getStatus()))\n                    .count()\n            )\n            .on(sensorContext.project())\n            .save();\n    }\n\n    @Override\n    public void describe(SensorDescriptor descriptor) {\n        descriptor\n            .name(\"License Check\")\n            .createIssuesForRuleRepositories(\n                RULE_REPO_KEY,\n                RULE_REPO_KEY_JS,\n                RULE_REPO_KEY_TS,\n                RULE_REPO_KEY_GROOVY,\n                RULE_REPO_KEY_KOTLIN\n            );\n    }\n\n    @Override\n    public void execute(SensorContext context) {\n        if (!configuration.getBoolean(LicenseCheckPropertyKeys.ACTIVATION_KEY).orElse(true)) {\n            LOGGER.info(\"Scanner is set to inactive. No scan possible.\");\n            return;\n        }\n\n        Set<Dependency> dependencies = new TreeSet<>();\n\n        for (Scanner scanner : scanners) {\n            dependencies.addAll(scanner.scan(context));\n        }\n        InputProject project = context.project();\n        Set<Dependency> validatedDependencies = validateLicenses.validateLicenses(\n            dependencies,\n            context\n        );\n\n        Set<License> usedLicenses = validateLicenses.getUsedLicenses(\n            validatedDependencies,\n            project\n        );\n\n        AGGREGATED_LICENSES.addAll(usedLicenses);\n        AGGREGATED_DEPENDENCIES.addAll(validatedDependencies);\n\n        // root module?\n        if (context.project().key().equals(context.module().key())) {\n            saveDependencies(context, AGGREGATED_DEPENDENCIES);\n            saveLicenses(context, AGGREGATED_LICENSES);\n            saveMeasures(context, AGGREGATED_LICENSES, AGGREGATED_DEPENDENCIES);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/Scanner.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport java.util.Set;\nimport org.sonar.api.batch.sensor.SensorContext;\n\npublic interface Scanner {\n    Set<Dependency> scan(SensorContext context);\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/ValidateLicenses.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMapping;\nimport at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMappingService;\nimport at.porscheinformatik.sonarqube.licensecheck.license.License;\nimport at.porscheinformatik.sonarqube.licensecheck.license.LicenseService;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.TreeSet;\nimport java.util.stream.Collectors;\nimport org.codehaus.plexus.util.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.issue.NewIssue;\nimport org.sonar.api.batch.sensor.issue.NewIssueLocation;\nimport org.sonar.api.rule.RuleKey;\nimport org.sonar.api.scanner.ScannerSide;\nimport org.sonar.api.scanner.fs.InputProject;\n\n@ScannerSide\npublic class ValidateLicenses {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(ValidateLicenses.class);\n\n    private final LicenseService licenseService;\n    private final DependencyMappingService dependencyMappingService;\n\n    public ValidateLicenses(\n        LicenseService licenseService,\n        DependencyMappingService dependencyMappingService\n    ) {\n        super();\n        this.licenseService = licenseService;\n        this.dependencyMappingService = dependencyMappingService;\n    }\n\n    public Set<Dependency> validateLicenses(Set<Dependency> dependencies, SensorContext context) {\n        List<License> licenses = licenseService.getLicenses(context.project());\n        List<DependencyMapping> dependencyMappings =\n            dependencyMappingService.getDependencyMappings();\n\n        for (Dependency dependency : dependencies) {\n            License license = findLicense(licenses, dependency);\n            if (license == null) {\n                mapDependencyToLicense(dependencyMappings, dependency);\n            }\n\n            overrideLicenseForDependency(dependencyMappings, dependency);\n\n            if (!isLicensesValid(context, licenses, dependency)) {\n                dependency.setStatus(Dependency.Status.Unknown);\n            } else {\n                dependency.setStatus(Dependency.Status.Allowed);\n            }\n        }\n        return dependencies;\n    }\n\n    public Set<License> getUsedLicenses(Set<Dependency> dependencies, InputProject project) {\n        List<License> licenses = licenseService.getLicenses(project);\n        Set<License> usedLicenseList = new TreeSet<>();\n\n        for (Dependency dependency : dependencies) {\n            for (License license : licenses) {\n                if (license.getIdentifier().equals(dependency.getLicense())) {\n                    usedLicenseList.add(license);\n                }\n            }\n        }\n        return usedLicenseList;\n    }\n\n    private License findLicense(List<License> licenses, Dependency dependency) {\n        for (License license : licenses) {\n            if (license.getIdentifier().equals(dependency.getLicense())) {\n                return license;\n            }\n        }\n        return null;\n    }\n\n    private static void mapDependencyToLicense(\n        List<DependencyMapping> dependencyMappings,\n        Dependency dependency\n    ) {\n        dependencyMappings.forEach(dependencyMapping -> {\n            if (StringUtils.isBlank(dependency.getLicense())) {\n                String matchString = dependencyMapping.getKey();\n                if (dependency.getName().matches(matchString)) {\n                    dependency.setLicense(dependencyMapping.getLicense());\n                }\n            }\n        });\n    }\n\n    private static void overrideLicenseForDependency(\n        List<DependencyMapping> dependencyMappings,\n        Dependency dependency\n    ) {\n        dependencyMappings\n            .stream()\n            .filter(DependencyMapping::getOverwrite)\n            .forEach(dependencyMapping -> {\n                String matchString = dependencyMapping.getKey();\n                if (dependency.getName().matches(matchString)) {\n                    dependency.setLicense(dependencyMapping.getLicense());\n                }\n            });\n    }\n\n    private static boolean isLicensesValid(\n        SensorContext context,\n        List<License> licenses,\n        Dependency dependency\n    ) {\n        if (StringUtils.isBlank(dependency.getLicense())) {\n            licenseNotFoundIssue(context, dependency);\n            return false;\n        }\n\n        if (checkSpdxLicense(dependency.getLicense(), licenses)) {\n            return true;\n        }\n\n        List<License> licensesContainingDependency = licenses\n            .stream()\n            .filter(l -> dependency.getLicense().contains(l.getIdentifier()))\n            .collect(Collectors.toList());\n\n        String[] andLicenses = dependency\n            .getLicense()\n            .replace(\"(\", \"\")\n            .replace(\")\", \"\")\n            .split(\" AND \");\n\n        if (licensesContainingDependency.size() != andLicenses.length) {\n            licenseNotFoundIssue(context, dependency);\n        } else {\n            StringBuilder notAllowedLicense = new StringBuilder();\n\n            for (License element : licensesContainingDependency) {\n                if (!element.getAllowed()) {\n                    notAllowedLicense.append(element.getName()).append(\" \");\n                }\n            }\n            licenseNotAllowedIssue(context, dependency, notAllowedLicense.toString());\n        }\n\n        return false;\n    }\n\n    private static boolean checkSpdxLicense(String spdxLicenseString, List<License> licenses) {\n        if (spdxLicenseString.contains(\" OR \")) {\n            return checkSpdxLicenseWithOr(spdxLicenseString, licenses);\n        } else if (spdxLicenseString.contains(\" AND \")) {\n            return checkSpdxLicenseWithAnd(spdxLicenseString, licenses);\n        }\n\n        return licenses\n            .stream()\n            .filter(l -> l.getIdentifier().equals(spdxLicenseString))\n            .anyMatch(License::getAllowed);\n    }\n\n    private static boolean checkSpdxLicenseWithOr(\n        String spdxLicenseString,\n        List<License> licenses\n    ) {\n        String[] orLicenses = spdxLicenseString.replace(\"(\", \"\").replace(\")\", \"\").split(\" OR \");\n        return licenses\n            .stream()\n            .filter(l -> ValidateLicenses.contains(orLicenses, l.getIdentifier()))\n            .anyMatch(License::getAllowed);\n    }\n\n    private static boolean checkSpdxLicenseWithAnd(\n        String spdxLicenseString,\n        List<License> licenses\n    ) {\n        String[] andLicenses = spdxLicenseString.replace(\"(\", \"\").replace(\")\", \"\").split(\" AND \");\n        long count = andLicenses.length;\n        List<License> foundLicenses = licenses\n            .stream()\n            .filter(l -> ValidateLicenses.contains(andLicenses, l.getIdentifier()))\n            .collect(Collectors.toList());\n        long allowedLicenseCount = foundLicenses.stream().filter(License::getAllowed).count();\n        return count == allowedLicenseCount;\n    }\n\n    private static void licenseNotAllowedIssue(\n        SensorContext context,\n        Dependency dependency,\n        String notAllowedLicense\n    ) {\n        LOGGER.info(\n            \"Dependency \" +\n                dependency.getName() +\n                \" uses a not allowed license \" +\n                notAllowedLicense\n        );\n\n        dependency.setStatus(Dependency.Status.Forbidden);\n\n        createIssue(\n            context,\n            dependency,\n            LicenseCheckRulesDefinition.RULE_NOT_ALLOWED_LICENSE_KEY,\n            \"Dependency \" +\n                dependency.getName() +\n                \" uses a not allowed license \" +\n                dependency.getLicense()\n        );\n    }\n\n    private static void licenseNotFoundIssue(SensorContext context, Dependency dependency) {\n        String message = String.format(\n            \"No License found for Dependency %s (license from source: %s)\",\n            dependency.getName(),\n            dependency.getLicense()\n        );\n\n        LOGGER.info(message);\n\n        createIssue(context, dependency, LicenseCheckRulesDefinition.RULE_UNLISTED_KEY, message);\n    }\n\n    private static void createIssue(\n        SensorContext context,\n        Dependency dependency,\n        String rule,\n        String message\n    ) {\n        NewIssue issue = context.newIssue().forRule(RuleKey.of(getRepoKey(dependency), rule));\n\n        NewIssueLocation location = issue.newLocation().on(dependency.getInputComponent());\n        if (dependency.getTextRange() != null) {\n            location = location.at(dependency.getTextRange());\n        }\n\n        issue.at(location.message(message)).save();\n    }\n\n    private static String getRepoKey(Dependency dependency) {\n        switch (dependency.getLang()) {\n            case LicenseCheckRulesDefinition.LANG_JS:\n                return LicenseCheckRulesDefinition.RULE_REPO_KEY_JS;\n            case LicenseCheckRulesDefinition.LANG_TS:\n                return LicenseCheckRulesDefinition.RULE_REPO_KEY_TS;\n            case LicenseCheckRulesDefinition.LANG_GROOVY:\n                return LicenseCheckRulesDefinition.RULE_REPO_KEY_GROOVY;\n            case LicenseCheckRulesDefinition.LANG_SCALA:\n                return LicenseCheckRulesDefinition.RULE_REPO_KEY_SCALA;\n            case LicenseCheckRulesDefinition.LANG_KOTLIN:\n                return LicenseCheckRulesDefinition.RULE_REPO_KEY_KOTLIN;\n            case LicenseCheckRulesDefinition.LANG_JAVA:\n            default:\n                return LicenseCheckRulesDefinition.RULE_REPO_KEY;\n        }\n    }\n\n    private static boolean contains(String[] items, String valueToFind) {\n        for (String item : items) {\n            if (item != null && item.equals(valueToFind)) {\n                return true;\n            }\n        }\n        return false;\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/dependencymapping/DependencyMapping.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.dependencymapping;\n\nimport java.util.Objects;\n\npublic class DependencyMapping implements Comparable<DependencyMapping> {\n\n    public static final String FIELD_KEY = \"key\";\n    public static final String FIELD_LICENSE = \"license\";\n    public static final String FIELD_OVERWRITE = \"overwrite\";\n\n    private String key;\n    private String license;\n    private Boolean overwrite;\n\n    public DependencyMapping(String key, String license, Boolean overwrite) {\n        super();\n        this.key = key;\n        this.license = license;\n        this.overwrite = overwrite;\n    }\n\n    public String getKey() {\n        return key;\n    }\n\n    public void setKey(String key) {\n        this.key = key;\n    }\n\n    public String getLicense() {\n        return license;\n    }\n\n    public void setLicense(String license) {\n        this.license = license;\n    }\n\n    public Boolean getOverwrite() {\n        return overwrite;\n    }\n\n    public void setOverwrite(Boolean overwrite) {\n        this.overwrite = overwrite;\n    }\n\n    @Override\n    public String toString() {\n        return \"{key:\" + key + \", license:\" + license + \"}\";\n    }\n\n    @Override\n    public int compareTo(DependencyMapping o) {\n        if (o == null) {\n            return 1;\n        }\n\n        if (this.license.compareTo(o.license) == 0) {\n            return this.key.compareTo(o.key);\n        } else {\n            return this.license.compareTo(o.license);\n        }\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        DependencyMapping that = (DependencyMapping) o;\n        return Objects.equals(key, that.key) && Objects.equals(license, that.license);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(key, license);\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/dependencymapping/DependencyMappingService.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.dependencymapping;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPropertyKeys.DEPENDENCY_MAPPING;\nimport static at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMapping.FIELD_KEY;\nimport static at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMapping.FIELD_LICENSE;\nimport static at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMapping.FIELD_OVERWRITE;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.scanner.ScannerSide;\nimport org.sonar.api.server.ServerSide;\n\n@ServerSide\n@ScannerSide\npublic class DependencyMappingService {\n\n    private final Configuration configuration;\n\n    public DependencyMappingService(Configuration configuration) {\n        super();\n        this.configuration = configuration;\n    }\n\n    public List<DependencyMapping> getDependencyMappings() {\n        return Arrays.stream(configuration.getStringArray(DEPENDENCY_MAPPING))\n            .map(idx -> {\n                String idxProp = \".\" + idx + \".\";\n                String key = configuration\n                    .get(DEPENDENCY_MAPPING + idxProp + FIELD_KEY)\n                    .orElse(null);\n                String license = configuration\n                    .get(DEPENDENCY_MAPPING + idxProp + FIELD_LICENSE)\n                    .orElse(null);\n                Boolean overwrite = configuration\n                    .getBoolean(DEPENDENCY_MAPPING + idxProp + FIELD_OVERWRITE)\n                    .orElse(Boolean.TRUE);\n                return new DependencyMapping(key, license, overwrite);\n            })\n            .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/gradle/GradleDependencyScanner.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.gradle;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPropertyKeys.GRADLE_JSON_REPORT_PATH;\n\nimport at.porscheinformatik.sonarqube.licensecheck.Dependency;\nimport at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition;\nimport at.porscheinformatik.sonarqube.licensecheck.Scanner;\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport javax.json.Json;\nimport javax.json.JsonArray;\nimport javax.json.JsonObject;\nimport javax.json.JsonReader;\nimport org.codehaus.plexus.util.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.sonar.api.batch.sensor.SensorContext;\n\npublic class GradleDependencyScanner implements Scanner {\n\n    public static final String JSON_REPORT_PATH_DEFAULT =\n        \"build\" +\n        File.separator +\n        \"reports\" +\n        File.separator +\n        \"dependency-license\" +\n        File.separator +\n        \"license-details.json\";\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(GradleDependencyScanner.class);\n\n    private final LicenseMappingService licenseMappingService;\n\n    public GradleDependencyScanner(LicenseMappingService licenseMappingService) {\n        this.licenseMappingService = licenseMappingService;\n    }\n\n    @Override\n    public Set<Dependency> scan(SensorContext context) {\n        Map<Pattern, String> defaultLicenseMap = licenseMappingService.getLicenseMap();\n\n        String pathDef = context\n            .config()\n            .get(GRADLE_JSON_REPORT_PATH)\n            .orElse(JSON_REPORT_PATH_DEFAULT);\n        LOGGER.debug(\"Searching for license file at {}\", pathDef);\n        File licenseDetailsJsonFile = context\n            .fileSystem()\n            .baseDir()\n            .toPath()\n            .resolve(pathDef)\n            .toFile()\n            .getAbsoluteFile();\n        if (!licenseDetailsJsonFile.exists()) {\n            LOGGER.info(\n                \"No license-details.json file found in {} - skipping Gradle dependency scan\",\n                licenseDetailsJsonFile.getPath()\n            );\n            return Collections.emptySet();\n        }\n\n        return readLicenseDetailsJson(licenseDetailsJsonFile)\n            .stream()\n            .map(d -> matchLicense(defaultLicenseMap, d))\n            .peek(d -> d.setInputComponent(context.module()))\n            .collect(Collectors.toSet());\n    }\n\n    private Set<Dependency> readLicenseDetailsJson(File licenseDetailsJsonFile) {\n        Set<Dependency> dependencySet = new HashSet<>();\n        try (\n            InputStream fis = new FileInputStream(licenseDetailsJsonFile);\n            JsonReader jsonReader = Json.createReader(fis);\n        ) {\n            JsonArray arr = jsonReader.readObject().getJsonArray(\"dependencies\");\n            prepareDependencySet(dependencySet, arr);\n            return dependencySet;\n        } catch (IOException e) {\n            LOGGER.error(\n                \"Problems reading Gradle license file {}: {}\",\n                licenseDetailsJsonFile.getPath(),\n                e.getMessage()\n            );\n        }\n        return dependencySet;\n    }\n\n    private void prepareDependencySet(Set<Dependency> dependencySet, JsonArray arr) {\n        for (javax.json.JsonValue entry : arr) {\n            JsonObject jsonDepObj = entry.asJsonObject();\n            JsonArray arrModuleUrls = jsonDepObj.getJsonArray(\"moduleUrls\");\n            String moduleLicense = getModuleLicenseFromJsonObject(jsonDepObj);\n            String moduleLicenseUrl = null;\n            if (arrModuleUrls != null) {\n                moduleLicenseUrl = arrModuleUrls.getString(0, null);\n            }\n            Dependency dep = new Dependency(\n                jsonDepObj.getString(\"moduleName\", null),\n                jsonDepObj.getString(\"moduleVersion\", null),\n                moduleLicense,\n                LicenseCheckRulesDefinition.LANG_JAVA\n            );\n            dep.setPomPath(moduleLicenseUrl);\n            dependencySet.add(dep);\n        }\n    }\n\n    private String getModuleLicenseFromJsonObject(JsonObject jsonDepObj) {\n        String moduleLicense = null;\n        JsonArray arrModuleLicenses = jsonDepObj.getJsonArray(\"moduleLicenses\");\n        if (arrModuleLicenses != null && !arrModuleLicenses.isEmpty()) {\n            moduleLicense = getModuleLicense(arrModuleLicenses);\n        }\n        return moduleLicense;\n    }\n\n    private String getModuleLicense(JsonArray arrModuleLicenses) {\n        String moduleLicense = null;\n        JsonObject firstJsonObj = arrModuleLicenses.getJsonObject(0);\n        if (firstJsonObj != null) {\n            moduleLicense = firstJsonObj.getString(\"moduleLicense\", null);\n            if (moduleLicense == null) {\n                moduleLicense = firstJsonObj.getString(\"moduleLicenseUrl\", null);\n            }\n        }\n        return moduleLicense;\n    }\n\n    private Dependency matchLicense(Map<Pattern, String> licenseMap, Dependency dependency) {\n        if (StringUtils.isBlank(dependency.getLicense())) {\n            LOGGER.info(\"Dependency '{}' has no license set.\", dependency.getName());\n            return dependency;\n        }\n\n        for (Map.Entry<Pattern, String> entry : licenseMap.entrySet()) {\n            if (entry.getKey().matcher(dependency.getLicense()).matches()) {\n                dependency.setLicense(entry.getValue());\n                break;\n            }\n        }\n        return dependency;\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/license/License.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.license;\n\nimport java.io.StringReader;\nimport java.io.StringWriter;\nimport java.util.*;\nimport javax.json.*;\nimport javax.json.stream.JsonGenerator;\nimport org.codehaus.plexus.util.StringUtils;\n\npublic class License implements Comparable<License> {\n\n    public static final String FIELD_NAME = \"name\";\n    public static final String FIELD_ID = \"id\";\n    public static final String FIELD_ALLOWED = \"allowed\";\n    public static final String STATUS = \"status\";\n\n    private String name;\n    private String identifier;\n    private Boolean allowed;\n\n    public License(String name, String identifier, Boolean allowed) {\n        super();\n        this.name = name;\n        this.identifier = identifier;\n        this.allowed = Objects.requireNonNull(allowed);\n    }\n\n    public License(String name, String identifier, String allowed) {\n        this(name, identifier, Boolean.parseBoolean(allowed));\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getIdentifier() {\n        return identifier;\n    }\n\n    public void setIdentifier(String identifier) {\n        this.identifier = identifier;\n    }\n\n    public Boolean getAllowed() {\n        return allowed;\n    }\n\n    public void setAllowed(Boolean allowed) {\n        this.allowed = allowed;\n    }\n\n    @Override\n    public String toString() {\n        return \"{name:\" + name + \", identifier:\" + identifier + \", status:\" + allowed + \"}\";\n    }\n\n    public static List<License> fromString(String serializedLicensesString) {\n        if (serializedLicensesString == null) {\n            return new ArrayList<>();\n        }\n\n        if (serializedLicensesString.startsWith(\"[\")) {\n            List<License> licenses = new ArrayList<>();\n\n            try (\n                JsonReader jsonReader = Json.createReader(\n                    new StringReader(serializedLicensesString)\n                );\n            ) {\n                JsonArray licensesJson = jsonReader.readArray();\n                for (JsonObject licenseJson : licensesJson.getValuesAs(JsonObject.class)) {\n                    licenses.add(\n                        new License(\n                            licenseJson.getString(\"name\"),\n                            licenseJson.getString(\"identifier\"),\n                            licenseJson.getString(STATUS)\n                        )\n                    );\n                }\n            }\n            return licenses;\n        } else if (serializedLicensesString.startsWith(\"{\")) {\n            return readLegacyJson(serializedLicensesString);\n        } else {\n            return readLegacySeparated(serializedLicensesString);\n        }\n    }\n\n    /**\n     * @param serializedLicensesString setting string\n     * @return a list with licences\n     * @deprecated remove with later release\n     */\n    @Deprecated\n    private static List<License> readLegacySeparated(String serializedLicensesString) {\n        List<License> licenses = new ArrayList<>();\n\n        if (StringUtils.isNotEmpty(serializedLicensesString)) {\n            String[] parts = serializedLicensesString.split(\";\");\n\n            for (String licenseString : parts) {\n                String[] subParts = licenseString.split(\"~\");\n                String name = subParts.length > 0 ? subParts[0] : null;\n                String identifier = subParts.length > 1 ? subParts[1] : null;\n                String status = subParts.length > 2 ? subParts[2] : null;\n                licenses.add(new License(name, identifier, status));\n            }\n        }\n\n        return licenses;\n    }\n\n    /**\n     * @param serializedLicensesString setting string\n     * @return a list with licences\n     * @deprecated remove with later release\n     */\n    @Deprecated\n    private static List<License> readLegacyJson(String serializedLicensesString) {\n        List<License> licenses = new ArrayList<>();\n\n        try (\n            JsonReader jsonReader = Json.createReader(new StringReader(serializedLicensesString));\n        ) {\n            JsonObject licensesJson = jsonReader.readObject();\n            for (Map.Entry<String, JsonValue> licenseJson : licensesJson.entrySet()) {\n                JsonObject value = (JsonObject) licenseJson.getValue();\n                licenses.add(\n                    new License(\n                        value.getString(\"name\"),\n                        licenseJson.getKey(),\n                        value.getString(STATUS)\n                    )\n                );\n            }\n        }\n\n        return licenses;\n    }\n\n    public static String createJsonString(Collection<License> licenses) {\n        TreeSet<License> licenseSet = new TreeSet<>(licenses);\n\n        StringWriter jsonString = new StringWriter();\n        JsonGenerator generator = Json.createGenerator(jsonString);\n        generator.writeStartArray();\n        for (License license : licenseSet) {\n            generator.writeStartObject();\n            generator.write(\"name\", license.getName());\n            generator.write(\"identifier\", license.getIdentifier());\n            generator.write(STATUS, license.getAllowed().toString());\n            generator.writeEnd();\n        }\n        generator.writeEnd();\n        generator.close();\n\n        return jsonString.toString();\n    }\n\n    @Override\n    public int compareTo(License o) {\n        if (o == null) {\n            return 1;\n        }\n\n        if (this.identifier.compareTo(o.identifier) == 0) {\n            if (this.name.compareTo(o.name) == 0) {\n                return this.allowed.compareTo(o.allowed);\n            } else {\n                return this.name.compareTo(o.name);\n            }\n        } else {\n            return this.identifier.compareTo(o.identifier);\n        }\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        License license = (License) o;\n        return (\n            Objects.equals(name, license.name) &&\n            Objects.equals(identifier, license.identifier) &&\n            Objects.equals(allowed, license.allowed)\n        );\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(name, identifier, allowed);\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/license/LicenseService.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.license;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPropertyKeys.LICENSE_SET;\nimport static at.porscheinformatik.sonarqube.licensecheck.license.License.FIELD_ALLOWED;\nimport static at.porscheinformatik.sonarqube.licensecheck.license.License.FIELD_ID;\nimport static at.porscheinformatik.sonarqube.licensecheck.license.License.FIELD_NAME;\n\nimport at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicense;\nimport at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicenseService;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.scanner.ScannerSide;\nimport org.sonar.api.scanner.fs.InputProject;\nimport org.sonar.api.server.ServerSide;\n\n@ServerSide\n@ScannerSide\npublic class LicenseService {\n\n    private final Configuration configuration;\n    private final ProjectLicenseService projectLicenseService;\n\n    public LicenseService(\n        Configuration configuration,\n        ProjectLicenseService projectLicenseService\n    ) {\n        super();\n        this.configuration = configuration;\n        this.projectLicenseService = projectLicenseService;\n    }\n\n    public List<License> getLicenses(InputProject module) {\n        List<License> globalLicenses = getLicenses();\n\n        if (module == null) {\n            return globalLicenses;\n        }\n\n        Collection<ProjectLicense> projectLicenses = projectLicenseService.getProjectLicenseList(\n            module.key()\n        );\n\n        for (License license : globalLicenses) {\n            for (ProjectLicense projectLicense : projectLicenses) {\n                if (license.getIdentifier().equals(projectLicense.getLicense())) {\n                    license.setAllowed(projectLicense.getAllowed()); // override the status of the globalLicenses\n                }\n            }\n        }\n\n        return globalLicenses;\n    }\n\n    public List<License> getLicenses() {\n        return Arrays.stream(configuration.getStringArray(LICENSE_SET))\n            .map(idx -> {\n                String idxProp = \".\" + idx + \".\";\n                String name = configuration.get(LICENSE_SET + idxProp + FIELD_NAME).orElse(null);\n                String identifier = configuration\n                    .get(LICENSE_SET + idxProp + FIELD_ID)\n                    .orElse(null);\n                Boolean allowed = configuration\n                    .getBoolean(LICENSE_SET + idxProp + FIELD_ALLOWED)\n                    .orElse(Boolean.FALSE);\n                return new License(name, identifier, allowed.toString());\n            })\n            .collect(Collectors.toList());\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/licensemapping/LicenseMapping.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.licensemapping;\n\nimport java.io.StringReader;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.regex.Pattern;\nimport javax.json.Json;\nimport javax.json.JsonArray;\nimport javax.json.JsonObject;\nimport javax.json.JsonReader;\nimport org.codehaus.plexus.util.StringUtils;\n\npublic class LicenseMapping implements Comparable<LicenseMapping> {\n\n    public static final String FIELD_LICENSE = \"license\";\n    public static final String FIELD_REGEX = \"regex\";\n\n    private final Pattern regex;\n    private final String license;\n\n    public LicenseMapping(String regex, String license) {\n        super();\n        this.regex = Pattern.compile(regex);\n        this.license = license;\n    }\n\n    public Pattern getRegex() {\n        return regex;\n    }\n\n    public String getLicense() {\n        return license;\n    }\n\n    @Override\n    public int compareTo(LicenseMapping o) {\n        if (o == null) {\n            return 1;\n        } else if (this.license.compareTo(o.license) == 0) {\n            return this.regex.toString().compareTo(o.regex.toString());\n        } else {\n            return this.license.compareTo(o.license);\n        }\n    }\n\n    public static List<LicenseMapping> fromString(String licenseMappingString) {\n        List<LicenseMapping> licensMappings = new ArrayList<>();\n\n        if (licenseMappingString != null && licenseMappingString.startsWith(\"[\")) {\n            try (\n                JsonReader jsonReader = Json.createReader(new StringReader(licenseMappingString));\n            ) {\n                JsonArray licensesJson = jsonReader.readArray();\n                for (int i = 0; i < licensesJson.size(); i++) {\n                    JsonObject licenseJson = licensesJson.getJsonObject(i);\n                    String regex;\n                    try {\n                        regex = licenseJson.getString(FIELD_REGEX);\n                    } catch (NullPointerException e) {\n                        regex = licenseJson.getString(\"licenseNameRegEx\", null);\n                    }\n\n                    if (regex != null) {\n                        licensMappings.add(\n                            new LicenseMapping(regex, licenseJson.getString(FIELD_LICENSE))\n                        );\n                    }\n                }\n            }\n        } else if (StringUtils.isNotEmpty(licenseMappingString)) {\n            // deprecated - remove with later release\n            String[] licenseMappingEntries = licenseMappingString.split(\";\");\n            for (String licenseMappingEntry : licenseMappingEntries) {\n                String[] licenseMappingEntryParts = licenseMappingEntry.split(\"~\");\n                licensMappings.add(\n                    new LicenseMapping(licenseMappingEntryParts[0], licenseMappingEntryParts[1])\n                );\n            }\n        }\n\n        return licensMappings;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        LicenseMapping that = (LicenseMapping) o;\n        return (\n            Objects.equals(regex.pattern(), that.regex.pattern()) &&\n            Objects.equals(license, that.license)\n        );\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(regex.pattern(), license);\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/licensemapping/LicenseMappingService.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.licensemapping;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPropertyKeys.LICENSE_MAPPING;\nimport static at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMapping.FIELD_LICENSE;\nimport static at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMapping.FIELD_REGEX;\n\nimport java.util.Arrays;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Pattern;\nimport java.util.stream.Collectors;\nimport org.codehaus.plexus.util.StringUtils;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.scanner.ScannerSide;\nimport org.sonar.api.server.ServerSide;\n\n@ServerSide\n@ScannerSide\npublic class LicenseMappingService {\n\n    private final Configuration configuration;\n\n    /** Holding license map on scanner side */\n    private static Map<Pattern, String> LICENSE_MAP;\n\n    public LicenseMappingService(Configuration configuration) {\n        super();\n        this.configuration = configuration;\n    }\n\n    public List<LicenseMapping> getLicenseMappingList() {\n        return Arrays.stream(configuration.getStringArray(LICENSE_MAPPING))\n            .map(idx -> {\n                String idxProp = \".\" + idx + \".\";\n                String licenseRegex = configuration\n                    .get(LICENSE_MAPPING + idxProp + FIELD_REGEX)\n                    .orElse(\"\");\n                String licenseId = configuration\n                    .get(LICENSE_MAPPING + idxProp + FIELD_LICENSE)\n                    .orElse(null);\n                return new LicenseMapping(licenseRegex, licenseId);\n            })\n            .collect(Collectors.toList());\n    }\n\n    public Map<Pattern, String> getLicenseMap() {\n        if (LICENSE_MAP != null) {\n            return LICENSE_MAP;\n        }\n\n        LICENSE_MAP = new HashMap<>();\n        for (LicenseMapping license : getLicenseMappingList()) {\n            LICENSE_MAP.put(license.getRegex(), license.getLicense());\n        }\n        return LICENSE_MAP;\n    }\n\n    public String mapLicense(String licenseName) {\n        if (StringUtils.isBlank(licenseName)) {\n            return licenseName;\n        }\n\n        Map<Pattern, String> licenseMap = getLicenseMap();\n        for (Map.Entry<Pattern, String> entry : licenseMap.entrySet()) {\n            if (entry.getKey().matcher(licenseName).matches()) {\n                return entry.getValue();\n            }\n        }\n        return licenseName;\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/maven/DirectoryFinder.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.maven;\n\nimport at.porscheinformatik.sonarqube.licensecheck.Dependency;\nimport java.io.File;\nimport org.apache.maven.shared.utils.cli.CommandLineUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nclass DirectoryFinder {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(DirectoryFinder.class);\n\n    private DirectoryFinder() {}\n\n    public static File getPomPath(Dependency findPathOfDependency, File mavenRepositoryDir) {\n        String[] ids = findPathOfDependency.getName().split(\":\");\n        String groupId = ids[0];\n        String artifactId = ids[1];\n\n        String tmp =\n            groupId.replace(\".\", \"/\") +\n            \"/\" +\n            artifactId +\n            \"/\" +\n            findPathOfDependency.getVersion() +\n            \"/\" +\n            artifactId +\n            \"-\" +\n            findPathOfDependency.getVersion() +\n            \".pom\";\n\n        return new File(mavenRepositoryDir, tmp);\n    }\n\n    public static File getMavenRepositoryDir(String userSettings, String globalSettings) {\n        if (System.getProperty(\"maven.repo.local\") != null) {\n            return new File(System.getProperty(\"maven.repo.local\"));\n        }\n        String mavenOpts = System.getenv(\"MAVEN_OPTS\");\n        if (mavenOpts != null) {\n            try {\n                String[] opts = CommandLineUtils.translateCommandline(mavenOpts);\n                for (String opt : opts) {\n                    if (opt.startsWith(\"-Dmaven.repo.local=\")) {\n                        String repoPath = opt.substring(\"-Dmaven.repo.local=\".length());\n                        return new File(repoPath);\n                    }\n                }\n            } catch (Exception e) {\n                LOGGER.warn(\"Could not parse MAVEN_OPTS: \" + mavenOpts, e);\n                // ignore\n            }\n        }\n\n        File mavenConfFile = new File(System.getProperty(\"user.home\"), \".m2/settings.xml\");\n        if (userSettings != null) {\n            mavenConfFile = new File(userSettings);\n        }\n        if (mavenConfFile.exists() && mavenConfFile.isFile()) {\n            File localRepositoryPath = SettingsXmlParser.parseXmlFile(\n                mavenConfFile\n            ).getLocalRepositoryPath();\n            if (localRepositoryPath != null) {\n                return localRepositoryPath;\n            }\n        }\n\n        mavenConfFile = new File(System.getenv(\"MAVEN_HOME\"), \"conf/settings.xml\");\n        if (globalSettings != null) {\n            mavenConfFile = new File(globalSettings);\n        }\n        if (mavenConfFile.exists() && mavenConfFile.isFile()) {\n            File localRepositoryPath = SettingsXmlParser.parseXmlFile(\n                mavenConfFile\n            ).getLocalRepositoryPath();\n            if (localRepositoryPath != null) {\n                return localRepositoryPath;\n            }\n        }\n\n        return new File(System.getProperty(\"user.home\"), \".m2/repository\");\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/maven/LicenseFinder.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.maven;\n\nimport at.porscheinformatik.sonarqube.licensecheck.Dependency;\nimport at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.util.Collections;\nimport java.util.List;\nimport org.apache.maven.model.License;\nimport org.apache.maven.model.Model;\nimport org.apache.maven.model.Parent;\nimport org.apache.maven.model.io.xpp3.MavenXpp3Reader;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nclass LicenseFinder {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(LicenseFinder.class);\n\n    private LicenseFinder() {}\n\n    public static List<License> getLicenses(\n        File filePath,\n        String userSettings,\n        String globalSettings\n    ) {\n        try {\n            Model model = new MavenXpp3Reader().read(new FileInputStream(filePath));\n\n            if (!model.getLicenses().isEmpty()) {\n                return model.getLicenses();\n            } else {\n                if (model.getParent() != null) {\n                    Parent parent = model.getParent();\n                    Dependency dependency = new Dependency(\n                        parent.getGroupId() + \":\" + parent.getArtifactId(),\n                        parent.getVersion(),\n                        null,\n                        LicenseCheckRulesDefinition.LANG_JAVA\n                    );\n                    return getLicenses(\n                        DirectoryFinder.getPomPath(\n                            dependency,\n                            DirectoryFinder.getMavenRepositoryDir(userSettings, globalSettings)\n                        ),\n                        userSettings,\n                        globalSettings\n                    );\n                } else {\n                    return Collections.emptyList();\n                }\n            }\n        } catch (Exception e) {\n            LOGGER.warn(\"Could not parse Maven POM \" + filePath, e);\n            return Collections.emptyList();\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/maven/MavenDependencyScanner.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.maven;\n\nimport at.porscheinformatik.sonarqube.licensecheck.Dependency;\nimport at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition;\nimport at.porscheinformatik.sonarqube.licensecheck.Scanner;\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Map.Entry;\nimport java.util.Objects;\nimport java.util.Properties;\nimport java.util.Set;\nimport java.util.function.Function;\nimport java.util.regex.Pattern;\nimport java.util.stream.Stream;\nimport org.apache.maven.model.License;\nimport org.apache.maven.shared.invoker.DefaultInvocationRequest;\nimport org.apache.maven.shared.invoker.DefaultInvoker;\nimport org.apache.maven.shared.invoker.InvocationRequest;\nimport org.apache.maven.shared.invoker.InvocationResult;\nimport org.apache.maven.shared.invoker.Invoker;\nimport org.apache.maven.shared.invoker.MavenInvocationException;\nimport org.codehaus.plexus.util.StringUtils;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.sensor.SensorContext;\n\npublic class MavenDependencyScanner implements Scanner {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(MavenDependencyScanner.class);\n    private static final String MAVEN_REPO_LOCAL = \"maven.repo.local\";\n\n    private final LicenseMappingService licenseMappingService;\n\n    public MavenDependencyScanner(LicenseMappingService licenseMappingService) {\n        this.licenseMappingService = licenseMappingService;\n    }\n\n    @Override\n    public Set<Dependency> scan(SensorContext context) {\n        MavenSettings settings = getSettingsFromCommandLineArgs();\n\n        FileSystem fs = context.fileSystem();\n        FilePredicate pomXmlPredicate = fs.predicates().matchesPathPattern(\"**/pom.xml\");\n\n        Set<Dependency> allDependencies = new HashSet<>();\n\n        for (InputFile pomXml : fs.inputFiles(pomXmlPredicate)) {\n            context.markForPublishing(pomXml);\n\n            LOGGER.info(\"Scanning for Maven dependencies (POM: {})\", pomXml.uri());\n            try (\n                Stream<Dependency> dependencies = readDependencyList(\n                    new File(pomXml.uri()),\n                    settings\n                );\n            ) {\n                dependencies\n                    .map(this.loadLicenseFromPom(licenseMappingService.getLicenseMap(), settings))\n                    .forEach(dependency -> {\n                        dependency.setInputComponent(pomXml);\n                        dependency.setTextRange(pomXml.newRange(1, 0, pomXml.lines(), 0));\n                        allDependencies.add(dependency);\n                    });\n            }\n        }\n\n        return allDependencies;\n    }\n\n    private static Stream<Dependency> readDependencyList(File pomXml, MavenSettings settings) {\n        Path tempFile = createTempFile();\n        if (tempFile == null) {\n            return Stream.empty();\n        }\n\n        InvocationRequest request = new DefaultInvocationRequest();\n        request.setRecursive(false);\n        request.setPomFile(pomXml);\n        request.setBaseDirectory(pomXml.getParentFile());\n        request.setGoals(Collections.singletonList(\"dependency:list\"));\n        if (settings.userSettings != null) {\n            request.setUserSettingsFile(new File(settings.userSettings));\n            LOGGER.info(\"Using user settings {}\", settings.userSettings);\n        }\n        if (settings.globalSettings != null) {\n            request.setGlobalSettingsFile(new File(settings.globalSettings));\n            LOGGER.info(\"Using global settings {}\", settings.globalSettings);\n        }\n        Properties properties = new Properties();\n        properties.setProperty(\"outputFile\", tempFile.toAbsolutePath().toString());\n        properties.setProperty(\"outputAbsoluteArtifactFilename\", \"true\");\n        properties.setProperty(\"includeScope\", \"runtime\"); // only runtime (scope compile + runtime)\n        if (System.getProperty(MAVEN_REPO_LOCAL) != null) {\n            properties.setProperty(MAVEN_REPO_LOCAL, System.getProperty(MAVEN_REPO_LOCAL));\n        }\n        if (System.getenv(\"MAVEN_OPTS\") != null) {\n            request.setMavenOpts(System.getenv(\"MAVEN_OPTS\"));\n        }\n        request.setBatchMode(true);\n        request.setProperties(properties);\n\n        return invokeMaven(request, tempFile);\n    }\n\n    private static Stream<Dependency> invokeMaven(InvocationRequest request, Path mavenOutputFile) {\n        try {\n            StringBuilder mavenExecutionErrors = new StringBuilder();\n            Invoker invoker = new DefaultInvoker();\n            if (System.getProperty(\"maven.home\") != null) {\n                invoker.setMavenHome(new File(System.getProperty(\"maven.home\")));\n            } else if (System.getenv(\"MAVEN_HOME\") != null) {\n                invoker.setMavenHome(new File(System.getenv(\"MAVEN_HOME\")));\n            } else {\n                String mvnPath = getMvnPath();\n                if (mvnPath != null) {\n                    invoker.setMavenHome(new File(mvnPath).getParentFile().getParentFile());\n                } else {\n                    LOGGER.warn(\"Could not find mvn in path\");\n                }\n            }\n            request.setOutputHandler(line -> {\n                if (line.startsWith(\"[ERROR] \")) {\n                    mavenExecutionErrors.append(line.substring(8)).append(System.lineSeparator());\n                }\n            });\n            InvocationResult result = invoker.execute(request);\n            if (result.getExitCode() != 0) {\n                LOGGER.warn(\n                    \"Could not get dependency list via maven\",\n                    result.getExecutionException()\n                );\n                LOGGER.warn(mavenExecutionErrors.toString());\n            }\n            return Files.lines(mavenOutputFile)\n                .filter(StringUtils::isNotBlank)\n                .map(MavenDependencyScanner::findDependency)\n                .filter(Objects::nonNull);\n        } catch (MavenInvocationException e) {\n            LOGGER.warn(\"Could not get dependency list via maven\", e);\n        } catch (Exception e) {\n            LOGGER.warn(\"Error reading file\", e);\n        }\n        return Stream.empty();\n    }\n\n    private static String getMvnPath() {\n        boolean isWindows = System.getProperty(\"os.name\").toLowerCase().contains(\"win\");\n        String locateCommand = isWindows ? \"where\" : \"which\";\n        String mvnCommand = isWindows ? \"mvn.cmd\" : \"mvn\";\n        ProcessBuilder processBuilder = new ProcessBuilder(locateCommand, mvnCommand);\n\n        try {\n            Process process = processBuilder.start();\n            BufferedReader reader = new BufferedReader(\n                new InputStreamReader(process.getInputStream())\n            );\n            String line = reader.readLine();\n            reader.close();\n            return line;\n        } catch (IOException e) {\n            LOGGER.warn(\"Could not find mvn in path: {}\", e.getMessage());\n            return null;\n        }\n    }\n\n    private static Path createTempFile() {\n        try {\n            Path tempFile = Files.createTempFile(\"dependencies\", \".txt\");\n            tempFile.toFile().deleteOnExit();\n            return tempFile;\n        } catch (IOException e) {\n            LOGGER.error(\"Could not create temp file for dependencies: {}\", e.getMessage());\n            return null;\n        }\n    }\n\n    static Dependency findDependency(String line) {\n        String[] items = getItems(line);\n        if (items == null) return null;\n\n        String groupId = items[0];\n        String artifactId = items[1];\n        String version = items[3];\n        String path = items[5];\n\n        String classifier = null;\n        if (items.length > 6) {\n            classifier = items[3];\n            version = items[4];\n            path = items[6];\n        }\n\n        if (classifier != null) {\n            if (\"data\".equals(classifier)) {\n                // skip data classifier\n                return null;\n            }\n            path = path.replace(\"-\" + classifier, \"\");\n        }\n        int lastDotIndex = path.lastIndexOf('.');\n        if (lastDotIndex > 0) {\n            path = path.substring(0, lastDotIndex) + \".pom\";\n        }\n\n        Dependency dependency = new Dependency(\n            groupId + \":\" + artifactId,\n            version,\n            null,\n            LicenseCheckRulesDefinition.LANG_JAVA\n        );\n        if (new File(path).exists()) {\n            dependency.setPomPath(path);\n        }\n        return dependency;\n    }\n\n    private static String[] getItems(String line) {\n        // Remove module info introduced with Maven Dependency Plugin 3.0 (and JDK > 9)\n        line = line.replaceFirst(\" -- module .*\", \"\");\n\n        String[] items = line.trim().split(\":\");\n        if (items.length < 4) {\n            return null;\n        }\n\n        // Windows-specific absolute path \"C:\\my\\path\"\n        if (items[items.length - 2].length() == 1) {\n            items[items.length - 2] += \":\" + items[items.length - 1];\n            String[] newItems = new String[items.length - 1];\n            System.arraycopy(items, 0, newItems, 0, items.length - 1);\n            items = newItems;\n        }\n\n        return items;\n    }\n\n    private Function<Dependency, Dependency> loadLicenseFromPom(\n        Map<Pattern, String> licenseMap,\n        MavenSettings settings\n    ) {\n        return (Dependency dependency) -> {\n            if (\n                StringUtils.isNotBlank(dependency.getLicense()) || dependency.getPomPath() == null\n            ) {\n                return dependency;\n            }\n\n            return loadLicense(licenseMap, settings, dependency);\n        };\n    }\n\n    private static Dependency loadLicense(\n        Map<Pattern, String> licenseMap,\n        MavenSettings settings,\n        Dependency dependency\n    ) {\n        String pomPath = dependency.getPomPath();\n        if (pomPath != null) {\n            List<License> licenses = LicenseFinder.getLicenses(\n                new File(pomPath),\n                settings.userSettings,\n                settings.globalSettings\n            );\n            if (licenses.isEmpty()) {\n                LOGGER.info(\"No licenses found in dependency {}\", dependency.getName());\n                return dependency;\n            }\n\n            for (License license : licenses) {\n                boolean found = licenseMatcher(licenseMap, dependency, license);\n                if (found) {\n                    break;\n                }\n            }\n        }\n        return dependency;\n    }\n\n    /**\n     * @return true if license was found in defined license list, false otherwise\n     */\n    private static boolean licenseMatcher(\n        Map<Pattern, String> licenseMap,\n        Dependency dependency,\n        License license\n    ) {\n        String licenseName = license.getName();\n        if (StringUtils.isBlank(licenseName)) {\n            LOGGER.info(\"Dependency '{}' has an empty license.\", dependency.getName());\n            return false;\n        }\n\n        for (Entry<Pattern, String> entry : licenseMap.entrySet()) {\n            if (entry.getKey().matcher(licenseName).matches()) {\n                dependency.setLicense(entry.getValue());\n                return true;\n            }\n        }\n\n        dependency.setLicense(licenseName);\n\n        LOGGER.info(\n            \"No licenses match found for '{}' in dependency '{}:{}'\",\n            licenseName,\n            dependency.getName(),\n            dependency.getVersion()\n        );\n\n        return false;\n    }\n\n    private static MavenSettings getSettingsFromCommandLineArgs() {\n        String globalSettings = null;\n        String userSettings = null;\n        String commandArgs = System.getProperty(\"sun.java.command\");\n        try (java.util.Scanner scanner = new java.util.Scanner(commandArgs)) {\n            while (scanner.hasNext()) {\n                String part = scanner.next();\n                if (part.equals(\"-gs\") || part.equals(\"--global-settings\")) {\n                    globalSettings = scanner.next();\n                } else if (part.equals(\"-s\") || part.equals(\"--settings\")) {\n                    userSettings = scanner.next();\n                }\n            }\n        } catch (Exception e) {\n            LOGGER.debug(\"Ignore unparsable command line\", e);\n        }\n        return new MavenSettings(globalSettings, userSettings);\n    }\n}\n\nclass MavenSettings {\n\n    final String globalSettings;\n    final String userSettings;\n\n    MavenSettings(String globalSettings, String userSetttings) {\n        this.globalSettings = globalSettings;\n        this.userSettings = userSetttings;\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/maven/Setting.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.maven;\n\nimport java.io.File;\n\nclass Setting {\n\n    private File localRepositoryPath;\n\n    public File getLocalRepositoryPath() {\n        return localRepositoryPath;\n    }\n\n    public void setLocalRepositoryPath(File localRepositoryPath) {\n        this.localRepositoryPath = localRepositoryPath;\n    }\n\n    public void setLocalRepositoryPath(String localRepositoryPath) {\n        this.localRepositoryPath = new File(localRepositoryPath);\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/maven/SettingsXmlHandler.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.maven;\n\nimport org.xml.sax.Attributes;\nimport org.xml.sax.helpers.DefaultHandler;\n\nclass SettingsXmlHandler extends DefaultHandler {\n\n    private boolean enableReadElementData = false;\n    private String tagName = \"\";\n    private Setting setting;\n\n    @Override\n    public void startDocument() {\n        setting = new Setting();\n    }\n\n    @Override\n    public void startElement(String uri, String localName, String qName, Attributes attributes) {\n        if (qName.equals(\"localRepository\")) {\n            tagName = \"localRepository\";\n        } else {\n            tagName = \"\";\n        }\n\n        enableReadElementData = true;\n    }\n\n    @Override\n    public void endElement(String uri, String localName, String qName) {\n        enableReadElementData = false;\n    }\n\n    @Override\n    public void characters(char[] ch, int start, int length) {\n        if (enableReadElementData && tagName.equals(\"localRepository\")) {\n            setting.setLocalRepositoryPath(new String(ch, start, length));\n        }\n    }\n\n    public Setting getSetting() {\n        return setting;\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/maven/SettingsXmlParser.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.maven;\n\nimport java.io.File;\nimport javax.xml.XMLConstants;\nimport javax.xml.parsers.SAXParser;\nimport javax.xml.parsers.SAXParserFactory;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\npublic class SettingsXmlParser extends SettingsXmlHandler {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(SettingsXmlParser.class);\n\n    private SettingsXmlParser() {}\n\n    public static Setting parseXmlFile(File filePath) {\n        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();\n        SettingsXmlHandler settingsXmlHandler = new SettingsXmlHandler();\n        SAXParser saxParser;\n\n        if (filePath.exists()) {\n            try {\n                saxParser = saxParserFactory.newSAXParser();\n                saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, \"\");\n                saxParser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, \"\");\n                saxParser.parse(filePath, settingsXmlHandler);\n            } catch (Exception e) {\n                LOGGER.warn(\"Could not parse file \" + filePath, e);\n            }\n        } else {\n            LOGGER.info(\"Could not find file \" + filePath);\n        }\n\n        return settingsXmlHandler.getSetting();\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/npm/PackageJsonDependencyScanner.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.npm;\n\nimport at.porscheinformatik.sonarqube.licensecheck.Dependency;\nimport at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition;\nimport at.porscheinformatik.sonarqube.licensecheck.Scanner;\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileNotFoundException;\nimport java.io.InputStream;\nimport java.util.HashSet;\nimport java.util.Set;\nimport javax.json.Json;\nimport javax.json.JsonArray;\nimport javax.json.JsonObject;\nimport javax.json.JsonReader;\nimport javax.json.JsonValue;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.sonar.api.batch.fs.FilePredicate;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.sensor.SensorContext;\n\npublic class PackageJsonDependencyScanner implements Scanner {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(\n        PackageJsonDependencyScanner.class\n    );\n    private static final String PACKAGE_LICENSE = \"license\";\n\n    private final LicenseMappingService licenseMappingService;\n    private final boolean resolveTransitiveDeps;\n\n    public PackageJsonDependencyScanner(\n        LicenseMappingService licenseMappingService,\n        boolean resolveTransitiveDeps\n    ) {\n        this.licenseMappingService = licenseMappingService;\n        this.resolveTransitiveDeps = resolveTransitiveDeps;\n    }\n\n    @Override\n    public Set<Dependency> scan(SensorContext context) {\n        FileSystem fs = context.fileSystem();\n        FilePredicate packageJsonPredicate = fs.predicates().matchesPathPattern(\"**/package.json\");\n\n        Set<Dependency> allDependencies = new HashSet<>();\n\n        LOGGER.info(\"Scanning for NPM dependencies (dir={})\", fs.baseDir());\n        for (InputFile packageJsonFile : fs.inputFiles(packageJsonPredicate)) {\n            context.markForPublishing(packageJsonFile);\n\n            LOGGER.info(\"Scanning package.json: (path={})\", packageJsonFile);\n            allDependencies.addAll(dependencyParser(fs.baseDir(), packageJsonFile));\n        }\n\n        return allDependencies;\n    }\n\n    private Set<Dependency> dependencyParser(File baseDir, InputFile packageJsonFile) {\n        Set<Dependency> dependencies = new HashSet<>();\n\n        try (\n            InputStream fis = packageJsonFile.inputStream();\n            JsonReader jsonReader = Json.createReader(fis);\n        ) {\n            JsonObject packageJson = jsonReader.readObject();\n\n            JsonObject packageJsonDependencies = packageJson.getJsonObject(\"dependencies\");\n            if (packageJsonDependencies != null) {\n                scanDependencies(\n                    new File(packageJsonFile.uri().resolve(\"node_modules\")),\n                    packageJsonDependencies.keySet(),\n                    dependencies\n                );\n                dependencies.forEach(dependency -> {\n                    dependency.setInputComponent(packageJsonFile);\n                    dependency.setTextRange(\n                        packageJsonFile.newRange(1, 0, packageJsonFile.lines(), 0)\n                    );\n                });\n            }\n        } catch (Exception e) {\n            LOGGER.error(\"Error reading package.json\", e);\n        }\n\n        return dependencies;\n    }\n\n    private void scanDependencies(\n        File nodeModulesDir,\n        Set<String> packageNames,\n        Set<Dependency> dependencies\n    ) {\n        LOGGER.info(\"Scanning NPM packages \" + packageNames);\n\n        for (String packageName : packageNames) {\n            if (dependencies.stream().anyMatch(d -> packageName.equals(d.getName()))) {\n                LOGGER.debug(\n                    \"Package {} has already been encountered and will not be scanned again\",\n                    packageName\n                );\n                continue;\n            }\n\n            File packageJsonFile = new File(nodeModulesDir, packageName + \"/package.json\");\n            if (!packageJsonFile.exists()) {\n                LOGGER.warn(\n                    \"No package.json file found for package {} at {} - skipping dependency.\",\n                    packageName,\n                    packageJsonFile\n                );\n                continue;\n            }\n\n            try (\n                InputStream fis = new FileInputStream(packageJsonFile);\n                JsonReader jsonReader = Json.createReader(fis);\n            ) {\n                JsonObject packageJson = jsonReader.readObject();\n                if (packageJson != null) {\n                    String license = licenseMappingService.mapLicense(readLicense(packageJson));\n\n                    dependencies.add(\n                        new Dependency(\n                            packageName,\n                            packageJson.getString(\"version\", null),\n                            license,\n                            LicenseCheckRulesDefinition.LANG_JS\n                        )\n                    );\n\n                    if (resolveTransitiveDeps) {\n                        JsonObject packageJsonDependencies = packageJson.getJsonObject(\n                            \"dependencies\"\n                        );\n                        if (packageJsonDependencies != null) {\n                            scanDependencies(\n                                nodeModulesDir,\n                                packageJsonDependencies.keySet(),\n                                dependencies\n                            );\n                        }\n                    }\n                }\n            } catch (FileNotFoundException e) {\n                LOGGER.error(\"Could not load package.json\", e);\n            } catch (Exception e) {\n                LOGGER.error(\"Could not check NPM package \" + packageName, e);\n            }\n        }\n    }\n\n    private String readLicense(JsonObject packageJson) {\n        if (packageJson.containsKey(PACKAGE_LICENSE)) {\n            final Object licenceObj = packageJson.get(PACKAGE_LICENSE);\n            if (licenceObj instanceof JsonObject) {\n                return ((JsonObject) licenceObj).getString(\"type\", \"\");\n            } else {\n                return packageJson.getString(PACKAGE_LICENSE, \"\");\n            }\n        } else if (packageJson.containsKey(\"licenses\")) {\n            final JsonArray licenses = packageJson.getJsonArray(\"licenses\");\n            if (licenses.size() == 1) {\n                return licenses.getJsonObject(0).getString(\"type\", \"\");\n            } else if (licenses.size() > 1) {\n                String license = \"(\";\n                for (JsonValue licenseObj : licenses) {\n                    if (licenseObj instanceof JsonObject) {\n                        String licensePart = licenseObj.asJsonObject().getString(\"type\", \"\");\n                        if (!licensePart.trim().isEmpty()) {\n                            license += license.length() > 1 ? (\" OR \" + licensePart) : licensePart;\n                        }\n                    }\n                }\n                return license.length() == 1 ? \"\" : (license + \")\");\n            }\n        }\n        return \"\";\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/projectlicense/ProjectLicense.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.projectlicense;\n\nimport java.io.StringReader;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Objects;\nimport javax.json.Json;\nimport javax.json.JsonArray;\nimport javax.json.JsonObject;\nimport javax.json.JsonReader;\n\npublic class ProjectLicense implements Comparable<ProjectLicense> {\n\n    public static final String FIELD_PROJECT_KEY = \"projectKey\";\n    public static final String FIELD_LICENSE = \"license\";\n    public static final String FIELD_ALLOWED = \"allowed\";\n\n    private final String projectKey;\n    private final String license;\n    private final Boolean allowed;\n\n    public ProjectLicense(String projectKey, String license, Boolean allowed) {\n        super();\n        this.projectKey = projectKey;\n        this.license = license;\n        this.allowed = allowed;\n    }\n\n    public ProjectLicense(String projectKey, String license, String allowed) {\n        this(projectKey, license, Boolean.parseBoolean(allowed));\n    }\n\n    public String getProjectKey() {\n        return projectKey;\n    }\n\n    public String getLicense() {\n        return license;\n    }\n\n    public Boolean getAllowed() {\n        return allowed;\n    }\n\n    @Deprecated\n    public static List<ProjectLicense> fromString(String projectLicensesString) {\n        List<ProjectLicense> projectLicenses = new ArrayList<>();\n\n        try (JsonReader jsonReader = Json.createReader(new StringReader(projectLicensesString))) {\n            JsonArray projectLicensesJson = jsonReader.readArray();\n            for (int i = 0; i < projectLicensesJson.size(); i++) {\n                JsonObject projectLicenseJson = projectLicensesJson.getJsonObject(i);\n                projectLicenses.add(\n                    new ProjectLicense(\n                        projectLicenseJson.getString(FIELD_PROJECT_KEY),\n                        projectLicenseJson.getString(FIELD_LICENSE),\n                        projectLicenseJson.getString(\"status\")\n                    )\n                );\n            }\n        }\n\n        return projectLicenses;\n    }\n\n    public int compareTo(ProjectLicense o) {\n        if (o == null) {\n            return 1;\n        }\n\n        if (this.getProjectKey().compareTo(o.getProjectKey()) == 0) {\n            if (this.getLicense().compareTo(o.getLicense()) == 0) {\n                return this.getAllowed().compareTo(o.getAllowed());\n            } else {\n                return this.getLicense().compareTo(o.getLicense());\n            }\n        } else {\n            return this.getProjectKey().compareTo(o.getProjectKey());\n        }\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) {\n            return true;\n        }\n        if (o == null || getClass() != o.getClass()) {\n            return false;\n        }\n        ProjectLicense that = (ProjectLicense) o;\n        return (\n            Objects.equals(projectKey, that.projectKey) &&\n            Objects.equals(license, that.license) &&\n            Objects.equals(allowed, that.allowed)\n        );\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.hash(projectKey, license, allowed);\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/projectlicense/ProjectLicenseService.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.projectlicense;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPropertyKeys.PROJECT_LICENSE_SET;\nimport static at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicense.FIELD_ALLOWED;\nimport static at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicense.FIELD_LICENSE;\nimport static at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicense.FIELD_PROJECT_KEY;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.stream.Collectors;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.scanner.ScannerSide;\nimport org.sonar.api.server.ServerSide;\n\n@ServerSide\n@ScannerSide\npublic class ProjectLicenseService {\n\n    private final Configuration configuration;\n\n    public ProjectLicenseService(Configuration configuration) {\n        super();\n        this.configuration = configuration;\n    }\n\n    public List<ProjectLicense> getProjectLicenseList() {\n        return Arrays.stream(configuration.getStringArray(PROJECT_LICENSE_SET))\n            .map(idx -> {\n                String idxProp = \".\" + idx + \".\";\n                String projectKey = configuration\n                    .get(PROJECT_LICENSE_SET + idxProp + FIELD_PROJECT_KEY)\n                    .orElse(null);\n                String license = configuration\n                    .get(PROJECT_LICENSE_SET + idxProp + FIELD_LICENSE)\n                    .orElse(null);\n                Boolean allowed = configuration\n                    .getBoolean(PROJECT_LICENSE_SET + idxProp + FIELD_ALLOWED)\n                    .orElse(Boolean.FALSE);\n                return new ProjectLicense(projectKey, license, allowed);\n            })\n            .collect(Collectors.toList());\n    }\n\n    public Collection<ProjectLicense> getProjectLicenseList(String projectKey) {\n        Collection<ProjectLicense> allProjectLicenses = getProjectLicenseList();\n        Collection<ProjectLicense> projectSpecificLicenses = new ArrayList<>();\n\n        for (ProjectLicense projectLicense : allProjectLicenses) {\n            if (projectLicense.getProjectKey().equals(projectKey)) {\n                projectSpecificLicenses.add(projectLicense);\n            }\n        }\n\n        return projectSpecificLicenses;\n    }\n}\n"
  },
  {
    "path": "src/main/java/at/porscheinformatik/sonarqube/licensecheck/utils/IOUtils.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.utils;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.io.StringWriter;\nimport java.nio.charset.StandardCharsets;\n\npublic class IOUtils {\n\n    private IOUtils() {}\n\n    public static String readToString(InputStream input) throws IOException {\n        Reader in = new InputStreamReader(input, StandardCharsets.UTF_8);\n        StringWriter out = new StringWriter();\n        int n;\n        char[] buffer = new char[4096];\n        while ((n = in.read(buffer)) != -1) {\n            out.write(buffer, 0, n);\n        }\n        return out.toString();\n    }\n}\n"
  },
  {
    "path": "src/main/resources/at/porscheinformatik/sonarqube/licensecheck/license/spdx_license_list.json",
    "content": "[\n  {\n    \"identifier\": \"Glide\",\n    \"name\": \"3dfx Glide License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Abstyles\",\n    \"name\": \"Abstyles License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AFL-1.1\",\n    \"name\": \"Academic Free License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AFL-1.2\",\n    \"name\": \"Academic Free License v1.2\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AFL-2.0\",\n    \"name\": \"Academic Free License v2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AFL-2.1\",\n    \"name\": \"Academic Free License v2.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AFL-3.0\",\n    \"name\": \"Academic Free License v3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AMPAS\",\n    \"name\": \"Academy of Motion Picture Arts and Sciences BSD\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"APL-1.0\",\n    \"name\": \"Adaptive Public License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Adobe-Glyph\",\n    \"name\": \"Adobe Glyph List License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"APAFML\",\n    \"name\": \"Adobe Postscript AFM License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Adobe-2006\",\n    \"name\": \"Adobe Systems Incorporated Source Code License Agreement\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AGPL-1.0\",\n    \"name\": \"Affero General Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Afmparse\",\n    \"name\": \"Afmparse License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Aladdin\",\n    \"name\": \"Aladdin Free Public License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ADSL\",\n    \"name\": \"Amazon Digital Services License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AMDPLPA\",\n    \"name\": \"AMD's plpa_map.c License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ANTLR-PD\",\n    \"name\": \"ANTLR Software Rights Notice\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Apache-1.0\",\n    \"name\": \"Apache License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Apache-1.1\",\n    \"name\": \"Apache License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Apache-2.0\",\n    \"name\": \"Apache License 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AML\",\n    \"name\": \"Apple MIT License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"APSL-1.0\",\n    \"name\": \"Apple Public Source License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"APSL-1.1\",\n    \"name\": \"Apple Public Source License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"APSL-1.2\",\n    \"name\": \"Apple Public Source License 1.2\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"APSL-2.0\",\n    \"name\": \"Apple Public Source License 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Artistic-1.0\",\n    \"name\": \"Artistic License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Artistic-1.0-Perl\",\n    \"name\": \"Artistic License 1.0 (Perl)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Artistic-1.0-cl8\",\n    \"name\": \"Artistic License 1.0 w/clause 8\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Artistic-2.0\",\n    \"name\": \"Artistic License 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AAL\",\n    \"name\": \"Attribution Assurance License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Bahyph\",\n    \"name\": \"Bahyph License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Barr\",\n    \"name\": \"Barr License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Beerware\",\n    \"name\": \"Beerware License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BitTorrent-1.0\",\n    \"name\": \"BitTorrent Open Source License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BitTorrent-1.1\",\n    \"name\": \"BitTorrent Open Source License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSL-1.0\",\n    \"name\": \"Boost Software License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Borceux\",\n    \"name\": \"Borceux license\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-2-Clause\",\n    \"name\": \"BSD 2-clause \\\"Simplified\\\" License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-2-Clause-FreeBSD\",\n    \"name\": \"BSD 2-clause FreeBSD License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-2-Clause-NetBSD\",\n    \"name\": \"BSD 2-clause NetBSD License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-3-Clause\",\n    \"name\": \"BSD 3-clause \\\"New\\\" or \\\"Revised\\\" License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-3-Clause-Clear\",\n    \"name\": \"BSD 3-clause Clear License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-3-Clause-No-Nuclear-License\",\n    \"name\": \"BSD 3-Clause No Nuclear License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-3-Clause-No-Nuclear-License-2014\",\n    \"name\": \"BSD 3-Clause No Nuclear License 2014\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-3-Clause-No-Nuclear-Warranty\",\n    \"name\": \"BSD 3-Clause No Nuclear Warranty\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-4-Clause\",\n    \"name\": \"BSD 4-clause \\\"Original\\\" or \\\"Old\\\" License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-Protection\",\n    \"name\": \"BSD Protection License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-Source-Code\",\n    \"name\": \"BSD Source Code Attribution\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-3-Clause-Attribution\",\n    \"name\": \"BSD with attribution\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"0BSD\",\n    \"name\": \"BSD Zero Clause License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-4-Clause-UC\",\n    \"name\": \"BSD-4-Clause (University of California-Specific)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"bzip2-1.0.5\",\n    \"name\": \"bzip2 and libbzip2 License v1.0.5\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"bzip2-1.0.6\",\n    \"name\": \"bzip2 and libbzip2 License v1.0.6\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Caldera\",\n    \"name\": \"Caldera License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CECILL-1.0\",\n    \"name\": \"CeCILL Free Software License Agreement v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CECILL-1.1\",\n    \"name\": \"CeCILL Free Software License Agreement v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CECILL-2.0\",\n    \"name\": \"CeCILL Free Software License Agreement v2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CECILL-2.1\",\n    \"name\": \"CeCILL Free Software License Agreement v2.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CECILL-B\",\n    \"name\": \"CeCILL-B Free Software License Agreement\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CECILL-C\",\n    \"name\": \"CeCILL-C Free Software License Agreement\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ClArtistic\",\n    \"name\": \"Clarified Artistic License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MIT-CMU\",\n    \"name\": \"CMU License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CNRI-Jython\",\n    \"name\": \"CNRI Jython License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CNRI-Python\",\n    \"name\": \"CNRI Python License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CNRI-Python-GPL-Compatible\",\n    \"name\": \"CNRI Python Open Source GPL Compatible License Agreement\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CPOL-1.02\",\n    \"name\": \"Code Project Open License 1.02\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CDDL-1.0\",\n    \"name\": \"Common Development and Distribution License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CDDL-1.1\",\n    \"name\": \"Common Development and Distribution License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CPAL-1.0\",\n    \"name\": \"Common Public Attribution License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CPL-1.0\",\n    \"name\": \"Common Public License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CATOSL-1.1\",\n    \"name\": \"Computer Associates Trusted Open Source License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Condor-1.1\",\n    \"name\": \"Condor Public License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-1.0\",\n    \"name\": \"Creative Commons Attribution 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-2.0\",\n    \"name\": \"Creative Commons Attribution 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-2.5\",\n    \"name\": \"Creative Commons Attribution 2.5\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-3.0\",\n    \"name\": \"Creative Commons Attribution 3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-4.0\",\n    \"name\": \"Creative Commons Attribution 4.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-ND-1.0\",\n    \"name\": \"Creative Commons Attribution No Derivatives 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-ND-2.0\",\n    \"name\": \"Creative Commons Attribution No Derivatives 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-ND-2.5\",\n    \"name\": \"Creative Commons Attribution No Derivatives 2.5\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-ND-3.0\",\n    \"name\": \"Creative Commons Attribution No Derivatives 3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-ND-4.0\",\n    \"name\": \"Creative Commons Attribution No Derivatives 4.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-1.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-2.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-2.5\",\n    \"name\": \"Creative Commons Attribution Non Commercial 2.5\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-3.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial 3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-4.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial 4.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-ND-1.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial No Derivatives 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-ND-2.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial No Derivatives 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-ND-2.5\",\n    \"name\": \"Creative Commons Attribution Non Commercial No Derivatives 2.5\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-ND-3.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial No Derivatives 3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-ND-4.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial No Derivatives 4.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-SA-1.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial Share Alike 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-SA-2.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial Share Alike 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-SA-2.5\",\n    \"name\": \"Creative Commons Attribution Non Commercial Share Alike 2.5\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-SA-3.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial Share Alike 3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-NC-SA-4.0\",\n    \"name\": \"Creative Commons Attribution Non Commercial Share Alike 4.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-SA-1.0\",\n    \"name\": \"Creative Commons Attribution Share Alike 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-SA-2.0\",\n    \"name\": \"Creative Commons Attribution Share Alike 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-SA-2.5\",\n    \"name\": \"Creative Commons Attribution Share Alike 2.5\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-SA-3.0\",\n    \"name\": \"Creative Commons Attribution Share Alike 3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC-BY-SA-4.0\",\n    \"name\": \"Creative Commons Attribution Share Alike 4.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CC0-1.0\",\n    \"name\": \"Creative Commons Zero v1.0 Universal\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Crossword\",\n    \"name\": \"Crossword License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CrystalStacker\",\n    \"name\": \"CrystalStacker License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"CUA-OPL-1.0\",\n    \"name\": \"CUA Office Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Cube\",\n    \"name\": \"Cube License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"curl\",\n    \"name\": \"curl License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"D-FSL-1.0\",\n    \"name\": \"Deutsche Freie Software Lizenz\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"diffmark\",\n    \"name\": \"diffmark license\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"WTFPL\",\n    \"name\": \"Do What The F*ck You Want To Public License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"DOC\",\n    \"name\": \"DOC License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Dotseqn\",\n    \"name\": \"Dotseqn License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"DSDP\",\n    \"name\": \"DSDP License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"dvipdfm\",\n    \"name\": \"dvipdfm License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"EPL-1.0\",\n    \"name\": \"Eclipse Public License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ECL-1.0\",\n    \"name\": \"Educational Community License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ECL-2.0\",\n    \"name\": \"Educational Community License v2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"eGenix\",\n    \"name\": \"eGenix.com Public License 1.1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"EFL-1.0\",\n    \"name\": \"Eiffel Forum License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"EFL-2.0\",\n    \"name\": \"Eiffel Forum License v2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MIT-advertising\",\n    \"name\": \"Enlightenment License (e16)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MIT-enna\",\n    \"name\": \"enna License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Entessa\",\n    \"name\": \"Entessa Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ErlPL-1.1\",\n    \"name\": \"Erlang Public License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"EUDatagrid\",\n    \"name\": \"EU DataGrid Software License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"EUPL-1.0\",\n    \"name\": \"European Union Public License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"EUPL-1.1\",\n    \"name\": \"European Union Public License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Eurosym\",\n    \"name\": \"Eurosym License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Fair\",\n    \"name\": \"Fair License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MIT-feh\",\n    \"name\": \"feh License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Frameworx-1.0\",\n    \"name\": \"Frameworx Open License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"FreeImage\",\n    \"name\": \"FreeImage Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"FTL\",\n    \"name\": \"Freetype Project License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"FSFAP\",\n    \"name\": \"FSF All Permissive License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"FSFUL\",\n    \"name\": \"FSF Unlimited License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"FSFULLR\",\n    \"name\": \"FSF Unlimited License (with License Retention)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Giftware\",\n    \"name\": \"Giftware License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GL2PS\",\n    \"name\": \"GL2PS License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Glulxe\",\n    \"name\": \"Glulxe License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"AGPL-3.0\",\n    \"name\": \"GNU Affero General Public License v3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GFDL-1.1\",\n    \"name\": \"GNU Free Documentation License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GFDL-1.2\",\n    \"name\": \"GNU Free Documentation License v1.2\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GFDL-1.3\",\n    \"name\": \"GNU Free Documentation License v1.3\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-1.0\",\n    \"name\": \"GNU General Public License v1.0 only\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-2.0\",\n    \"name\": \"GNU General Public License v2.0 only\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-3.0\",\n    \"name\": \"GNU General Public License v3.0 only\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LGPL-2.1\",\n    \"name\": \"GNU Lesser General Public License v2.1 only\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LGPL-3.0\",\n    \"name\": \"GNU Lesser General Public License v3.0 only\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LGPL-2.0\",\n    \"name\": \"GNU Library General Public License v2 only\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"gnuplot\",\n    \"name\": \"gnuplot License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"gSOAP-1.3b\",\n    \"name\": \"gSOAP Public License v1.3b\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"HaskellReport\",\n    \"name\": \"Haskell Language Report License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"HPND\",\n    \"name\": \"Historic Permission Notice and Disclaimer\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"IBM-pibs\",\n    \"name\": \"IBM PowerPC Initialization and Boot Software\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"IPL-1.0\",\n    \"name\": \"IBM Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ICU\",\n    \"name\": \"ICU License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ImageMagick\",\n    \"name\": \"ImageMagick License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"iMatix\",\n    \"name\": \"iMatix Standard Function Library Agreement\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Imlib2\",\n    \"name\": \"Imlib2 License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"IJG\",\n    \"name\": \"Independent JPEG Group License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Info-ZIP\",\n    \"name\": \"Info-ZIP License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Intel-ACPI\",\n    \"name\": \"Intel ACPI Software License Agreement\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Intel\",\n    \"name\": \"Intel Open Source License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Interbase-1.0\",\n    \"name\": \"Interbase Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"IPA\",\n    \"name\": \"IPA Font License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ISC\",\n    \"name\": \"ISC License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"JasPer-2.0\",\n    \"name\": \"JasPer License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"JSON\",\n    \"name\": \"JSON License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LPPL-1.0\",\n    \"name\": \"LaTeX Project Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LPPL-1.1\",\n    \"name\": \"LaTeX Project Public License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LPPL-1.2\",\n    \"name\": \"LaTeX Project Public License v1.2\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LPPL-1.3a\",\n    \"name\": \"LaTeX Project Public License v1.3a\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LPPL-1.3c\",\n    \"name\": \"LaTeX Project Public License v1.3c\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Latex2e\",\n    \"name\": \"Latex2e License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"BSD-3-Clause-LBNL\",\n    \"name\": \"Lawrence Berkeley National Labs BSD variant license\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Leptonica\",\n    \"name\": \"Leptonica License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LGPLLR\",\n    \"name\": \"Lesser General Public License For Linguistic Resources\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Libpng\",\n    \"name\": \"libpng License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"libtiff\",\n    \"name\": \"libtiff License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LAL-1.2\",\n    \"name\": \"Licence Art Libre 1.2\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LAL-1.3\",\n    \"name\": \"Licence Art Libre 1.3\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LiLiQ-P-1.1\",\n    \"name\": \"Licence Libre du Québec – Permissive version 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LiLiQ-Rplus-1.1\",\n    \"name\": \"Licence Libre du Québec – Réciprocité forte version 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LiLiQ-R-1.1\",\n    \"name\": \"Licence Libre du Québec – Réciprocité version 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LPL-1.02\",\n    \"name\": \"Lucent Public License v1.02\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LPL-1.0\",\n    \"name\": \"Lucent Public License Version 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MakeIndex\",\n    \"name\": \"MakeIndex License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MTLL\",\n    \"name\": \"Matrix Template Library License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MS-PL\",\n    \"name\": \"Microsoft Public License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MS-RL\",\n    \"name\": \"Microsoft Reciprocal License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MirOS\",\n    \"name\": \"MirOS Licence\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MITNFA\",\n    \"name\": \"MIT +no-false-attribs license\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MIT\",\n    \"name\": \"MIT License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Motosoto\",\n    \"name\": \"Motosoto License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MPL-1.0\",\n    \"name\": \"Mozilla Public License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MPL-1.1\",\n    \"name\": \"Mozilla Public License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MPL-2.0\",\n    \"name\": \"Mozilla Public License 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"MPL-2.0-no-copyleft-exception\",\n    \"name\": \"Mozilla Public License 2.0 (no copyleft exception)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"mpich2\",\n    \"name\": \"mpich2 License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Multics\",\n    \"name\": \"Multics License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Mup\",\n    \"name\": \"Mup License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NASA-1.3\",\n    \"name\": \"NASA Open Source Agreement 1.3\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Naumen\",\n    \"name\": \"Naumen Public License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NBPL-1.0\",\n    \"name\": \"Net Boolean Public License v1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Net-SNMP\",\n    \"name\": \"Net-SNMP License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NetCDF\",\n    \"name\": \"NetCDF license\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NGPL\",\n    \"name\": \"Nethack General Public License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NOSL\",\n    \"name\": \"Netizen Open Source License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NPL-1.0\",\n    \"name\": \"Netscape Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NPL-1.1\",\n    \"name\": \"Netscape Public License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Newsletr\",\n    \"name\": \"Newsletr License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NLPL\",\n    \"name\": \"No Limit Public License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Nokia\",\n    \"name\": \"Nokia Open Source License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NPOSL-3.0\",\n    \"name\": \"Non-Profit Open Software License 3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NLOD-1.0\",\n    \"name\": \"Norwegian Licence for Open Government Data\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Noweb\",\n    \"name\": \"Noweb License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NRL\",\n    \"name\": \"NRL License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NTP\",\n    \"name\": \"NTP License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Nunit\",\n    \"name\": \"Nunit License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OCLC-2.0\",\n    \"name\": \"OCLC Research Public License 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ODbL-1.0\",\n    \"name\": \"ODC Open Database License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"PDDL-1.0\",\n    \"name\": \"ODC Public Domain Dedication & License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OCCT-PL\",\n    \"name\": \"Open CASCADE Technology Public License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OGTSL\",\n    \"name\": \"Open Group Test Suite License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.2.2\",\n    \"name\": \"Open LDAP Public License  2.2.2\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-1.1\",\n    \"name\": \"Open LDAP Public License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-1.2\",\n    \"name\": \"Open LDAP Public License v1.2\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-1.3\",\n    \"name\": \"Open LDAP Public License v1.3\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-1.4\",\n    \"name\": \"Open LDAP Public License v1.4\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.0\",\n    \"name\": \"Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.0.1\",\n    \"name\": \"Open LDAP Public License v2.0.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.1\",\n    \"name\": \"Open LDAP Public License v2.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.2\",\n    \"name\": \"Open LDAP Public License v2.2\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.2.1\",\n    \"name\": \"Open LDAP Public License v2.2.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.3\",\n    \"name\": \"Open LDAP Public License v2.3\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.4\",\n    \"name\": \"Open LDAP Public License v2.4\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.5\",\n    \"name\": \"Open LDAP Public License v2.5\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.6\",\n    \"name\": \"Open LDAP Public License v2.6\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.7\",\n    \"name\": \"Open LDAP Public License v2.7\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OLDAP-2.8\",\n    \"name\": \"Open LDAP Public License v2.8\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OML\",\n    \"name\": \"Open Market License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OPL-1.0\",\n    \"name\": \"Open Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OSL-1.0\",\n    \"name\": \"Open Software License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OSL-1.1\",\n    \"name\": \"Open Software License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OSL-2.0\",\n    \"name\": \"Open Software License 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OSL-2.1\",\n    \"name\": \"Open Software License 2.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OSL-3.0\",\n    \"name\": \"Open Software License 3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OpenSSL\",\n    \"name\": \"OpenSSL License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OSET-PL-2.1\",\n    \"name\": \"OSET Public License version 2.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"PHP-3.0\",\n    \"name\": \"PHP License v3.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"PHP-3.01\",\n    \"name\": \"PHP License v3.01\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Plexus\",\n    \"name\": \"Plexus Classworlds License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"PostgreSQL\",\n    \"name\": \"PostgreSQL License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"psfrag\",\n    \"name\": \"psfrag License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"psutils\",\n    \"name\": \"psutils License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Python-2.0\",\n    \"name\": \"Python License 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"QPL-1.0\",\n    \"name\": \"Q Public License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Qhull\",\n    \"name\": \"Qhull License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Rdisc\",\n    \"name\": \"Rdisc License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"RPSL-1.0\",\n    \"name\": \"RealNetworks Public Source License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"RPL-1.1\",\n    \"name\": \"Reciprocal Public License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"RPL-1.5\",\n    \"name\": \"Reciprocal Public License 1.5\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"RHeCos-1.1\",\n    \"name\": \"Red Hat eCos Public License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"RSCPL\",\n    \"name\": \"Ricoh Source Code Public License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"RSA-MD\",\n    \"name\": \"RSA Message-Digest License \",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Ruby\",\n    \"name\": \"Ruby License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SAX-PD\",\n    \"name\": \"Sax Public Domain Notice\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Saxpath\",\n    \"name\": \"Saxpath License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SCEA\",\n    \"name\": \"SCEA Shared Source License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SWL\",\n    \"name\": \"Scheme Widget Library (SWL) Software License Agreement\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SMPPL\",\n    \"name\": \"Secure Messaging Protocol Public License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Sendmail\",\n    \"name\": \"Sendmail License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SGI-B-1.0\",\n    \"name\": \"SGI Free Software License B v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SGI-B-1.1\",\n    \"name\": \"SGI Free Software License B v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SGI-B-2.0\",\n    \"name\": \"SGI Free Software License B v2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OFL-1.0\",\n    \"name\": \"SIL Open Font License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"OFL-1.1\",\n    \"name\": \"SIL Open Font License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SimPL-2.0\",\n    \"name\": \"Simple Public License 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Sleepycat\",\n    \"name\": \"Sleepycat License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SNIA\",\n    \"name\": \"SNIA Public License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Spencer-86\",\n    \"name\": \"Spencer License 86\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Spencer-94\",\n    \"name\": \"Spencer License 94\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Spencer-99\",\n    \"name\": \"Spencer License 99\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SMLNJ\",\n    \"name\": \"Standard ML of New Jersey License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SugarCRM-1.1.3\",\n    \"name\": \"SugarCRM Public License v1.1.3\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SISSL\",\n    \"name\": \"Sun Industry Standards Source License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SISSL-1.2\",\n    \"name\": \"Sun Industry Standards Source License v1.2\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"SPL-1.0\",\n    \"name\": \"Sun Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Watcom-1.0\",\n    \"name\": \"Sybase Open Watcom Public License 1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"TCL\",\n    \"name\": \"TCL/TK License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"TCP-wrappers\",\n    \"name\": \"TCP Wrappers License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Unlicense\",\n    \"name\": \"The Unlicense\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"TMate\",\n    \"name\": \"TMate Open Source License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"TORQUE-1.1\",\n    \"name\": \"TORQUE v2.5+ Software License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"TOSL\",\n    \"name\": \"Trusster Open Source License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Unicode-DFS-2015\",\n    \"name\": \"Unicode License Agreement - Data Files and Software (2015)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Unicode-DFS-2016\",\n    \"name\": \"Unicode License Agreement - Data Files and Software (2016)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Unicode-TOU\",\n    \"name\": \"Unicode Terms of Use\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"UPL-1.0\",\n    \"name\": \"Universal Permissive License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"NCSA\",\n    \"name\": \"University of Illinois/NCSA Open Source License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Vim\",\n    \"name\": \"Vim License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"VOSTROM\",\n    \"name\": \"VOSTROM Public License for Open Source\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"VSL-1.0\",\n    \"name\": \"Vovida Software License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"W3C-20150513\",\n    \"name\": \"W3C Software Notice and Document License (2015-05-13)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"W3C-19980720\",\n    \"name\": \"W3C Software Notice and License (1998-07-20)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"W3C\",\n    \"name\": \"W3C Software Notice and License (2002-12-31)\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Wsuipa\",\n    \"name\": \"Wsuipa License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Xnet\",\n    \"name\": \"X.Net License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"X11\",\n    \"name\": \"X11 License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Xerox\",\n    \"name\": \"Xerox License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"XFree86-1.1\",\n    \"name\": \"XFree86 License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"xinetd\",\n    \"name\": \"xinetd License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"xpp\",\n    \"name\": \"XPP License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"XSkat\",\n    \"name\": \"XSkat License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"YPL-1.0\",\n    \"name\": \"Yahoo! Public License v1.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"YPL-1.1\",\n    \"name\": \"Yahoo! Public License v1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Zed\",\n    \"name\": \"Zed License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Zend-2.0\",\n    \"name\": \"Zend License v2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Zimbra-1.3\",\n    \"name\": \"Zimbra Public License v1.3\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Zimbra-1.4\",\n    \"name\": \"Zimbra Public License v1.4\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"Zlib\",\n    \"name\": \"zlib License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"zlib-acknowledgement\",\n    \"name\": \"zlib/libpng License with Acknowledgement\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ZPL-1.1\",\n    \"name\": \"Zope Public License 1.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ZPL-2.0\",\n    \"name\": \"Zope Public License 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"ZPL-2.1\",\n    \"name\": \"Zope Public License 2.1\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"eCos-2.0\",\n    \"name\": \"eCos license version 2.0\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-1.0+\",\n    \"name\": \"GNU General Public License v1.0 or later\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-2.0+\",\n    \"name\": \"GNU General Public License v2.0 or later\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-2.0-with-autoconf-exception\",\n    \"name\": \"GNU General Public License v2.0 w/Autoconf exception\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-2.0-with-bison-exception\",\n    \"name\": \"GNU General Public License v2.0 w/Bison exception\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-2.0-with-classpath-exception\",\n    \"name\": \"GNU General Public License v2.0 w/Classpath exception\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-2.0-with-font-exception\",\n    \"name\": \"GNU General Public License v2.0 w/Font exception\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-2.0-with-GCC-exception\",\n    \"name\": \"GNU General Public License v2.0 w/GCC Runtime Library exception\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-3.0+\",\n    \"name\": \"GNU General Public License v3.0 or later\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-3.0-with-autoconf-exception\",\n    \"name\": \"GNU General Public License v3.0 w/Autoconf exception\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"GPL-3.0-with-GCC-exception\",\n    \"name\": \"GNU General Public License v3.0 w/GCC Runtime Library exception\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LGPL-2.1+\",\n    \"name\": \"GNU Lesser General Public License v2.1 or later\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LGPL-3.0+\",\n    \"name\": \"GNU Lesser General Public License v3.0 or later\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"LGPL-2.0+\",\n    \"name\": \"GNU Library General Public License v2 or later\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"StandardML-NJ\",\n    \"name\": \"Standard ML of New Jersey License\",\n    \"status\": \"false\"\n  },\n  {\n    \"identifier\": \"WXwindows\",\n    \"name\": \"wxWindows Library License\",\n    \"status\": \"false\"\n  }\n]\n"
  },
  {
    "path": "src/main/resources/at/porscheinformatik/sonarqube/licensecheck/licensemapping/default_license_mapping.json",
    "content": "[\n  { \"regex\": \"Apple License\", \"license\": \"AML\" },\n  { \"regex\": \".*Apache.*1\\\\.1.*\", \"license\": \"Apache-1.1\" },\n  { \"regex\": \".*AL.*2.*\", \"license\": \"Apache-2.0\" },\n  { \"regex\": \".*ASF.*2.*\", \"license\": \"Apache-2.0\" },\n  { \"regex\": \".*ASL.*\", \"license\": \"Apache-2.0\" },\n  { \"regex\": \".*Apache.*2.*\", \"license\": \"Apache-2.0\" },\n  { \"regex\": \".*BSD.*\", \"license\": \"BSD-3-Clause\" },\n  { \"regex\": \"^CC0.*$\", \"license\": \"CC0-1.0\" },\n  { \"regex\": \".*CDDL.*\", \"license\": \"CDDL-1.0\" },\n  { \"regex\": \".*CDDL.*GPL.*\", \"license\": \"(CDDL-1.0 OR GPL-2.0)\" },\n  { \"regex\": \".*CDDL.*1.1.*\", \"license\": \"CDDL-1.1\" },\n  {\n    \"regex\": \".*Common Development and Distribution License.*\",\n    \"license\": \"CDDL-1.1\"\n  },\n  { \"regex\": \".*Common Public License.*\", \"license\": \"CPL-1.0\" },\n  { \"regex\": \".*(EPL|Eclipse Public License).*\", \"license\": \"EPL-1.0\" },\n  { \"regex\": \".*LGPL.*2\\\\.1.*\", \"license\": \"LGPL-2.1\" },\n  { \"regex\": \".*Lesser.*Public License.*2\\\\.1.*\", \"license\": \"LGPL-2.1\" },\n  { \"regex\": \".*LGPL.*\", \"license\": \"LGPL-3.0\" },\n  { \"regex\": \".*Lesser Public License.*\", \"license\": \"LGPL-3.0\" },\n  { \"regex\": \".*MIT.*\", \"license\": \"MIT\" },\n  { \"regex\": \".*MPL.*1\\\\.1.*\", \"license\": \"MPL-1.1\" },\n  { \"regex\": \".*Mozilla Public License.*\", \"license\": \"MPL-1.1\" },\n  { \"regex\": \".*PostgreSQL License.*\", \"license\": \"PostgreSQL\" },\n  { \"regex\": \".*Eclipse.Distribution.License.*.1.0.*\", \"license\": \"BSD-3-Clause\" },\n  { \"regex\": \".*GPL2.*CPE.*\", \"license\": \"GPL-2.0-with-classpath-exception\" },\n  { \"regex\": \".*JSON.*License.*\", \"license\": \"JSON\" },\n  { \"regex\": \".*W3C.Software.*License.*\", \"license\": \"W3C\" }\n]\n"
  },
  {
    "path": "src/main/web/configuration/configuration.jsx",
    "content": "import { useEffect, useState } from \"react\";\nimport \"../shared/styles.css\";\nimport DependencyMappingsPage from \"./dependency-mappings-page\";\nimport LicenseMappingsPage from \"./license-mappings-page\";\nimport LicensesPage from \"./licenses-page\";\nimport ProjectLicensesPage from \"./project-licenses-page\";\n\nconst Configuration = () => {\n  const [currentRoute, setCurrentRoute] = useState(\"licenses\");\n\n  useEffect(() => {\n    const result = window.location.search.match(/category=([^&=]*)/);\n    if (result && result.length > 1) {\n      setCurrentRoute(result[1]);\n    }\n  }, []);\n\n  const activateCategory = (event, route) => {\n    event.preventDefault();\n    setCurrentRoute(route);\n    window.history.pushState({}, document.title, `?category=${route}`);\n  };\n\n  return (\n    <div className=\"sqlc-page\">\n      <ul className=\"sw-flex sw-items-end sw-gap-8 sw-mt-4\">\n        <li>\n          <a\n            href=\"?category=licenses\"\n            className=\"sw-flex sw-items-center\"\n            style={currentRoute === \"licenses\" ? { borderBottomColor: \"rgb(123, 135, 217)\" } : {}}\n            onClick={(e) => activateCategory(e, \"licenses\")}\n          >\n            Licenses\n          </a>\n        </li>\n        <li>\n          <a\n            href=\"?category=license-mappings\"\n            className={currentRoute === \"license-mappings\" ? \"selected\" : \"\"}\n            onClick={(e) => activateCategory(e, \"license-mappings\")}\n          >\n            License Mappings\n          </a>\n        </li>\n        <li>\n          <a\n            href=\"?category=dependency-mappings\"\n            className={currentRoute === \"dependency-mappings\" ? \"selected\" : \"\"}\n            onClick={(e) => activateCategory(e, \"dependency-mappings\")}\n          >\n            Dependency Mappings\n          </a>\n        </li>\n        <li>\n          <a\n            href=\"?category=project-licenses\"\n            className={currentRoute === \"project-licenses\" ? \"selected\" : \"\"}\n            onClick={(e) => activateCategory(e, \"project-licenses\")}\n          >\n            Project Licenses\n          </a>\n        </li>\n      </ul>\n      <br />\n      {currentRoute === \"licenses\" && <LicensesPage />}\n      {currentRoute === \"dependency-mappings\" && <DependencyMappingsPage />}\n      {currentRoute === \"license-mappings\" && <LicenseMappingsPage />}\n      {currentRoute === \"project-licenses\" && <ProjectLicensesPage />}\n    </div>\n  );\n};\n\nexport default Configuration;\n"
  },
  {
    "path": "src/main/web/configuration/dependency-mappings-page.jsx",
    "content": "import {\n  Button,\n  ButtonIcon,\n  Checkbox,\n  IconDelete,\n  IconEdit,\n  IconSearch,\n  IconTriangleDown,\n  IconTriangleUp,\n  Label,\n  Modal,\n  TextInput,\n} from \"@sonarsource/echoes-react\";\nimport { useEffect, useState } from \"react\";\nimport { loadDependencyMappings, loadLicenses, saveDependencyMappings } from \"./sonar-api\";\n\nconst DependencyMappingsPage = () => {\n  const [items, setItems] = useState([]);\n  const [itemToDelete, setItemToDelete] = useState(null);\n  const [itemToEdit, setItemToEdit] = useState(null);\n  const [editMode, setEditMode] = useState(null);\n  const [searchText, setSearchText] = useState(\"\");\n  const [licenses, setLicenses] = useState([]);\n  const [sortBy, setSortBy] = useState(\"key\");\n  const [sortDirection, setSortDirection] = useState(\"asc\");\n\n  useEffect(() => {\n    load();\n  }, []);\n\n  const load = () => {\n    return Promise.all([\n      loadLicenses().then((l) => setLicenses(l)),\n      loadDependencyMappings().then((md) => setItems(md)),\n    ]);\n  };\n\n  const findLicenseName = (license) => {\n    const licenseItem = licenses.find((l) => l.id === license);\n    return licenseItem ? licenseItem.name : \"-\";\n  };\n\n  const showAddDialog = () => {\n    setItemToEdit({});\n    setEditMode(\"add\");\n  };\n\n  const showEditDialog = (item) => {\n    setItemToEdit({ ...item, old_key: item.key });\n    setEditMode(\"edit\");\n  };\n\n  const cancelEdit = () => {\n    setItemToEdit(null);\n  };\n\n  const saveItems = (items) => {\n    saveDependencyMappings(items).then(() => {\n      loadDependencyMappings().then((md) => setItems(md));\n      setItemToEdit(null);\n      setItemToDelete(null);\n    });\n  };\n\n  const saveItem = (item) => {\n    if (editMode === \"add\") {\n      saveItems([...items, item]);\n    } else {\n      const newItems = [...items];\n      const itemToChange = newItems.find((i) => i.key === item.old_key);\n      itemToChange.key = item.key;\n      itemToChange.license = item.license;\n      itemToChange.overwrite = item.overwrite;\n      saveItems(newItems);\n    }\n  };\n\n  const showDeleteDialog = (item) => {\n    setItemToDelete(item);\n  };\n\n  const cancelDelete = () => {\n    setItemToDelete(null);\n  };\n\n  const deleteItem = (item) => {\n    saveItems(items.filter((i) => i.key !== item.key));\n  };\n\n  const sort = (param) => {\n    if (param === sortBy) {\n      setSortDirection(sortDirection === \"asc\" ? \"desc\" : \"asc\");\n    }\n    setSortBy(param);\n  };\n\n  const sortedItems = [...items].sort((a, b) => {\n    const modifier = sortDirection === \"desc\" ? -1 : 1;\n    if (a[sortBy] < b[sortBy]) return -modifier;\n    if (a[sortBy] > b[sortBy]) return modifier;\n    return 0;\n  });\n\n  const displayedItems = !searchText\n    ? sortedItems\n    : sortedItems.filter(\n        (item) =>\n          item.key.toLowerCase().includes(searchText.toLowerCase()) ||\n          item.license.toLowerCase().includes(searchText.toLowerCase()),\n      );\n\n  return (\n    <div>\n      <header className=\"sw-mb-5\">\n        <h1 className=\"sw-mb-4\">License Check - Dependency Mappings</h1>\n        <div className=\"sw-mb-4\">Maps a dependency name/key (with regex) to a license</div>\n        <div className=\"page-actions\">\n          <Button onClick={showAddDialog}>Add Dependency Mapping</Button>\n        </div>\n      </header>\n\n      <div className=\"sw-mb-4\">\n        <TextInput\n          value={searchText}\n          onChange={(e) => setSearchText(e.target.value)}\n          prefix={<IconSearch key=\"IconSearch\" />}\n        />\n      </div>\n\n      <div>\n        <table className=\"sqlc-data\">\n          <thead>\n            <tr>\n              <th onClick={() => sort(\"key\")} scope=\"col\">\n                Key Regex\n                {sortBy === \"key\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"license\")} scope=\"col\">\n                License\n                {sortBy === \"license\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"overwrite\")} scope=\"col\">\n                Overwrite License\n                {sortBy === \"overwrite\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th scope=\"col\">Actions</th>\n            </tr>\n          </thead>\n          <tbody>\n            {displayedItems.map((item) => (\n              <tr key={item.key}>\n                <td className=\"sw-truncate\">{item.key}</td>\n                <td className=\"sw-truncate\">\n                  {item.license} / {findLicenseName(item.license)}\n                </td>\n                <td>{item.overwrite === \"true\" ? \"Yes\" : \"No\"}</td>\n                <td className=\"sw-whitespace-nowrap\">\n                  <ButtonIcon\n                    variety=\"default-ghost\"\n                    ariaLabel=\"Edit\"\n                    Icon={IconEdit}\n                    onClick={() => showEditDialog(item)}\n                  />\n                  {items.length > 1 && (\n                    <ButtonIcon\n                      variety=\"default-ghost\"\n                      ariaLabel=\"Delete\"\n                      Icon={IconDelete}\n                      onClick={() => showDeleteDialog(item)}\n                    />\n                  )}\n                </td>\n              </tr>\n            ))}\n            {!displayedItems.length && (\n              <tr>\n                <td colSpan=\"4\">No Maven dependencies available</td>\n              </tr>\n            )}\n          </tbody>\n        </table>\n      </div>\n\n      <Modal\n        title={editMode === \"add\" ? \"Add Maven Dependency\" : \"Edit Maven Dependency\"}\n        isOpen={!!itemToEdit}\n        onOpenChange={(isOpen) => {\n          if (!isOpen) {\n            cancelEdit();\n          }\n        }}\n        primaryButton={<Button onClick={() => saveItem(itemToEdit)}>Save</Button>}\n        secondaryButton={\n          <Button variety=\"default-ghost\" onClick={cancelEdit}>\n            Cancel\n          </Button>\n        }\n        content={\n          itemToEdit && (\n            <div>\n              <div className=\"modal-field\">\n                <Label htmlFor=\"keyEdit\">\n                  Key Regex<em className=\"mandatory\">*</em>\n                </Label>\n                <TextInput\n                  required\n                  autoFocus={true}\n                  value={itemToEdit.key || \"\"}\n                  onChange={(e) => setItemToEdit({ ...itemToEdit, key: e.target.value })}\n                  id=\"keyEdit\"\n                  name=\"keyEdit\"\n                  maxLength=\"255\"\n                />\n              </div>\n              <div className=\"modal-field\">\n                <Label htmlFor=\"licenseSelect\">\n                  License<em className=\"mandatory\">*</em>\n                </Label>\n                <select\n                  required\n                  value={itemToEdit.license || \"\"}\n                  onChange={(e) => setItemToEdit({ ...itemToEdit, license: e.target.value })}\n                  id=\"licenseSelect\"\n                  name=\"licenseSelect\"\n                  className=\"sw-w-full sw-px-3 sw-py-2 sw-border sw-border-neutral-200 sw-rounded\"\n                >\n                  <option value=\"\">Select a license</option>\n                  {licenses.map((license) => (\n                    <option key={license.id} value={license.id}>\n                      {license.id} / {license.name}\n                    </option>\n                  ))}\n                </select>\n              </div>\n              <div className=\"modal-field\">\n                <Label>Overwrite License</Label>\n                <div>\n                  <Checkbox\n                    label=\"Overwrite\"\n                    name=\"overwrite\"\n                    checked={itemToEdit.overwrite === \"true\"}\n                    onCheck={(checked) =>\n                      setItemToEdit({\n                        ...itemToEdit,\n                        overwrite: checked ? \"true\" : \"false\",\n                      })\n                    }\n                  />\n                </div>\n              </div>\n            </div>\n          )\n        }\n      />\n\n      <Modal\n        title=\"Delete Maven Dependency\"\n        isOpen={!!itemToDelete}\n        onOpenChange={(isOpen) => {\n          if (!isOpen) {\n            cancelDelete();\n          }\n        }}\n        description={`Are you sure you want to delete the Maven dependency mapping \"${itemToDelete?.key}\" / \"${itemToDelete?.license}\"?`}\n        primaryButton={<Button onClick={() => deleteItem(itemToDelete)}>Delete</Button>}\n        secondaryButton={\n          <Button variety=\"default-ghost\" onClick={cancelDelete}>\n            Cancel\n          </Button>\n        }\n      />\n    </div>\n  );\n};\n\nexport default DependencyMappingsPage;\n"
  },
  {
    "path": "src/main/web/configuration/license-mappings-page.jsx",
    "content": "import {\n  Button,\n  ButtonIcon,\n  IconDelete,\n  IconEdit,\n  IconSearch,\n  IconTriangleDown,\n  IconTriangleUp,\n  Label,\n  Modal,\n  TextInput,\n} from \"@sonarsource/echoes-react\";\nimport { useEffect, useState } from \"react\";\nimport { loadLicenseMappings, loadLicenses, saveLicenseMappings } from \"./sonar-api\";\n\nconst LicenseMappingsPage = () => {\n  const [items, setItems] = useState([]);\n  const [itemToDelete, setItemToDelete] = useState(null);\n  const [itemToEdit, setItemToEdit] = useState(null);\n  const [editMode, setEditMode] = useState(null);\n  const [searchText, setSearchText] = useState(\"\");\n  const [licenses, setLicenses] = useState([]);\n  const [sortBy, setSortBy] = useState(\"regex\");\n  const [sortDirection, setSortDirection] = useState(\"asc\");\n\n  useEffect(() => {\n    load();\n  }, []);\n\n  const load = () => {\n    return Promise.all([\n      loadLicenses().then((l) => setLicenses(l)),\n      loadLicenseMappings().then((ml) => setItems(ml)),\n    ]);\n  };\n\n  const findLicenseName = (license) => {\n    const licenseItem = licenses.find((l) => l.id === license);\n    return licenseItem ? licenseItem.name : \"-\";\n  };\n\n  const showAddDialog = () => {\n    setItemToEdit({});\n    setEditMode(\"add\");\n  };\n\n  const showEditDialog = (item) => {\n    setItemToEdit({ ...item, old_regex: item.regex });\n    setEditMode(\"edit\");\n  };\n\n  const cancelEdit = () => {\n    setItemToEdit(null);\n  };\n\n  const saveItems = (items) => {\n    saveLicenseMappings(items).then(() => {\n      loadLicenseMappings().then((ml) => setItems(ml));\n      setItemToEdit(null);\n      setItemToDelete(null);\n    });\n  };\n\n  const saveItem = (item) => {\n    if (editMode === \"add\") {\n      saveItems([...items, item]);\n    } else {\n      const newItems = [...items];\n      const itemToChange = newItems.find((i) => i.regex === item.old_regex);\n      itemToChange.regex = item.regex;\n      itemToChange.license = item.license;\n      saveItems(newItems);\n    }\n  };\n\n  const showDeleteDialog = (item) => {\n    setItemToDelete(item);\n  };\n\n  const cancelDelete = () => {\n    setItemToDelete(null);\n  };\n\n  const deleteItem = (item) => {\n    saveItems(items.filter((i) => i.regex !== item.regex));\n  };\n\n  const sort = (param) => {\n    if (param === sortBy) {\n      setSortDirection(sortDirection === \"asc\" ? \"desc\" : \"asc\");\n    }\n    setSortBy(param);\n  };\n\n  const sortedItems = [...items].sort((a, b) => {\n    const modifier = sortDirection === \"desc\" ? -1 : 1;\n    if (a[sortBy] < b[sortBy]) return -modifier;\n    if (a[sortBy] > b[sortBy]) return modifier;\n    return 0;\n  });\n\n  const displayedItems = !searchText\n    ? sortedItems\n    : sortedItems.filter(\n        (item) =>\n          item.regex.toLowerCase().includes(searchText.toLowerCase()) ||\n          item.license.toLowerCase().includes(searchText.toLowerCase()),\n      );\n\n  return (\n    <div>\n      <header className=\"sw-mb-5\">\n        <h1 className=\"sw-mb-4\">License Check - License Mappings</h1>\n        <div className=\"sw-mb-4\">Maps a license name (with regex) to a license</div>\n        <div className=\"page-actions\">\n          <Button onClick={showAddDialog}>Add License Mapping</Button>\n        </div>\n      </header>\n\n      <div className=\"sw-mb-4\">\n        <TextInput\n          value={searchText}\n          onChange={(e) => setSearchText(e.target.value)}\n          prefix={<IconSearch key=\"IconSearch\" />}\n        />\n      </div>\n\n      <div>\n        <table className=\"sqlc-data\">\n          <thead>\n            <tr>\n              <th onClick={() => sort(\"regex\")} scope=\"col\">\n                License Text Regex\n                {sortBy === \"regex\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"license\")} scope=\"col\">\n                License\n                {sortBy === \"license\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th scope=\"col\">Actions</th>\n            </tr>\n          </thead>\n          <tbody>\n            {displayedItems.map((item) => (\n              <tr key={item.regex}>\n                <td className=\"sw-truncate\">{item.regex}</td>\n                <td className=\"sw-truncate\">\n                  {item.license} / {findLicenseName(item.license)}\n                </td>\n                <td className=\"sw-whitespace-nowrap\">\n                  <ButtonIcon\n                    variety=\"default-ghost\"\n                    ariaLabel=\"Edit\"\n                    Icon={IconEdit}\n                    onClick={() => showEditDialog(item)}\n                  />\n                  {items.length > 1 && (\n                    <ButtonIcon\n                      variety=\"default-ghost\"\n                      ariaLabel=\"Delete\"\n                      Icon={IconDelete}\n                      onClick={() => showDeleteDialog(item)}\n                    />\n                  )}\n                </td>\n              </tr>\n            ))}\n            {!displayedItems.length && (\n              <tr>\n                <td colSpan=\"3\">No license mappings available</td>\n              </tr>\n            )}\n          </tbody>\n        </table>\n      </div>\n\n      <Modal\n        title={editMode === \"add\" ? \"Add License Mapping\" : \"Edit License Mapping\"}\n        isOpen={!!itemToEdit}\n        onOpenChange={(isOpen) => {\n          if (!isOpen) {\n            cancelEdit();\n          }\n        }}\n        primaryButton={<Button onClick={() => saveItem(itemToEdit)}>Save</Button>}\n        secondaryButton={\n          <Button variety=\"default-ghost\" onClick={cancelEdit}>\n            Cancel\n          </Button>\n        }\n        content={\n          itemToEdit && (\n            <div>\n              <div className=\"modal-field\">\n                <Label htmlFor=\"regexEdit\">\n                  License Text Regex<em className=\"mandatory\">*</em>\n                </Label>\n                <TextInput\n                  required\n                  autoFocus\n                  value={itemToEdit.regex || \"\"}\n                  onChange={(e) => setItemToEdit({ ...itemToEdit, regex: e.target.value })}\n                  id=\"regexEdit\"\n                  name=\"regexEdit\"\n                  maxLength=\"255\"\n                />\n              </div>\n              <div className=\"modal-field\">\n                <Label htmlFor=\"licenseSelect\">\n                  License<em className=\"mandatory\">*</em>\n                </Label>\n                <select\n                  required\n                  value={itemToEdit.license || \"\"}\n                  onChange={(e) => setItemToEdit({ ...itemToEdit, license: e.target.value })}\n                  id=\"licenseSelect\"\n                  name=\"licenseSelect\"\n                  className=\"sw-w-full sw-px-3 sw-py-2 sw-border sw-border-neutral-200 sw-rounded\"\n                >\n                  <option value=\"\">Select a license</option>\n                  {licenses.map((license) => (\n                    <option key={license.id} value={license.id}>\n                      {license.id} / {license.name}\n                    </option>\n                  ))}\n                </select>\n              </div>\n            </div>\n          )\n        }\n      />\n\n      <Modal\n        title=\"Delete License Mapping\"\n        isOpen={!!itemToDelete}\n        onOpenChange={(isOpen) => {\n          if (!isOpen) {\n            cancelDelete();\n          }\n        }}\n        description={`Are you sure you want to delete the license mapping \"${itemToDelete?.regex}\" / \"${itemToDelete?.license}\"?`}\n        primaryButton={<Button onClick={() => deleteItem(itemToDelete)}>Delete</Button>}\n        secondaryButton={\n          <Button variety=\"default-ghost\" onClick={cancelDelete}>\n            Cancel\n          </Button>\n        }\n      />\n    </div>\n  );\n};\n\nexport default LicenseMappingsPage;\n"
  },
  {
    "path": "src/main/web/configuration/licenses-page.jsx",
    "content": "import {\n  Button,\n  ButtonIcon,\n  Checkbox,\n  IconCheckCircle,\n  IconDelete,\n  IconEdit,\n  IconError,\n  IconSearch,\n  IconTriangleDown,\n  IconTriangleUp,\n  Label,\n  Modal,\n  TextInput,\n} from \"@sonarsource/echoes-react\";\nimport { useEffect, useState } from \"react\";\nimport { loadLicenses, saveLicenses } from \"./sonar-api\";\n\nconst LicensesPage = () => {\n  const [items, setItems] = useState([]);\n  const [itemToDelete, setItemToDelete] = useState(null);\n  const [itemToEdit, setItemToEdit] = useState(null);\n  const [editMode, setEditMode] = useState(null);\n  const [searchText, setSearchText] = useState(\"\");\n  const [sortBy, setSortBy] = useState(\"id\");\n  const [sortDirection, setSortDirection] = useState(\"asc\");\n\n  useEffect(() => {\n    load();\n  }, []);\n\n  const load = () => {\n    return loadLicenses().then((l) => setItems(l));\n  };\n\n  const showAddDialog = () => {\n    setItemToEdit({ allowed: false });\n    setEditMode(\"add\");\n  };\n\n  const showEditDialog = (item) => {\n    setItemToEdit({ ...item });\n    setEditMode(\"edit\");\n  };\n\n  const cancelEdit = () => {\n    setItemToEdit(null);\n  };\n\n  const importSpdx = () => {\n    fetch(\"https://raw.githubusercontent.com/spdx/license-list-data/main/json/licenses.json\")\n      .then((r) => r.json())\n      .then((data) => {\n        const licenses = data.licenses.map((l) => ({\n          id: l.licenseId,\n          name: l.name,\n          allowed: false,\n        }));\n        saveItems(licenses);\n      });\n  };\n\n  const saveItems = (items) => {\n    saveLicenses(items).then(() => {\n      load();\n      setItemToEdit(null);\n      setItemToDelete(null);\n    });\n  };\n\n  const saveItem = (item) => {\n    if (editMode === \"add\") {\n      saveItems([...items, item]);\n    } else {\n      const newItems = [...items];\n      const itemToChange = newItems.find((i) => i.id === item.id);\n      if (itemToChange) {\n        itemToChange.name = item.name;\n        itemToChange.allowed = item.allowed;\n        saveItems(newItems);\n      }\n    }\n  };\n\n  const showDeleteDialog = (item) => {\n    setItemToDelete(item);\n  };\n\n  const cancelDelete = () => {\n    setItemToDelete(null);\n  };\n\n  const deleteItem = (item) => {\n    saveItems(items.filter((i) => i.id !== item.id));\n  };\n\n  const sort = (param) => {\n    if (param === sortBy) {\n      setSortDirection(sortDirection === \"asc\" ? \"desc\" : \"asc\");\n    }\n    setSortBy(param);\n  };\n\n  const sortedItems = [...items].sort((a, b) => {\n    const modifier = sortDirection === \"desc\" ? -1 : 1;\n    if (a[sortBy] < b[sortBy]) return -modifier;\n    if (a[sortBy] > b[sortBy]) return modifier;\n    return 0;\n  });\n\n  const displayedItems = !searchText\n    ? sortedItems\n    : sortedItems.filter(\n        (item) =>\n          item.name.toLowerCase().includes(searchText.toLowerCase()) ||\n          item.id.toLowerCase().includes(searchText.toLowerCase()),\n      );\n\n  return (\n    <div>\n      <header className=\"sw-mb-5\">\n        <h1 className=\"sw-mb-4\">License Check - Licenses</h1>\n        <div className=\"sw-mb-4\">Add and administer licenses, allow or disallow globally.</div>\n        <div className=\"page-actions\">\n          <Button onClick={showAddDialog}>Add License</Button>\n        </div>\n      </header>\n\n      {items.length === 0 ? (\n        <div className=\"panel\">\n          <p>\n            Currently, you have no licenses defined. You can add all licenses from\n            <a href=\"https://github.com/spdx/license-list-data\">SPDX</a>.\n          </p>\n          <Button className=\"button\" onClick={importSpdx}>\n            Add SPDX list\n          </Button>\n        </div>\n      ) : (\n        <>\n          <div className=\"sw-mb-4\">\n            <TextInput\n              value={searchText}\n              onChange={(e) => setSearchText(e.target.value)}\n              prefix={<IconSearch key=\"IconSearch\" />}\n            ></TextInput>\n          </div>\n\n          <div>\n            <table className=\"sqlc-data\">\n              <thead>\n                <tr>\n                  <th onClick={() => sort(\"id\")} scope=\"col\">\n                    Identifier\n                    {sortBy === \"id\" &&\n                      (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n                  </th>\n                  <th onClick={() => sort(\"name\")} scope=\"col\">\n                    Name\n                    {sortBy === \"name\" &&\n                      (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n                  </th>\n                  <th onClick={() => sort(\"allowed\")} scope=\"col\">\n                    Status\n                    {sortBy === \"allowed\" &&\n                      (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n                  </th>\n                  <th scope=\"col\">Actions</th>\n                </tr>\n              </thead>\n              <tbody>\n                {displayedItems.map((item) => (\n                  <tr key={item.id}>\n                    <td className=\"sw-truncate\">{item.id}</td>\n                    <td className=\"sw-truncate\">{item.name}</td>\n                    <td>\n                      {item.allowed === \"true\" ? (\n                        <IconCheckCircle style={{ color: \"green\" }} />\n                      ) : (\n                        <IconError style={{ color: \"darkred\" }} />\n                      )}\n                      &nbsp;\n                      {item.allowed === \"true\" ? \"Allowed\" : \"Forbidden\"}\n                    </td>\n                    <td className=\"sw-whitespace-nowrap\">\n                      <ButtonIcon\n                        variety=\"default-ghost\"\n                        ariaLabel=\"Edit\"\n                        Icon={IconEdit}\n                        onClick={() => showEditDialog(item)}\n                      />\n                      {items.length > 1 && (\n                        <ButtonIcon\n                          variety=\"default-ghost\"\n                          ariaLabel=\"Delete\"\n                          Icon={IconDelete}\n                          onClick={() => showDeleteDialog(item)}\n                        />\n                      )}\n                    </td>\n                  </tr>\n                ))}\n              </tbody>\n            </table>\n          </div>\n        </>\n      )}\n\n      <Modal\n        title={editMode === \"add\" ? \"Add License\" : \"Edit License\"}\n        isOpen={!!itemToEdit}\n        onOpenChange={(isOpen) => {\n          if (!isOpen) {\n            cancelEdit();\n          }\n        }}\n        primaryButton={<Button onClick={() => saveItem(itemToEdit)}>Save</Button>}\n        secondaryButton={\n          <Button variety=\"default-ghost\" onClick={cancelEdit}>\n            Cancel\n          </Button>\n        }\n        content={\n          itemToEdit && (\n            <div>\n              <div className=\"modal-field\">\n                <Label htmlFor=\"itemIdEdit\">\n                  Identifier<em className=\"mandatory\">*</em>\n                </Label>\n                <TextInput\n                  required\n                  autoFocus={editMode === \"add\"}\n                  disabled={editMode !== \"add\"}\n                  value={itemToEdit.id || \"\"}\n                  onChange={(e) => setItemToEdit({ ...itemToEdit, id: e.target.value })}\n                  id=\"itemIdEdit\"\n                  name=\"itemIdEdit\"\n                  type=\"text\"\n                  size=\"50\"\n                  maxLength=\"255\"\n                />\n              </div>\n              <div className=\"modal-field\">\n                <Label htmlFor=\"itemNameEdit\">\n                  Name<em className=\"mandatory\">*</em>\n                </Label>\n                <TextInput\n                  required\n                  autoFocus={editMode !== \"add\"}\n                  value={itemToEdit.name || \"\"}\n                  onChange={(e) => setItemToEdit({ ...itemToEdit, name: e.target.value })}\n                  id=\"itemNameEdit\"\n                  name=\"itemNameEdit\"\n                  type=\"text\"\n                  size=\"50\"\n                  maxLength=\"255\"\n                />\n              </div>\n              <div className=\"modal-field\">\n                <Label>\n                  Status<em className=\"mandatory\">*</em>\n                </Label>\n                <div>\n                  <Checkbox\n                    label=\"Allowed\"\n                    name=\"itemAllowedEdit\"\n                    checked={itemToEdit.allowed === \"true\"}\n                    onCheck={(checked) =>\n                      setItemToEdit({\n                        ...itemToEdit,\n                        allowed: checked ? \"true\" : \"false\",\n                      })\n                    }\n                  />\n                </div>\n              </div>\n            </div>\n          )\n        }\n      ></Modal>\n\n      <Modal\n        title=\"Delete license\"\n        description={`Are you sure you want to delete the license \"${itemToDelete?.id}\" / \"${itemToDelete?.name}\"?`}\n        primaryButton={<Button onClick={() => deleteItem(itemToDelete)}>Delete</Button>}\n        secondaryButton={\n          <Button variety=\"default-ghost\" onClick={cancelDelete}>\n            Cancel\n          </Button>\n        }\n        isOpen={!!itemToDelete}\n        onOpenChange={(isOpen) => {\n          if (!isOpen) cancelDelete();\n        }}\n      ></Modal>\n    </div>\n  );\n};\n\nexport default LicensesPage;\n"
  },
  {
    "path": "src/main/web/configuration/project-licenses-page.jsx",
    "content": "import {\n  Button,\n  ButtonIcon,\n  Checkbox,\n  IconCheckCircle,\n  IconDelete,\n  IconEdit,\n  IconError,\n  IconSearch,\n  IconTriangleDown,\n  IconTriangleUp,\n  Label,\n  Modal,\n  TextInput,\n} from \"@sonarsource/echoes-react\";\nimport { useEffect, useState } from \"react\";\nimport { loadLicenses, loadProjectLicenses, loadProjects, saveProjectLicenses } from \"./sonar-api\";\n\nconst ProjectLicensesPage = () => {\n  const [items, setItems] = useState([]);\n  const [itemToDelete, setItemToDelete] = useState(null);\n  const [itemToEdit, setItemToEdit] = useState(null);\n  const [editMode, setEditMode] = useState(null);\n  const [searchText, setSearchText] = useState(\"\");\n  const [licenses, setLicenses] = useState([]);\n  const [projects, setProjects] = useState([]);\n  const [sortBy, setSortBy] = useState(\"status\");\n  const [sortDirection, setSortDirection] = useState(\"asc\");\n\n  useEffect(() => {\n    load();\n  }, []);\n\n  const load = () => {\n    return Promise.all([\n      loadLicenses().then((l) => setLicenses(l)),\n      loadProjects().then((p) => setProjects(p)),\n      loadProjectLicenses().then((pl) => setItems(pl)),\n    ]);\n  };\n\n  const findProjectName = (projectKey) => {\n    const projectItem = projects.find((p) => p.key === projectKey);\n    return projectItem ? projectItem.name : \"-\";\n  };\n\n  const findLicenseName = (license) => {\n    const licenseItem = licenses.find((l) => l.id === license);\n    return licenseItem ? licenseItem.name : \"-\";\n  };\n\n  const showAddDialog = () => {\n    setItemToEdit({});\n    setEditMode(\"add\");\n  };\n\n  const showEditDialog = (item) => {\n    setItemToEdit({ ...item });\n    setEditMode(\"edit\");\n  };\n\n  const cancelEdit = () => {\n    setItemToEdit(null);\n  };\n\n  const saveItems = (items) => {\n    return saveProjectLicenses(items).then(() => {\n      loadProjectLicenses().then((pl) => setItems(pl));\n      setItemToEdit(null);\n      setItemToDelete(null);\n    });\n  };\n\n  const saveItem = (item) => {\n    if (editMode === \"add\") {\n      saveItems([...items, item]);\n    } else {\n      const newItems = [...items];\n      const itemToChange = newItems.find(\n        (i) => i.projectKey === item.projectKey && i.license === item.license,\n      );\n      itemToChange.allowed = item.allowed;\n      saveItems(newItems);\n    }\n  };\n\n  const showDeleteDialog = (item) => {\n    setItemToDelete(item);\n  };\n\n  const cancelDelete = () => {\n    setItemToDelete(null);\n  };\n\n  const deleteItem = (item) => {\n    saveItems(items.filter((i) => i.projectKey !== item.projectKey || i.license !== item.license));\n  };\n\n  const sort = (param) => {\n    if (param === sortBy) {\n      setSortDirection(sortDirection === \"asc\" ? \"desc\" : \"asc\");\n    }\n    setSortBy(param);\n  };\n\n  const sortedItems = [...items].sort((a, b) => {\n    const modifier = sortDirection === \"desc\" ? -1 : 1;\n    if (a[sortBy] < b[sortBy]) return -modifier;\n    if (a[sortBy] > b[sortBy]) return modifier;\n    return 0;\n  });\n\n  const displayedItems = !searchText\n    ? sortedItems\n    : sortedItems.filter(\n        (item) =>\n          item.projectKey.toLowerCase().includes(searchText.toLowerCase()) ||\n          item.license.toLowerCase().includes(searchText.toLowerCase()),\n      );\n\n  return (\n    <div>\n      <header className=\"sw-mb-5\">\n        <h1 className=\"sw-mb-4\">License Check - Project Licenses</h1>\n        <div className=\"sw-mb-4\">Allow/disallow licences for specific projects.</div>\n        <div className=\"page-actions\">\n          <Button onClick={showAddDialog}>Add Project License</Button>\n        </div>\n      </header>\n\n      <div className=\"sw-mb-4\">\n        <TextInput\n          value={searchText}\n          onChange={(e) => setSearchText(e.target.value)}\n          prefix={<IconSearch key=\"IconSearch\" />}\n        />\n      </div>\n\n      <div>\n        <table className=\"sqlc-data\">\n          <thead>\n            <tr>\n              <th onClick={() => sort(\"projectName\")} scope=\"col\">\n                Project\n                {sortBy === \"projectName\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"license\")} scope=\"col\">\n                License\n                {sortBy === \"license\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"status\")} scope=\"col\">\n                Status\n                {sortBy === \"status\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th scope=\"col\">Actions</th>\n            </tr>\n          </thead>\n          <tbody>\n            {displayedItems.map((item) => (\n              <tr key={`${item.projectKey}-${item.license}`}>\n                <td>\n                  <span title={item.projectKey}>{findProjectName(item.projectKey)}</span>\n                </td>\n                <td>\n                  {item.license} / {findLicenseName(item.license)}\n                </td>\n                <td>\n                  {item.allowed === \"true\" ? (\n                    <IconCheckCircle style={{ color: \"green\" }} />\n                  ) : (\n                    <IconError style={{ color: \"darkred\" }} />\n                  )}\n                  &nbsp;\n                  {item.allowed === \"true\" ? \"Allowed\" : \"Forbidden\"}\n                </td>\n                <td className=\"sw-whitespace-nowrap\">\n                  <ButtonIcon\n                    variety=\"default-ghost\"\n                    ariaLabel=\"Edit\"\n                    Icon={IconEdit}\n                    onClick={() => showEditDialog(item)}\n                  />\n                  <ButtonIcon\n                    variety=\"default-ghost\"\n                    ariaLabel=\"Delete\"\n                    Icon={IconDelete}\n                    onClick={() => showDeleteDialog(item)}\n                  />\n                </td>\n              </tr>\n            ))}\n            {!displayedItems.length && (\n              <tr>\n                <td colSpan=\"4\">No project licenses available</td>\n              </tr>\n            )}\n          </tbody>\n        </table>\n      </div>\n\n      <Modal\n        title={editMode === \"add\" ? \"Add Project License\" : \"Edit Project License\"}\n        isOpen={!!itemToEdit}\n        onOpenChange={(isOpen) => {\n          if (!isOpen) {\n            cancelEdit();\n          }\n        }}\n        primaryButton={<Button onClick={() => saveItem(itemToEdit)}>Save</Button>}\n        secondaryButton={\n          <Button variety=\"default-ghost\" onClick={cancelEdit}>\n            Cancel\n          </Button>\n        }\n        content={\n          itemToEdit && (\n            <div>\n              <div className=\"modal-field\">\n                <Label htmlFor=\"projectSelect\">\n                  Project<em className=\"mandatory\">*</em>\n                </Label>\n                <select\n                  required\n                  disabled={editMode !== \"add\"}\n                  value={itemToEdit.projectKey || \"\"}\n                  onChange={(e) => setItemToEdit({ ...itemToEdit, projectKey: e.target.value })}\n                  id=\"projectSelect\"\n                  name=\"projectSelect\"\n                  className=\"sw-w-full sw-px-3 sw-py-2 sw-border sw-border-neutral-200 sw-rounded\"\n                >\n                  <option value=\"\">Select a project</option>\n                  {projects.map((project) => (\n                    <option key={project.key} value={project.key}>\n                      {project.name}\n                    </option>\n                  ))}\n                </select>\n              </div>\n              <div className=\"modal-field\">\n                <Label htmlFor=\"licenseSelect\">\n                  License<em className=\"mandatory\">*</em>\n                </Label>\n                <select\n                  required\n                  disabled={editMode !== \"add\"}\n                  value={itemToEdit.license || \"\"}\n                  onChange={(e) => setItemToEdit({ ...itemToEdit, license: e.target.value })}\n                  id=\"licenseSelect\"\n                  name=\"licenseSelect\"\n                  className=\"sw-w-full sw-px-3 sw-py-2 sw-border sw-border-neutral-200 sw-rounded\"\n                >\n                  <option value=\"\">Select a license</option>\n                  {licenses.map((license) => (\n                    <option key={license.id} value={license.id}>\n                      {license.id} / {license.name}\n                    </option>\n                  ))}\n                </select>\n              </div>\n              <div className=\"modal-field\">\n                <Label>Status</Label>\n                <div>\n                  <Checkbox\n                    label=\"Allowed\"\n                    name=\"allowed\"\n                    checked={itemToEdit.allowed === \"true\"}\n                    onCheck={(checked) =>\n                      setItemToEdit({\n                        ...itemToEdit,\n                        allowed: checked ? \"true\" : \"false\",\n                      })\n                    }\n                  />\n                </div>\n              </div>\n            </div>\n          )\n        }\n      />\n\n      <Modal\n        title=\"Delete Project License\"\n        isOpen={!!itemToDelete}\n        onOpenChange={(isOpen) => {\n          if (!isOpen) {\n            cancelDelete();\n          }\n        }}\n        description={`Are you sure you want to delete the project license \"${findProjectName(\n          itemToDelete?.projectKey,\n        )}\" / \"${itemToDelete?.license}\"?`}\n        primaryButton={<Button onClick={() => deleteItem(itemToDelete)}>Delete</Button>}\n        secondaryButton={\n          <Button variety=\"default-ghost\" onClick={cancelDelete}>\n            Cancel\n          </Button>\n        }\n      />\n    </div>\n  );\n};\n\nexport default ProjectLicensesPage;\n"
  },
  {
    "path": "src/main/web/configuration/sonar-api.js",
    "content": "import { KEYS } from \"../property_keys\";\n\nfunction loadProjectPage(allProjects, pageIndex) {\n  return window.SonarRequest.getJSON(\n    `/api/components/search?qualifiers=TRK&ps=500&p=${pageIndex}`,\n  ).then((response) => {\n    const projects = response.components || [];\n    allProjects = allProjects.concat(projects);\n    if (response.paging && response.paging.total > pageIndex * 500) {\n      return loadProjectPage(allProjects.concat(projects), pageIndex + 1);\n    } else {\n      return allProjects;\n    }\n  });\n}\n\nexport function loadProjects() {\n  return loadProjectPage([], 1);\n}\n\nexport function loadLicenses() {\n  return window.SonarRequest.getJSON(`/api/settings/values?keys=${KEYS.LICENSE_SET}`).then(\n    (response) =>\n      response.settings && response.settings.length > 0 ? response.settings[0].fieldValues : [],\n  );\n}\n\nexport function saveLicenses(items) {\n  return window.SonarRequest.post(`/api/settings/set`, {\n    key: KEYS.LICENSE_SET,\n    fieldValues: items.map((i) => JSON.stringify(i)),\n  });\n}\n\nexport function loadProjectLicenses() {\n  return window.SonarRequest.getJSON(`/api/settings/values?keys=${KEYS.PROJECT_LICENSE_SET}`).then(\n    (response) =>\n      response.settings && response.settings.length > 0 ? response.settings[0].fieldValues : [],\n  );\n}\n\nexport function saveProjectLicenses(items) {\n  return window.SonarRequest.post(`/api/settings/set`, {\n    key: KEYS.PROJECT_LICENSE_SET,\n    fieldValues: items.map((i) => JSON.stringify(i)),\n  });\n}\n\nexport function loadLicenseMappings() {\n  return window.SonarRequest.getJSON(`/api/settings/values?keys=${KEYS.LICENSE_MAPPING}`).then(\n    (response) => response.settings[0].fieldValues,\n  );\n}\n\nexport function saveLicenseMappings(items) {\n  return window.SonarRequest.post(`/api/settings/set`, {\n    key: KEYS.LICENSE_MAPPING,\n    fieldValues: items.map((i) => JSON.stringify(i)),\n  });\n}\n\nexport function loadDependencyMappings() {\n  return window.SonarRequest.getJSON(`/api/settings/values?keys=${KEYS.DEPENDENCY_MAPPING}`).then(\n    (response) =>\n      response.settings && response.settings.length > 0 ? response.settings[0].fieldValues : [],\n  );\n}\n\nexport function saveDependencyMappings(items) {\n  return window.SonarRequest.post(`/api/settings/set`, {\n    key: KEYS.DEPENDENCY_MAPPING,\n    fieldValues: items.map((i) => JSON.stringify(i)),\n  });\n}\n"
  },
  {
    "path": "src/main/web/configuration.js",
    "content": "import { TooltipProvider } from \"@sonarsource/echoes-react\";\nimport { createRoot } from \"react-dom/client\";\nimport { IntlProvider } from \"react-intl\";\nimport Configuration from \"./configuration/configuration\";\n\nwindow.registerExtension(\"licensecheck/configuration\", function (options) {\n  const root = createRoot(options.el);\n  root.render(\n    <IntlProvider locale=\"en\">\n      <TooltipProvider>\n        <Configuration options={options} />\n      </TooltipProvider>\n    </IntlProvider>,\n  );\n\n  return function () {\n    root.unmount();\n  };\n});\n"
  },
  {
    "path": "src/main/web/dashboard/dashboard.jsx",
    "content": "import { Button, IconDownload } from \"@sonarsource/echoes-react\";\nimport saveAs from \"file-saverjs\";\nimport { useEffect, useState } from \"react\";\nimport \"../shared/styles.css\";\nimport Dependencies from \"./dependencies\";\nimport buildExcel from \"./excel-builder\";\nimport Licenses from \"./licenses\";\n\nconst Dashboard = ({ options }) => {\n  const [licenses, setLicenses] = useState([]);\n  const [dependencies, setDependencies] = useState([]);\n  const component = options.component;\n\n  useEffect(() => {\n    const params = new URLSearchParams(window.location.search);\n    const request = {\n      component: component.key,\n      metricKeys: \"licensecheck.license,licensecheck.dependency\",\n    };\n\n    if (params.has(\"branch\")) {\n      request.branch = params.get(\"branch\");\n    } else if (params.has(\"pullRequest\")) {\n      request.pullRequest = params.get(\"pullRequest\");\n    }\n\n    window.SonarRequest.getJSON(\"/api/measures/component\", request).then((response) => {\n      const processedLicenses = [];\n      const processedDependencies = [];\n\n      response.component.measures.forEach((measure) => {\n        if (measure.metric === \"licensecheck.license\") {\n          processedLicenses.push(...JSON.parse(measure.value));\n        } else if (measure.metric === \"licensecheck.dependency\") {\n          processedDependencies.push(...JSON.parse(measure.value));\n        }\n      });\n\n      processedDependencies.forEach((dependency) => {\n        dependency.status = \"Unknown\";\n        processedLicenses.forEach((license) => {\n          if (dependency.license === license.identifier) {\n            dependency.status = license.status === \"true\" ? \"Allowed\" : \"Forbidden\";\n          }\n        });\n      });\n\n      setLicenses(processedLicenses);\n      setDependencies(processedDependencies);\n    });\n  }, [component.key]);\n\n  const exportExcel = () => {\n    const blob = new Blob([buildExcel(dependencies, licenses)], {\n      type: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n    });\n    saveAs(blob, `license-check-${component.key}.xls`);\n  };\n\n  return (\n    <main className=\"sqlc-page\">\n      <header className=\"sw-mb-5 sw-mt-4\">\n        <div className=\"sw-flex sw-justify-between sw-items-center\">\n          <div>\n            <h1 className=\"sw-text-3xl sw-font-bold sw-mb-4\">License Check</h1>\n            <div className=\"sw-mb-4 sw-text-neutral-600\">\n              View and manage project dependencies and their licenses\n            </div>\n          </div>\n          <Button prefix={<IconDownload />} onClick={exportExcel}>\n            Export to Excel\n          </Button>\n        </div>\n      </header>\n\n      <div className=\"sw-mb-8\">\n        <Licenses licenses={licenses} />\n      </div>\n\n      <div className=\"sw-mb-4\">\n        <Dependencies dependencies={dependencies} />\n      </div>\n    </main>\n  );\n};\n\nexport default Dashboard;\n"
  },
  {
    "path": "src/main/web/dashboard/dependencies.jsx",
    "content": "import {\n  IconCheckCircle,\n  IconError,\n  IconTriangleDown,\n  IconTriangleUp,\n} from \"@sonarsource/echoes-react\";\nimport { useState } from \"react\";\n\nconst Dependencies = ({ dependencies }) => {\n  const [sortBy, setSortBy] = useState(\"name\");\n  const [sortDirection, setSortDirection] = useState(\"asc\");\n\n  const sortedDependencies = [...dependencies].sort((a, b) => {\n    const modifier = sortDirection === \"desc\" ? -1 : 1;\n    if (a[sortBy] < b[sortBy]) return -1 * modifier;\n    if (a[sortBy] > b[sortBy]) return modifier;\n    return 0;\n  });\n\n  const sort = (param) => {\n    if (param === sortBy) {\n      setSortDirection(sortDirection === \"asc\" ? \"desc\" : \"asc\");\n    }\n    setSortBy(param);\n  };\n\n  return (\n    <div>\n      <h2 className=\"sw-text-xl sw-font-bold sw-mb-4\">Dependencies</h2>\n      <div>\n        <table className=\"sqlc-data\">\n          <caption>\n            Here you see all project dependencies from Maven (including transitive) and NPM.\n          </caption>\n          <thead>\n            <tr>\n              <th onClick={() => sort(\"name\")} scope=\"col\">\n                Name\n                {sortBy === \"name\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"version\")} scope=\"col\">\n                Version\n                {sortBy === \"version\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"license\")} scope=\"col\">\n                License\n                {sortBy === \"license\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"status\")} scope=\"col\">\n                Status\n                {sortBy === \"status\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n            </tr>\n          </thead>\n          <tbody>\n            {sortedDependencies.map((dependency) => (\n              <tr key={`${dependency.name}-${dependency.version}`}>\n                <td className=\"sw-truncate\">{dependency.name}</td>\n                <td className=\"sw-truncate\">{dependency.version}</td>\n                <td className=\"sw-truncate\">{dependency.license}</td>\n                <td>\n                  {dependency.status === \"Allowed\" ? (\n                    <IconCheckCircle style={{ color: \"green\" }} />\n                  ) : dependency.status === \"Forbidden\" ? (\n                    <IconError style={{ color: \"darkred\" }} />\n                  ) : (\n                    <IconError style={{ color: \"orange\" }} />\n                  )}\n                  &nbsp;\n                  {dependency.status}\n                </td>\n              </tr>\n            ))}\n          </tbody>\n        </table>\n      </div>\n    </div>\n  );\n};\n\nexport default Dependencies;\n"
  },
  {
    "path": "src/main/web/dashboard/excel-builder.js",
    "content": "export default function buildExcel(dependencies, licenses) {\n  let excelFile = `<?xml version=\"1.0\" encoding=\"UTF-8\"?><?mso-application progid=\"Excel.Sheet\"?>\n  <Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"\n    xmlns:x=\"urn:schemas-microsoft-com:office:excel\"\n    xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\"\n    xmlns:html=\"http://www.w3.org/TR/REC-html40\">`;\n  excelFile += buildDependenciesWorksheet(dependencies);\n  excelFile += buildLicensesWorksheet(licenses);\n  excelFile += \"</Workbook>\";\n  return excelFile;\n}\n\nfunction buildDependenciesWorksheet(dependencies) {\n  let result = `<Worksheet ss:Name=\"Dependencies\">\n    <Table>\n      <Row>\n        <Cell>\n          <Data ss:Type=\"String\">Name</Data>\n        </Cell>\n        <Cell>\n          <Data ss:Type=\"String\">Version</Data>\n        </Cell>\n        <Cell>\n          <Data ss:Type=\"String\">License</Data>\n        </Cell>\n        <Cell>\n          <Data ss:Type=\"String\">Status</Data>\n        </Cell>\n      </Row>`;\n  dependencies.forEach((dependency) => {\n    result += `<Row>\n  <Cell><Data ss:Type=\"String\">${dependency.name}</Data></Cell>\n  <Cell><Data ss:Type=\"String\">${dependency.version}</Data></Cell>\n  <Cell><Data ss:Type=\"String\">${dependency.license}</Data></Cell>\n  <Cell><Data ss:Type=\"String\">${dependency.status}</Data></Cell>\n</Row>`;\n  });\n  return result + \"</Table></Worksheet>\";\n}\n\nfunction buildLicensesWorksheet(licenses) {\n  let result = `<Worksheet ss:Name=\"Licenses\">\n    <Table>\n      <Row>\n        <Cell>\n          <Data ss:Type=\"String\">Identifier</Data>\n        </Cell>\n        <Cell>\n          <Data ss:Type=\"String\">Name</Data>\n        </Cell>\n        <Cell>\n          <Data ss:Type=\"String\">Status</Data>\n        </Cell>\n      </Row>`;\n  licenses.forEach((license) => {\n    result += `<Row>\n  <Cell><Data ss:Type=\"String\">${license.identifier}</Data></Cell>\n  <Cell><Data ss:Type=\"String\">${license.name}</Data></Cell>\n  <Cell><Data ss:Type=\"String\">${license.status === \"true\" ? \"Allowed\" : \"Forbidden\"}</Data></Cell>\n</Row>`;\n  });\n  return result + \"</Table></Worksheet>\";\n}\n"
  },
  {
    "path": "src/main/web/dashboard/licenses.jsx",
    "content": "import {\n  IconCheckCircle,\n  IconError,\n  IconTriangleDown,\n  IconTriangleUp,\n} from \"@sonarsource/echoes-react\";\nimport { useState } from \"react\";\n\nconst Licenses = ({ licenses }) => {\n  const [sortBy, setSortBy] = useState(\"identifier\");\n  const [sortDirection, setSortDirection] = useState(\"asc\");\n\n  const sortedLicenses = [...licenses].sort((a, b) => {\n    const modifier = sortDirection === \"desc\" ? -1 : 1;\n    if (a[sortBy] < b[sortBy]) return -1 * modifier;\n    if (a[sortBy] > b[sortBy]) return modifier;\n    return 0;\n  });\n\n  const sort = (param) => {\n    if (param === sortBy) {\n      setSortDirection(sortDirection === \"asc\" ? \"desc\" : \"asc\");\n    }\n    setSortBy(param);\n  };\n\n  return (\n    <div>\n      <h2 className=\"sw-text-xl sw-font-bold sw-mb-4\">Licenses</h2>\n      <div>\n        <table className=\"sqlc-data\">\n          <caption>This is a list of all licenses used in any dependencies listed below.</caption>\n          <thead>\n            <tr>\n              <th onClick={() => sort(\"identifier\")} scope=\"col\">\n                Identifier\n                {sortBy === \"identifier\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"name\")} scope=\"col\">\n                Name\n                {sortBy === \"name\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n              <th onClick={() => sort(\"status\")} scope=\"col\">\n                Status\n                {sortBy === \"status\" &&\n                  (sortDirection === \"asc\" ? <IconTriangleUp /> : <IconTriangleDown />)}\n              </th>\n            </tr>\n          </thead>\n          <tbody>\n            {sortedLicenses.map((license) => (\n              <tr key={license.identifier}>\n                <td className=\"sw-truncate\">{license.identifier}</td>\n                <td className=\"sw-truncate\">{license.name}</td>\n                <td>\n                  {license.status === \"true\" ? (\n                    <IconCheckCircle style={{ color: \"green\" }} />\n                  ) : (\n                    <IconError style={{ color: \"darkred\" }} />\n                  )}\n                  &nbsp;\n                  {license.status === \"true\" ? \"Allowed\" : \"Forbidden\"}\n                </td>\n              </tr>\n            ))}\n          </tbody>\n        </table>\n      </div>\n    </div>\n  );\n};\n\nexport default Licenses;\n"
  },
  {
    "path": "src/main/web/dashboard.js",
    "content": "import { TooltipProvider } from \"@sonarsource/echoes-react\";\nimport { createRoot } from \"react-dom/client\";\nimport { IntlProvider } from \"react-intl\";\nimport Dashboard from \"./dashboard/dashboard\";\n\nwindow.registerExtension(\"licensecheck/dashboard\", function (options) {\n  const root = createRoot(options.el);\n  options.el.style.overflowY = \"auto\";\n  options.el.style.overflowX = \"hidden\";\n  options.el.style.width = \"100dvw\";\n  root.render(\n    <IntlProvider locale=\"en\">\n      <TooltipProvider>\n        <Dashboard options={options} />\n      </TooltipProvider>\n    </IntlProvider>,\n  );\n\n  return function () {\n    root.unmount();\n  };\n});\n"
  },
  {
    "path": "src/main/web/property_keys.js",
    "content": "export const KEYS = {\n  LICENSE_SET: \"licensecheck.license-set\",\n  PROJECT_LICENSE_SET: \"licensecheck.project-license-set\",\n  LICENSE_MAPPING: \"licensecheck.license-mapping\",\n  DEPENDENCY_MAPPING: \"licensecheck.dep-mapping\",\n};\n"
  },
  {
    "path": "src/main/web/shared/styles.css",
    "content": "table.sqlc-data {\n  width: 100%;\n  border-collapse: collapse;\n}\n\ntable.sqlc-data th {\n  color: rgb(29, 33, 47);\n  font: var(--echoes-typography-text-default-semi-bold);\n  cursor: pointer;\n}\n\ntable.sqlc-data th,\ntable.sqlc-data td {\n  border-top: 3px solid rgb(235, 235, 235);\n  padding: 1rem 0.5rem;\n  vertical-align: middle;\n  text-align: left;\n}\n\ntable.sqlc-data caption {\n  color: rgb(111, 119, 143);\n  text-align: left;\n  padding: 0.5rem 0;\n  margin-bottom: 0.5rem;\n}\n\ntable.sqlc-data tr:hover td {\n  background-color: rgb(244, 246, 255);\n}\n\n.arrow {\n  display: inline-block;\n  width: 0;\n  height: 0;\n  margin-left: 4px;\n  vertical-align: middle;\n}\n\n.arrow_up {\n  border-left: 4px solid transparent;\n  border-right: 4px solid transparent;\n  border-bottom: 4px solid rgb(29, 33, 47);\n}\n\n.arrow_down {\n  border-left: 4px solid transparent;\n  border-right: 4px solid transparent;\n  border-top: 4px solid rgb(29, 33, 47);\n}\n\n.sqlc-page {\n  box-sizing: border-box;\n  max-width: 1680px;\n  min-height: 100%;\n  padding: var(--echoes-dimension-space-300);\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/DependencyTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static java.util.Arrays.asList;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport org.junit.Test;\n\npublic class DependencyTest {\n\n    private static final String DEPENDENCIES_JSON =\n        \"[{\\\"name\\\":\\\"another\\\",\\\"version\\\":\\\"2.1.0\\\",\\\"license\\\":\\\"MIT\\\",\\\"lang\\\":\\\"java\\\"},\" +\n        \"{\\\"name\\\":\\\"library\\\",\\\"version\\\":\\\"1.0.0\\\",\\\"license\\\":\\\"Apache-2.0\\\",\\\"lang\\\":\\\"java\\\"}]\";\n    private static final Dependency DEP1 = new Dependency(\"another\", \"2.1.0\", \"MIT\");\n    private static final Dependency DEP2 = new Dependency(\"library\", \"1.0.0\", \"Apache-2.0\");\n\n    @Test\n    public void createString() {\n        String dependenciesJson = Dependency.createString(asList(DEP2, DEP1));\n\n        assertThat(dependenciesJson, equalTo(DEPENDENCIES_JSON));\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckMetricsTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static org.hamcrest.CoreMatchers.notNullValue;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport org.junit.Test;\n\npublic class LicenseCheckMetricsTest {\n\n    @Test\n    public void getMetrics() {\n        assertThat(new LicenseCheckMetrics().getMetrics(), notNullValue());\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckPageDefinitionTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport org.junit.Test;\nimport org.sonar.api.web.page.Context;\n\npublic class LicenseCheckPageDefinitionTest {\n\n    @Test\n    public void define() {\n        LicenseCheckPageDefinition definition = new LicenseCheckPageDefinition();\n        Context context = new Context();\n        definition.define(context);\n\n        assertThat(context.getPages().size(), is(2));\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckPluginTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\n\nimport org.junit.Test;\nimport org.sonar.api.Plugin;\n\npublic class LicenseCheckPluginTest {\n\n    @Test\n    public void define() {\n        Plugin.Context context = mock(Plugin.Context.class);\n        LicenseCheckPlugin plugin = new LicenseCheckPlugin();\n        plugin.define(context);\n\n        verify(context).addExtensions(any());\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckRulesDefinitionTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport org.junit.Test;\nimport org.sonar.api.server.rule.RulesDefinition;\n\npublic class LicenseCheckRulesDefinitionTest {\n\n    @Test\n    public void define() {\n        RulesDefinition.Context context = new RulesDefinition.Context();\n\n        new LicenseCheckRulesDefinition().define(context);\n\n        assertThat(context.repositories().size(), is(6));\n        for (RulesDefinition.Repository repository : context.repositories()) {\n            assertThat(repository.rules().size(), is(2));\n        }\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseCheckSensorTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY;\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_GROOVY;\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_JS;\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_KOTLIN;\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckRulesDefinition.RULE_REPO_KEY_TS;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\nimport at.porscheinformatik.sonarqube.licensecheck.license.License;\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;\nimport java.util.Collections;\nimport java.util.Optional;\nimport java.util.Set;\nimport org.apache.commons.lang3.reflect.FieldUtils;\nimport org.junit.Test;\nimport org.sonar.api.batch.fs.InputModule;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.SensorDescriptor;\nimport org.sonar.api.batch.sensor.measure.NewMeasure;\nimport org.sonar.api.config.Configuration;\nimport org.sonar.api.scanner.fs.InputProject;\n\npublic class LicenseCheckSensorTest {\n\n    public static final Set<Dependency> DEPENDENCIES = Collections.singleton(\n        new Dependency(\"name\", \"1.0.0\", \"MIT\")\n    );\n    private static final Set<License> LICENSES = Collections.singleton(\n        new License(\"MIT\", \"MIT\", true)\n    );\n\n    @Test\n    public void describe() {\n        Configuration configuration = createConfiguration();\n        LicenseCheckSensor sensor = new LicenseCheckSensor(configuration, null, null);\n        SensorDescriptor descriptor = mock(SensorDescriptor.class);\n        when(descriptor.name(anyString())).thenReturn(descriptor);\n\n        sensor.describe(descriptor);\n\n        verify(descriptor).name(\"License Check\");\n        verify(descriptor).createIssuesForRuleRepositories(\n            RULE_REPO_KEY,\n            RULE_REPO_KEY_JS,\n            RULE_REPO_KEY_TS,\n            RULE_REPO_KEY_GROOVY,\n            RULE_REPO_KEY_KOTLIN\n        );\n    }\n\n    @Test\n    public void execute() throws IllegalAccessException {\n        Configuration configuration = createConfiguration();\n        ValidateLicenses validateLicenses = mock(ValidateLicenses.class);\n        when(validateLicenses.validateLicenses(any(), any())).thenReturn(DEPENDENCIES);\n        when(validateLicenses.getUsedLicenses(any(), any())).thenReturn(LICENSES);\n        LicenseMappingService licenseMappingService = mock(LicenseMappingService.class);\n        LicenseCheckSensor sensor = new LicenseCheckSensor(\n            configuration,\n            validateLicenses,\n            licenseMappingService\n        );\n        Scanner mockScanner = mock(Scanner.class);\n        when(mockScanner.scan(any())).thenReturn(DEPENDENCIES);\n        Scanner[] scanners = new Scanner[] { mockScanner };\n        FieldUtils.writeField(sensor, \"scanners\", scanners, true);\n        SensorContext context = mock(SensorContext.class);\n        InputProject project = mock(InputProject.class);\n        InputModule module = mock(InputModule.class);\n        when(module.key()).thenReturn(\"my-project\");\n        when(context.module()).thenReturn(module);\n        when(project.key()).thenReturn(\"my-project\");\n        when(context.project()).thenReturn(project);\n        NewMeasure measure = mock(NewMeasure.class);\n        when(measure.forMetric(any())).thenReturn(measure);\n        when(measure.withValue(any())).thenReturn(measure);\n        when(measure.on(any())).thenReturn(measure);\n        when(context.newMeasure()).thenReturn(measure);\n\n        sensor.execute(context);\n\n        verify(measure, times(7)).save(); // 5 metrics + dependencies + licenses\n    }\n\n    private Configuration createConfiguration() {\n        Configuration configuration = mock(Configuration.class);\n        when(\n            configuration.getBoolean(LicenseCheckPropertyKeys.NPM_RESOLVE_TRANSITIVE_DEPS)\n        ).thenReturn(Optional.empty());\n        when(configuration.getBoolean(LicenseCheckPropertyKeys.ACTIVATION_KEY)).thenReturn(\n            Optional.of(true)\n        );\n        return configuration;\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/LicenseTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static java.util.Arrays.asList;\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.CoreMatchers.hasItems;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport at.porscheinformatik.sonarqube.licensecheck.license.License;\nimport java.util.List;\nimport org.junit.Test;\n\npublic class LicenseTest {\n\n    private static final String LICENSES_JSON =\n        \"[{\\\"name\\\":\\\"Apache 2.0\\\",\\\"identifier\\\":\\\"Apache-2.0\\\",\\\"status\\\":\\\"true\\\"},\" +\n        \"{\\\"name\\\":\\\"MIT License\\\",\\\"identifier\\\":\\\"MIT\\\",\\\"status\\\":\\\"false\\\"}]\";\n    private static final String LICENSES_STRING =\n        \"Apache 2.0~Apache-2.0~true;MIT License~MIT~false;\";\n\n    private static final License LIC1 = new License(\"Apache 2.0\", \"Apache-2.0\", \"true\");\n    private static final License LIC2 = new License(\"MIT License\", \"MIT\", \"false\");\n\n    @Test\n    public void createJsonString() {\n        String dependenciesJson = License.createJsonString(asList(LIC2, LIC1));\n\n        assertThat(dependenciesJson, equalTo(LICENSES_JSON));\n    }\n\n    @Test\n    public void fromStringOld() {\n        List<License> licenses = License.fromString(LICENSES_STRING);\n\n        assertThat(licenses, hasItems(LIC1, LIC2));\n    }\n\n    @Test\n    public void fromStringNew() {\n        List<License> licenses = License.fromString(LICENSES_JSON);\n\n        assertThat(licenses, hasItems(LIC1, LIC2));\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/PackageJsonDependencyScannerTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.containsInAnyOrder;\nimport static org.hamcrest.Matchers.hasSize;\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;\nimport at.porscheinformatik.sonarqube.licensecheck.npm.PackageJsonDependencyScanner;\nimport java.nio.charset.Charset;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.Set;\nimport java.util.stream.Collectors;\nimport org.junit.Test;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.internal.DefaultFileSystem;\nimport org.sonar.api.batch.fs.internal.TestInputFileBuilder;\nimport org.sonar.api.batch.sensor.SensorContext;\n\npublic class PackageJsonDependencyScannerTest {\n\n    private static final Path RESOURCE_FOLDER = Path.of(\"src/test/resources\");\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(\n        PackageJsonDependencyScannerTest.class\n    );\n\n    private SensorContext createContext(Path moduleBaseDir) {\n        SensorContext context = mock(SensorContext.class);\n        DefaultFileSystem fileSystem = new DefaultFileSystem(moduleBaseDir);\n        try {\n            // Provide all **source** files in the moduleBaseDir\n            Files.walk(moduleBaseDir).forEach(path -> {\n                try {\n                    if (Files.isDirectory(path)) {\n                        return;\n                    }\n                    if (path.toString().contains(\"/node_modules/\")) {\n                        LOGGER.info(\n                            \"Ignoring file {}, because it is in a node_modules folder\",\n                            path\n                        );\n                        return;\n                    }\n                    var lines = Files.readAllLines(path);\n                    InputFile inputFile = TestInputFileBuilder.create(\n                        \"test\",\n                        moduleBaseDir.toFile(),\n                        path.toFile()\n                    )\n                        // Required to get correct metadata\n                        .setContents(lines.stream().collect(Collectors.joining(\"\\n\")))\n                        // Otherwise .inputStream() will throw a NPE\n                        .setCharset(Charset.forName(\"UTF-8\"))\n                        .build();\n                    LOGGER.info(\"Added {} to the fs\", path);\n                    fileSystem.add(inputFile);\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n            });\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n        when(context.fileSystem()).thenReturn(fileSystem);\n        return context;\n    }\n\n    @Test\n    public void testHappyPath() {\n        Set<Dependency> dependencies = createScanner().scan(\n            createContext(RESOURCE_FOLDER.resolve(\"example\"))\n        );\n\n        assertThat(dependencies, hasSize(2));\n        assertThat(\n            dependencies,\n            containsInAnyOrder(\n                new Dependency(\"angular\", \"1.5.0\", \"MIT\"),\n                new Dependency(\"arangojs\", \"5.6.0\", \"Apache-2.0\")\n            )\n        );\n    }\n\n    @Test\n    public void testTransitive() {\n        Set<Dependency> dependencies = createScanner(true).scan(\n            createContext(RESOURCE_FOLDER.resolve(\"example\"))\n        );\n\n        assertThat(dependencies, hasSize(4));\n        assertThat(\n            dependencies,\n            containsInAnyOrder(\n                new Dependency(\"angular\", \"1.5.0\", \"MIT\"),\n                new Dependency(\"arangojs\", \"5.6.0\", \"Apache-2.0\"),\n                new Dependency(\"linkedlist\", \"1.0.1\", \"LGPLv3\"),\n                new Dependency(\"retry\", \"0.10.1\", \"MIT\")\n            )\n        );\n    }\n\n    @Test\n    public void testPackageJsonNotInBaseDir() throws Exception {\n        Set<Dependency> dependencies = createScanner().scan(\n            createContext(RESOURCE_FOLDER.resolve(\"example_nested\"))\n        );\n        assertThat(\n            dependencies,\n            containsInAnyOrder(\n                new Dependency(\"angular\", \"1.5.0\", \"MIT\"),\n                new Dependency(\"arangojs\", \"5.6.0\", \"Apache-2.0\")\n            )\n        );\n    }\n\n    @Test\n    public void testNoPackageJson() {\n        Set<Dependency> dependencies = createScanner().scan(\n            createContext(RESOURCE_FOLDER.resolve(\"no_package_json\"))\n        );\n\n        assertThat(dependencies, hasSize(0));\n    }\n\n    @Test\n    public void testNoNodeModules() {\n        Set<Dependency> dependencies = createScanner().scan(\n            createContext(RESOURCE_FOLDER.resolve(\"example/node_modules/arangojs\"))\n        );\n\n        assertThat(dependencies, hasSize(0));\n    }\n\n    @Test\n    public void testLicenseInDeprecatedLicenseFormat() {\n        final Set<Dependency> dependencies = createScanner().scan(\n            createContext(RESOURCE_FOLDER.resolve(\"deprecated_project\"))\n        );\n\n        assertEquals(1, dependencies.size());\n\n        final Dependency expectedDependency = new Dependency(\"dynamic-dedupe\", \"0.3.0\", \"MIT\");\n        assertEquals(expectedDependency, dependencies.toArray()[0]);\n    }\n\n    @Test\n    public void testLicenseInDeprecatedLicensesFormat() {\n        final Set<Dependency> dependencies = createScanner().scan(\n            createContext(RESOURCE_FOLDER.resolve(\"deprecated_multilicense_project\"))\n        );\n\n        assertEquals(1, dependencies.size());\n\n        final Dependency expectedDependency = new Dependency(\n            \"some-module\",\n            \"1.7.1\",\n            \"(MIT OR LGPLv3)\"\n        );\n        assertEquals(expectedDependency, dependencies.toArray()[0]);\n    }\n\n    private Scanner createScanner() {\n        return createScanner(false);\n    }\n\n    private Scanner createScanner(boolean resolveTransitiveDeps) {\n        LicenseMappingService licenseMappingService = mock(LicenseMappingService.class);\n        when(licenseMappingService.mapLicense(anyString())).thenCallRealMethod();\n        return new PackageJsonDependencyScanner(licenseMappingService, resolveTransitiveDeps);\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/ValidateLicensesTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck;\n\nimport static org.hamcrest.CoreMatchers.*;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.mockito.Mockito.*;\n\nimport at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMapping;\nimport at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMappingService;\nimport at.porscheinformatik.sonarqube.licensecheck.license.License;\nimport at.porscheinformatik.sonarqube.licensecheck.license.LicenseService;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.Set;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.sonar.api.batch.bootstrap.ProjectDefinition;\nimport org.sonar.api.batch.fs.internal.DefaultInputModule;\nimport org.sonar.api.batch.fs.internal.DefaultInputProject;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.batch.sensor.internal.SensorStorage;\nimport org.sonar.api.batch.sensor.issue.NewIssue;\nimport org.sonar.api.batch.sensor.issue.internal.DefaultIssue;\nimport org.sonar.api.scanner.fs.InputProject;\n\npublic class ValidateLicensesTest {\n\n    private static final License APACHE_LICENSE = new License(\"Apache-2.0\", \"Apache-2.0\", \"true\");\n    private ValidateLicenses validateLicenses;\n    private ProjectDefinition projectDefinition;\n    private InputProject module;\n    private DependencyMappingService dependencyMappingService;\n\n    @Before\n    public void setup() {\n        module = mock(InputProject.class);\n        when(module.key()).thenReturn(\"at.porscheinformatik.demo:demo\");\n        final LicenseService licenseService = mock(LicenseService.class);\n        when(licenseService.getLicenses(module)).thenReturn(\n            Arrays.asList(\n                new License(\"MIT\", \"MIT\", \"false\"),\n                new License(\"LGPL is fantastic\", \"LGPL\", \"true\"),\n                APACHE_LICENSE\n            )\n        );\n        dependencyMappingService = mock(DependencyMappingService.class);\n        validateLicenses = new ValidateLicenses(licenseService, dependencyMappingService);\n    }\n\n    private SensorContext createContext() {\n        SensorContext context = mock(SensorContext.class);\n        DefaultInputModule inputModule = mock(DefaultInputModule.class);\n        when(context.project()).thenReturn(module);\n        when(inputModule.definition()).thenReturn(projectDefinition);\n        when(context.module()).thenReturn(inputModule);\n        return context;\n    }\n\n    @Test\n    public void licenseNotAllowed() {\n        SensorContext context = createContext();\n        NewIssue issue = new DefaultIssue(\n            mock(DefaultInputProject.class),\n            mock(SensorStorage.class)\n        );\n        when(context.newIssue()).thenReturn(issue);\n\n        Dependency thing = new Dependency(\"thing\", \"1.0\", \"MIT\");\n        thing.setInputComponent(context.project());\n        validateLicenses.validateLicenses(deps(thing), context);\n\n        verify(context).newIssue();\n        assertThat(\n            issue.toString(),\n            containsString(LicenseCheckRulesDefinition.RULE_NOT_ALLOWED_LICENSE_KEY)\n        );\n    }\n\n    @Test\n    public void licenseAllowed() {\n        SensorContext context = createContext();\n\n        validateLicenses.validateLicenses(\n            deps(\n                new Dependency(\"thing\", \"1.0\", \"Apache-2.0\", LicenseCheckRulesDefinition.LANG_JS),\n                new Dependency(\"another\", \"2.0\", \"Apache-2.0\", LicenseCheckRulesDefinition.LANG_JS)\n            ),\n            context\n        );\n\n        verify(context, never()).newIssue();\n    }\n\n    @Test\n    public void licenseAllowed_scala() {\n        SensorContext context = createContext();\n\n        validateLicenses.validateLicenses(\n            deps(\n                new Dependency(\n                    \"thing\",\n                    \"1.0\",\n                    \"Apache-2.0\",\n                    LicenseCheckRulesDefinition.LANG_SCALA\n                ),\n                new Dependency(\n                    \"another\",\n                    \"2.0\",\n                    \"Apache-2.0\",\n                    LicenseCheckRulesDefinition.LANG_SCALA\n                )\n            ),\n            context\n        );\n\n        verify(context, never()).newIssue();\n    }\n\n    @Test\n    public void licenseAllowed_kotlin() {\n        SensorContext context = createContext();\n\n        validateLicenses.validateLicenses(\n            deps(\n                new Dependency(\n                    \"thing\",\n                    \"1.0\",\n                    \"Apache-2.0\",\n                    LicenseCheckRulesDefinition.LANG_KOTLIN\n                ),\n                new Dependency(\n                    \"another\",\n                    \"2.0\",\n                    \"Apache-2.0\",\n                    LicenseCheckRulesDefinition.LANG_KOTLIN\n                )\n            ),\n            context\n        );\n\n        verify(context, never()).newIssue();\n    }\n\n    //  (LGPL OR Apache-2.0) AND (LGPL OR Apache-2.0)\n    @Test\n    public void checkSpdxOrCombination() {\n        SensorContext context = createContext();\n\n        validateLicenses.validateLicenses(\n            deps(\n                new Dependency(\"another\", \"2.0\", \"(LGPL OR Apache-2.0)\"),\n                new Dependency(\"thing\", \"1.0\", \"(MIT OR Apache-2.0)\")\n            ),\n            context\n        );\n\n        verify(context, never()).newIssue();\n    }\n\n    @Test\n    public void checkSpdxSeveralOrCombination() {\n        SensorContext context = createContext();\n\n        validateLicenses.validateLicenses(\n            deps(new Dependency(\"thing\", \"1.0\", \"(Apache-2.0 OR MIT OR Apache-2.0 OR LGPL)\")),\n            context\n        );\n\n        verify(context, never()).newIssue();\n    }\n\n    @Test\n    public void checkSpdxAndCombination() {\n        SensorContext context = createContext();\n\n        validateLicenses.validateLicenses(\n            deps(new Dependency(\"thing\", \"1.0\", \"(LGPL AND Apache-2.0)\")),\n            context\n        );\n\n        verify(context, never()).newIssue();\n    }\n\n    @Test\n    public void checkSpdxAndCombinationNotAllowed() {\n        SensorContext context = createContext();\n        NewIssue issue = new DefaultIssue(\n            mock(DefaultInputProject.class),\n            mock(SensorStorage.class)\n        );\n        when(context.newIssue()).thenReturn(issue);\n\n        Dependency thing = new Dependency(\"thing\", \"1.0\", \"(Apache-2.0 AND MIT)\");\n        thing.setInputComponent(context.project());\n        validateLicenses.validateLicenses(\n            deps(\n                new Dependency(\"another\", \"2.0\", \"LGPL\", LicenseCheckRulesDefinition.LANG_JAVA),\n                thing\n            ),\n            context\n        );\n\n        verify(context).newIssue();\n        assertThat(\n            issue.toString(),\n            containsString(LicenseCheckRulesDefinition.RULE_NOT_ALLOWED_LICENSE_KEY)\n        );\n    }\n\n    @Test\n    public void checkSpdxAndCombinationNotFound() {\n        SensorContext context = createContext();\n        NewIssue issue = new DefaultIssue(\n            mock(DefaultInputProject.class),\n            mock(SensorStorage.class)\n        );\n        when(context.newIssue()).thenReturn(issue);\n\n        Dependency thing = new Dependency(\n            \"thing\",\n            \"1.0\",\n            \"(Apache-2.0 AND Apache-1.1)\",\n            LicenseCheckRulesDefinition.LANG_KOTLIN\n        );\n        thing.setInputComponent(context.project());\n        validateLicenses.validateLicenses(deps(thing), context);\n\n        verify(context).newIssue();\n        assertThat(issue.toString(), containsString(LicenseCheckRulesDefinition.RULE_UNLISTED_KEY));\n    }\n\n    //  LGPL OR Apache-2.0 AND MIT\n    @Test\n    public void checkSpdxOrAndCombination() {\n        SensorContext context = createContext();\n\n        validateLicenses.validateLicenses(\n            deps(new Dependency(\"thing\", \"1.0\", \"(LGPL OR (Apache-2.0 AND MIT))\")),\n            context\n        );\n\n        verify(context, never()).newIssue();\n    }\n\n    @Test\n    public void licenseNull() {\n        SensorContext context = createContext();\n        NewIssue issue = new DefaultIssue(\n            mock(DefaultInputProject.class),\n            mock(SensorStorage.class)\n        );\n        when(context.newIssue()).thenReturn(issue);\n\n        Dependency thing = new Dependency(\n            \"thing\",\n            \"1.0\",\n            null,\n            LicenseCheckRulesDefinition.LANG_JS\n        );\n        thing.setInputComponent(context.project());\n        validateLicenses.validateLicenses(deps(thing), context);\n\n        verify(context).newIssue();\n        assertThat(issue.toString(), containsString(LicenseCheckRulesDefinition.RULE_UNLISTED_KEY));\n    }\n\n    @Test\n    public void licenseUnknown() {\n        SensorContext context = createContext();\n        NewIssue issue = new DefaultIssue(\n            mock(DefaultInputProject.class),\n            mock(SensorStorage.class)\n        );\n        when(context.newIssue()).thenReturn(issue);\n\n        Dependency thing = new Dependency(\"thing\", \"1.0\", \"Mamamia\");\n        thing.setInputComponent(context.project());\n        validateLicenses.validateLicenses(deps(thing), context);\n\n        verify(context).newIssue();\n        assertThat(issue.toString(), containsString(LicenseCheckRulesDefinition.RULE_UNLISTED_KEY));\n    }\n\n    @Test\n    public void getUsedLicenses() {\n        assertThat(validateLicenses.getUsedLicenses(deps(), module).size(), is(0));\n\n        Set<License> usedLicensesApache = validateLicenses.getUsedLicenses(\n            deps(\n                new Dependency(\"thing\", \"1.0\", \"Apache-2.0\"),\n                new Dependency(\"another\", \"2.0\", \"Apache-2.0\")\n            ),\n            module\n        );\n\n        assertThat(usedLicensesApache.size(), is(1));\n        assertThat(usedLicensesApache, hasItem(APACHE_LICENSE));\n    }\n\n    @Test\n    public void dependencyMapping() {\n        when(dependencyMappingService.getDependencyMappings()).thenReturn(\n            Collections.singletonList(\n                new DependencyMapping(\"^thing$\", APACHE_LICENSE.getIdentifier(), false)\n            )\n        );\n\n        Set<Dependency> deps = validateLicenses.validateLicenses(\n            deps(new Dependency(\"thing\", \"1.0\", null)),\n            createContext()\n        );\n\n        assertThat(deps.size(), is(1));\n        assertThat(deps.iterator().next().getLicense(), equalTo(APACHE_LICENSE.getIdentifier()));\n    }\n\n    private static Set<Dependency> deps(Dependency... dependencies) {\n        final Set<Dependency> dependencySet = new HashSet<>();\n        Collections.addAll(dependencySet, dependencies);\n        return dependencySet;\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/dependencymapping/DependencyMappingServiceTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.dependencymapping;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPropertyKeys.DEPENDENCY_MAPPING;\nimport static at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMapping.FIELD_KEY;\nimport static at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMapping.FIELD_LICENSE;\nimport static at.porscheinformatik.sonarqube.licensecheck.dependencymapping.DependencyMapping.FIELD_OVERWRITE;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.mockito.ArgumentMatchers.any;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport java.util.List;\nimport java.util.Optional;\nimport org.junit.Test;\nimport org.sonar.api.config.Configuration;\n\npublic class DependencyMappingServiceTest {\n\n    @Test\n    public void loadConfiguration() {\n        Configuration configuration = mock(Configuration.class);\n        when(configuration.getStringArray(DEPENDENCY_MAPPING)).thenReturn(\n            new String[] { \"1\", \"2\" }\n        );\n        when(configuration.get(any())).thenReturn(Optional.empty());\n        when(configuration.getBoolean(any())).thenReturn(Optional.empty());\n        when(configuration.get(DEPENDENCY_MAPPING + \".1.\" + FIELD_KEY)).thenReturn(\n            Optional.of(\"kee\")\n        );\n        when(configuration.get(DEPENDENCY_MAPPING + \".1.\" + FIELD_LICENSE)).thenReturn(\n            Optional.of(\"MIT\")\n        );\n        when(configuration.getBoolean(DEPENDENCY_MAPPING + \".1.\" + FIELD_OVERWRITE)).thenReturn(\n            Optional.of(true)\n        );\n\n        DependencyMappingService dependencyMappingService = new DependencyMappingService(\n            configuration\n        );\n\n        List<DependencyMapping> dependencyMappings =\n            dependencyMappingService.getDependencyMappings();\n        assertThat(dependencyMappings.size(), is(2));\n        assertThat(dependencyMappings.get(0), is(new DependencyMapping(\"kee\", \"MIT\", true)));\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/gradle/GradleDependencyScannerTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.gradle;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.mockito.ArgumentMatchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport at.porscheinformatik.sonarqube.licensecheck.Dependency;\nimport at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPropertyKeys;\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;\nimport java.io.File;\nimport java.nio.file.Path;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.regex.Pattern;\nimport org.junit.Test;\nimport org.mockito.Mockito;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.sensor.SensorContext;\nimport org.sonar.api.config.Configuration;\n\npublic class GradleDependencyScannerTest {\n\n    private SensorContext createContext(File folder) {\n        SensorContext context = mock(SensorContext.class);\n        FileSystem fileSystem = mock(FileSystem.class);\n        Configuration config = mock(Configuration.class);\n        when(fileSystem.baseDir()).thenReturn(folder);\n        when(fileSystem.workDir()).thenReturn(folder);\n        when(config.get(anyString())).thenReturn(Optional.empty());\n        when(context.fileSystem()).thenReturn(fileSystem);\n        when(context.config()).thenReturn(config);\n        return context;\n    }\n\n    @Test\n    public void testScannerWithMissingJsonFile() {\n        GradleDependencyScanner scanner = new GradleDependencyScanner(mockLicenseService());\n        Set<Dependency> dependencies = scanner.scan(createContext(new File(\"/abc\")));\n        assertEquals(0, dependencies.size());\n    }\n\n    @Test\n    public void testScanner() {\n        GradleDependencyScanner scanner = new GradleDependencyScanner(mockLicenseService());\n        Path resourceDirectory = Paths.get(\"src\", \"test\", \"resources\");\n        String absolutePath = resourceDirectory.toFile().getAbsolutePath();\n\n        Set<Dependency> dependencies = scanner.scan(createContext(new File(absolutePath)));\n        assertEquals(43, dependencies.size());\n    }\n\n    @Test\n    public void testScannerWithConfiguredDirectory() {\n        GradleDependencyScanner scanner = new GradleDependencyScanner(mockLicenseService());\n        Path resourceDirectory = Paths.get(\"src\", \"test\", \"resources\");\n        String absolutePath = resourceDirectory.toFile().getAbsolutePath();\n\n        SensorContext context = createContext(new File(absolutePath));\n        when(context.config().get(LicenseCheckPropertyKeys.GRADLE_JSON_REPORT_PATH)).thenReturn(\n            Optional.of(\"build/my-reports/license-details.json\")\n        );\n        Set<Dependency> dependencies = scanner.scan(context);\n        assertEquals(43, dependencies.size());\n    }\n\n    private LicenseMappingService mockLicenseService() {\n        Map<Pattern, String> licenseMap = new HashMap<>();\n        licenseMap.put(Pattern.compile(\".*Apache.*2.*\"), \"Apache-2.0\");\n        LicenseMappingService licenseService = Mockito.mock(LicenseMappingService.class);\n        when(licenseService.getLicenseMap()).thenReturn(licenseMap);\n        return licenseService;\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/licensemapping/LicenseMappingServiceTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.licensemapping;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPropertyKeys.LICENSE_MAPPING;\nimport static at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMapping.FIELD_LICENSE;\nimport static at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMapping.FIELD_REGEX;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Map;\nimport java.util.Optional;\nimport java.util.regex.Pattern;\nimport org.junit.Test;\nimport org.sonar.api.config.Configuration;\n\npublic class LicenseMappingServiceTest {\n\n    @Test\n    public void getLicenseMap() {\n        LicenseMappingService service = createService();\n\n        Map<Pattern, String> licenseMap = service.getLicenseMap();\n\n        assertThat(licenseMap.size(), is(2));\n    }\n\n    @Test\n    public void mapLicense() {\n        LicenseMappingService service = createService();\n\n        String license = service.mapLicense(\"Apache Software License, 2.0\");\n\n        assertThat(license, is(\"ASL2\"));\n    }\n\n    private LicenseMappingService createService() {\n        Configuration configuration = mock(Configuration.class);\n        when(configuration.getStringArray(LICENSE_MAPPING)).thenReturn(new String[] { \"1\", \"2\" });\n        when(configuration.get(LICENSE_MAPPING + \".1.\" + FIELD_REGEX)).thenReturn(\n            Optional.of(\"MIT\")\n        );\n        when(configuration.get(LICENSE_MAPPING + \".1.\" + FIELD_LICENSE)).thenReturn(\n            Optional.of(\"MIT\")\n        );\n        when(configuration.get(LICENSE_MAPPING + \".2.\" + FIELD_REGEX)).thenReturn(\n            Optional.of(\"^Apache.*2.*$\")\n        );\n        when(configuration.get(LICENSE_MAPPING + \".2.\" + FIELD_LICENSE)).thenReturn(\n            Optional.of(\"ASL2\")\n        );\n        return new LicenseMappingService(configuration);\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/licensemapping/LicenseMappingTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.licensemapping;\n\nimport static org.hamcrest.CoreMatchers.hasItem;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.Matchers.lessThan;\n\nimport at.porscheinformatik.sonarqube.licensecheck.utils.IOUtils;\nimport java.io.IOException;\nimport java.util.HashSet;\nimport java.util.List;\nimport org.junit.Test;\n\npublic class LicenseMappingTest {\n\n    @Test\n    public void loadFromJson() throws IOException {\n        String licenseMappingListString = IOUtils.readToString(\n            LicenseMapping.class.getResourceAsStream(\"default_license_mapping.json\")\n        );\n        List<LicenseMapping> licenseMappings = LicenseMapping.fromString(licenseMappingListString);\n\n        assertThat(licenseMappings, hasItem(new LicenseMapping(\"Apple License\", \"AML\")));\n    }\n\n    @Test\n    public void compare() {\n        LicenseMapping lm = new LicenseMapping(\"b\", \"A\");\n        LicenseMapping lm2 = new LicenseMapping(\"a\", \"B\");\n        assertThat(lm.compareTo(lm2), lessThan(0));\n    }\n\n    @Test\n    public void hash() {\n        HashSet<LicenseMapping> set = new HashSet<>();\n        set.add(new LicenseMapping(\"x\", \"X\"));\n        set.add(new LicenseMapping(\"x\", \"X\"));\n\n        assertThat(set.size(), is(1));\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/maven/DependencyMappingScannerTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.maven;\n\nimport static org.hamcrest.CoreMatchers.endsWith;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport at.porscheinformatik.sonarqube.licensecheck.Dependency;\nimport at.porscheinformatik.sonarqube.licensecheck.Scanner;\nimport at.porscheinformatik.sonarqube.licensecheck.licensemapping.LicenseMappingService;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.nio.file.Files;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.regex.Pattern;\nimport org.hamcrest.Matchers;\nimport org.junit.Test;\nimport org.mockito.Mockito;\nimport org.sonar.api.batch.fs.FileSystem;\nimport org.sonar.api.batch.fs.InputFile;\nimport org.sonar.api.batch.fs.internal.DefaultFileSystem;\nimport org.sonar.api.batch.sensor.SensorContext;\n\npublic class DependencyMappingScannerTest {\n\n    @Test\n    public void testLicensesAreFound() {\n        File moduleDir = new File(\".\");\n\n        Scanner scanner = new MavenDependencyScanner(mockLicenseService());\n\n        // -\n        Set<Dependency> dependencies = scanner.scan(createContext(moduleDir));\n\n        assertThat(dependencies.size(), Matchers.greaterThan(0));\n\n        // -\n        for (Dependency dep : dependencies) {\n            if (\"org.apache.commons:commons-lang3\".equals(dep.getName())) {\n                assertThat(dep.getLicense(), is(\"Apache-2.0\"));\n            } else if (\"org.codehaus.plexus:plexus-utils\".equals(dep.getName())) {\n                assertThat(dep.getLicense(), is(\"Apache-2.0\"));\n            }\n        }\n    }\n\n    @Test\n    public void testNullMavenProjectDependencies() throws IOException {\n        LicenseMappingService licenseService = Mockito.mock(LicenseMappingService.class);\n        Scanner scanner = new MavenDependencyScanner(licenseService);\n\n        File moduleDir = Files.createTempDirectory(\"lala\").toFile();\n        moduleDir.deleteOnExit();\n        Set<Dependency> dependencies = scanner.scan(createContext(moduleDir));\n\n        assertThat(dependencies.size(), is(0));\n    }\n\n    @Test\n    public void testFindDependency() {\n        String jarFilePath = new File(\"src/test/resources/test.jar\").getAbsolutePath();\n        Dependency dependency = MavenDependencyScanner.findDependency(\n            \"at.porscheinformatik.test:test:jar:1.2.3:compile:\" +\n                jarFilePath +\n                \" -- module test (auto)\"\n        );\n\n        assertThat(dependency.getName(), is(\"at.porscheinformatik.test:test\"));\n        assertThat(dependency.getVersion(), is(\"1.2.3\"));\n        assertThat(dependency.getPomPath(), endsWith(\"test.pom\"));\n    }\n\n    @Test\n    public void testFindDependencyWithClassifier() {\n        String jarFilePath = new File(\"src/test/resources/test-sources.jar\").getAbsolutePath();\n        Dependency dependency = MavenDependencyScanner.findDependency(\n            \"at.porscheinformatik.test:test:jar:sources:2.2:compile:\" +\n                jarFilePath +\n                \" -- module test (auto)\"\n        );\n\n        assertThat(dependency.getName(), is(\"at.porscheinformatik.test:test\"));\n        assertThat(dependency.getVersion(), is(\"2.2\"));\n        assertThat(dependency.getPomPath(), endsWith(\"test.pom\"));\n    }\n\n    @Test\n    public void testSettingsFromMaven() {\n        String oldVal = System.getProperty(\"sun.java.command\");\n        try {\n            System.setProperty(\n                \"sun.java.command\",\n                \"thisisatest -X -s src/test/resources/settings-does-not-exist.xml -gs src/test/resources/settings-does-not-exist.xml -B\"\n            );\n            Scanner scanner = new MavenDependencyScanner(mockLicenseService());\n\n            Set<Dependency> dependencies = scanner.scan(createContext(new File(\".\")));\n            assertThat(dependencies.size(), is(0));\n        } finally {\n            System.setProperty(\"sun.java.command\", oldVal);\n        }\n    }\n\n    private SensorContext createContext(File moduleDir) {\n        SensorContext context = mock(SensorContext.class);\n        InputFile pomXml = mock(InputFile.class);\n        when(pomXml.language()).thenReturn(\"xml\");\n        when(pomXml.filename()).thenReturn(\"pom.xml\");\n        when(pomXml.uri()).thenReturn(new File(moduleDir, \"pom.xml\").toURI());\n        when(pomXml.relativePath()).thenReturn(\"/pom.xml\");\n        when(pomXml.type()).thenReturn(InputFile.Type.MAIN);\n        try {\n            when(pomXml.inputStream()).thenAnswer(i ->\n                new FileInputStream(new File(moduleDir, \"pom.xml\"))\n            );\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n        FileSystem fileSystem = new DefaultFileSystem(moduleDir.toPath()).add(pomXml);\n        when(context.fileSystem()).thenReturn(fileSystem);\n\n        return context;\n    }\n\n    private LicenseMappingService mockLicenseService() {\n        Map<Pattern, String> licenseMap = new HashMap<>();\n        licenseMap.put(Pattern.compile(\".*Apache.*2.*\"), \"Apache-2.0\");\n        LicenseMappingService licenseService = Mockito.mock(LicenseMappingService.class);\n        when(licenseService.getLicenseMap()).thenReturn(licenseMap);\n        return licenseService;\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/maven/DirectoryFinderTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.maven;\n\nimport static org.junit.Assert.assertEquals;\n\nimport java.io.File;\nimport org.junit.Test;\n\npublic class DirectoryFinderTest {\n\n    @Test\n    public void checkMavenRepoLocal() {\n        String mavenRepoLocalOld = System.getProperty(\"maven.repo.local\");\n        System.setProperty(\"maven.repo.local\", \"/tmp/maven-repo\");\n        File repoDir = DirectoryFinder.getMavenRepositoryDir(null, null);\n        if (mavenRepoLocalOld != null) {\n            System.setProperty(\"maven.repo.local\", mavenRepoLocalOld);\n        } else {\n            System.clearProperty(\"maven.repo.local\");\n        }\n        assertEquals(repoDir.getAbsolutePath(), \"/tmp/maven-repo\");\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/projectlicense/ProjectLicenseTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.projectlicense;\n\nimport static at.porscheinformatik.sonarqube.licensecheck.LicenseCheckPropertyKeys.PROJECT_LICENSE_SET;\nimport static at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicense.FIELD_ALLOWED;\nimport static at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicense.FIELD_LICENSE;\nimport static at.porscheinformatik.sonarqube.licensecheck.projectlicense.ProjectLicense.FIELD_PROJECT_KEY;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Optional;\nimport org.junit.Test;\nimport org.sonar.api.config.Configuration;\n\npublic class ProjectLicenseTest {\n\n    @Test\n    public void getAllProjectLicenses() {\n        List<ProjectLicense> projectLicenseList =\n            createProjectLicenseService().getProjectLicenseList();\n\n        assertThat(projectLicenseList.size(), is(2));\n    }\n\n    @Test\n    public void getSpecificProjectLicenses() {\n        Collection<ProjectLicense> projectLicenseList =\n            createProjectLicenseService().getProjectLicenseList(\"proj1\");\n\n        assertThat(projectLicenseList.size(), is(1));\n        assertThat(projectLicenseList.iterator().next().getLicense(), is(\"MIT\"));\n    }\n\n    private ProjectLicenseService createProjectLicenseService() {\n        Configuration configuration = mock(Configuration.class);\n        when(configuration.getStringArray(PROJECT_LICENSE_SET)).thenReturn(\n            new String[] { \"1\", \"2\" }\n        );\n        when(configuration.get(PROJECT_LICENSE_SET + \".1.\" + FIELD_PROJECT_KEY)).thenReturn(\n            Optional.of(\"proj1\")\n        );\n        when(configuration.get(PROJECT_LICENSE_SET + \".2.\" + FIELD_PROJECT_KEY)).thenReturn(\n            Optional.of(\"proj2\")\n        );\n        when(configuration.get(PROJECT_LICENSE_SET + \".1.\" + FIELD_LICENSE)).thenReturn(\n            Optional.of(\"MIT\")\n        );\n        when(configuration.get(PROJECT_LICENSE_SET + \".2.\" + FIELD_LICENSE)).thenReturn(\n            Optional.of(\"BSD\")\n        );\n        when(configuration.getBoolean(PROJECT_LICENSE_SET + \".1.\" + FIELD_ALLOWED)).thenReturn(\n            Optional.of(false)\n        );\n        when(configuration.getBoolean(PROJECT_LICENSE_SET + \".2.\" + FIELD_ALLOWED)).thenReturn(\n            Optional.of(true)\n        );\n        return new ProjectLicenseService(configuration);\n    }\n}\n"
  },
  {
    "path": "src/test/java/at/porscheinformatik/sonarqube/licensecheck/utils/IOUtilsTest.java",
    "content": "package at.porscheinformatik.sonarqube.licensecheck.utils;\n\nimport static org.hamcrest.CoreMatchers.containsString;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\nimport java.io.IOException;\nimport org.junit.Test;\n\npublic class IOUtilsTest {\n\n    @Test\n    public void testLoadFile() throws IOException {\n        String packageJson = IOUtils.readToString(\n            IOUtilsTest.class.getResourceAsStream(\"/example/package.json\")\n        );\n        assertThat(packageJson, containsString(\"\\\"name\\\": \\\"test\\\"\"));\n    }\n}\n"
  },
  {
    "path": "src/test/resources/build/my-reports/license-details.json",
    "content": "{\n  \"dependencies\": [\n    {\n      \"moduleName\": \"ch.qos.logback:logback-classic\",\n      \"moduleVersion\": \"1.2.3\",\n      \"moduleUrls\": [\"http://www.qos.ch\"]\n    },\n    {\n      \"moduleName\": \"ch.qos.logback:logback-core\",\n      \"moduleVersion\": \"1.2.3\",\n      \"moduleUrls\": [\"http://www.qos.ch\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-v10.html, http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License - v 1.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-v10.html\"\n        },\n        {\n          \"moduleLicense\": \"GNU Lesser General Public License\",\n          \"moduleLicenseUrl\": \"http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.core:jackson-annotations\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"http://github.com/FasterXML/jackson\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.core:jackson-core\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"https://github.com/FasterXML/jackson-core\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.core:jackson-databind\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"http://github.com/FasterXML/jackson\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.datatype:jackson-datatype-jdk8\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.module:jackson-module-parameter-names\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\n        \"https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml:classmate\",\n      \"moduleVersion\": \"1.5.1\",\n      \"moduleUrls\": [\"https://github.com/FasterXML/java-classmate\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"jakarta.annotation:jakarta.annotation-api\",\n      \"moduleVersion\": \"1.3.5\",\n      \"moduleUrls\": [\"https://projects.eclipse.org/projects/ee4j.ca\", \"https://www.eclipse.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"jakarta.validation:jakarta.validation-api\",\n      \"moduleVersion\": \"2.0.2\",\n      \"moduleUrls\": [\"https://beanvalidation.org\", \"https://www.eclipse.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"jakarta.ws.rs:jakarta.ws.rs-api\",\n      \"moduleVersion\": \"2.1.6\",\n      \"moduleUrls\": [\n        \"https://github.com/eclipse-ee4j/jaxrs-api\",\n        \"https://www.eclipse.org/org/foundation/\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.logging.log4j:log4j-api\",\n      \"moduleVersion\": \"2.12.1\",\n      \"moduleUrls\": [\"https://www.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.logging.log4j:log4j-to-slf4j\",\n      \"moduleVersion\": \"2.12.1\",\n      \"moduleUrls\": [\"https://www.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.tomcat.embed:tomcat-embed-core\",\n      \"moduleVersion\": \"9.0.36\",\n      \"moduleUrls\": [\"https://tomcat.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0\",\n          \"moduleLicenseUrl\": \"https://opensource.org/licenses/CDDL-1.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.tomcat.embed:tomcat-embed-el\",\n      \"moduleVersion\": \"9.0.36\",\n      \"moduleUrls\": [\"https://tomcat.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.tomcat.embed:tomcat-embed-websocket\",\n      \"moduleVersion\": \"9.0.36\",\n      \"moduleUrls\": [\"https://tomcat.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.glassfish.hk2.external:jakarta.inject\",\n      \"moduleVersion\": \"2.6.1\",\n      \"moduleUrls\": [\"http://www.oracle.com\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.glassfish.hk2:osgi-resource-locator\",\n      \"moduleVersion\": \"1.0.3\",\n      \"moduleUrls\": [\"https://www.eclipse.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.glassfish.jersey.core:jersey-client\",\n      \"moduleVersion\": \"2.29.1\",\n      \"moduleUrls\": [\"https://www.eclipse.org/org/foundation/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html, http://www.eclipse.org/org/documents/edl-v10.php, https://opensource.org/licenses/BSD-2-Clause, http://www.apache.org/licenses/LICENSE-2.0.html, https://creativecommons.org/publicdomain/zero/1.0/, http://asm.objectweb.org/license.html, jquery.org/license, http://www.opensource.org/licenses/mit-license.php, https://www.w3.org/Consortium/Legal/copyright-documents-19990405\"\n        },\n        {\n          \"moduleLicense\": \"Apache License, 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"\n        },\n        {\n          \"moduleLicense\": \"BSD 2-Clause\",\n          \"moduleLicenseUrl\": \"https://opensource.org/licenses/BSD-2-Clause\"\n        },\n        {\n          \"moduleLicense\": \"EDL 1.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/org/documents/edl-v10.php\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"MIT license\",\n          \"moduleLicenseUrl\": \"http://www.opensource.org/licenses/mit-license.php\"\n        },\n        {\n          \"moduleLicense\": \"Modified BSD\",\n          \"moduleLicenseUrl\": \"http://asm.objectweb.org/license.html\"\n        },\n        {\n          \"moduleLicense\": \"Public Domain\",\n          \"moduleLicenseUrl\": \"https://creativecommons.org/publicdomain/zero/1.0/\"\n        },\n        {\n          \"moduleLicense\": \"W3C license\",\n          \"moduleLicenseUrl\": \"https://www.w3.org/Consortium/Legal/copyright-documents-19990405\"\n        },\n        {\n          \"moduleLicense\": \"jQuery license\",\n          \"moduleLicenseUrl\": \"jquery.org/license\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.glassfish.jersey.core:jersey-common\",\n      \"moduleVersion\": \"2.29.1\",\n      \"moduleUrls\": [\"https://www.eclipse.org/org/foundation/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html, http://www.apache.org/licenses/LICENSE-2.0.html, https://creativecommons.org/publicdomain/zero/1.0/\"\n        },\n        {\n          \"moduleLicense\": \"Apache License, 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"\n        },\n        {\n          \"moduleLicense\": \"BSD 2-Clause\",\n          \"moduleLicenseUrl\": \"https://opensource.org/licenses/BSD-2-Clause\"\n        },\n        {\n          \"moduleLicense\": \"EDL 1.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/org/documents/edl-v10.php\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"MIT license\",\n          \"moduleLicenseUrl\": \"http://www.opensource.org/licenses/mit-license.php\"\n        },\n        {\n          \"moduleLicense\": \"Modified BSD\",\n          \"moduleLicenseUrl\": \"http://asm.objectweb.org/license.html\"\n        },\n        {\n          \"moduleLicense\": \"Public Domain\",\n          \"moduleLicenseUrl\": \"https://creativecommons.org/publicdomain/zero/1.0/\"\n        },\n        {\n          \"moduleLicense\": \"The GNU General Public License (GPL), Version 2, With Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"W3C license\",\n          \"moduleLicenseUrl\": \"https://www.w3.org/Consortium/Legal/copyright-documents-19990405\"\n        },\n        {\n          \"moduleLicense\": \"jQuery license\",\n          \"moduleLicenseUrl\": \"jquery.org/license\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.hibernate.validator:hibernate-validator\",\n      \"moduleVersion\": \"6.0.20.Final\",\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.jboss.logging:jboss-logging\",\n      \"moduleVersion\": \"3.4.1.Final\",\n      \"moduleUrls\": [\"http://www.jboss.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Apache License, version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"Public Domain\",\n          \"moduleLicenseUrl\": \"http://repository.jboss.org/licenses/cc0-1.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.slf4j:jul-to-slf4j\",\n      \"moduleVersion\": \"1.7.30\",\n      \"moduleUrls\": [\"http://www.slf4j.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"MIT License\",\n          \"moduleLicenseUrl\": \"http://www.opensource.org/licenses/mit-license.php\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.slf4j:slf4j-api\",\n      \"moduleVersion\": \"1.7.30\",\n      \"moduleUrls\": [\"http://www.slf4j.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"MIT License\",\n          \"moduleLicenseUrl\": \"http://www.opensource.org/licenses/mit-license.php\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-autoconfigure\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-autoconfigure\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-devtools\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-devtools\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-json\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-json\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-logging\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-logging\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-tomcat\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-tomcat\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-validation\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-validation\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-web\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-web\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-aop\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-beans\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-context\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-core\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-expression\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-jcl\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-web\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-webmvc\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.yaml:snakeyaml\",\n      \"moduleVersion\": \"1.25\",\n      \"moduleUrls\": [\"http://www.snakeyaml.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/build/reports/dependency-license/license-details.json",
    "content": "{\n  \"dependencies\": [\n    {\n      \"moduleName\": \"ch.qos.logback:logback-classic\",\n      \"moduleVersion\": \"1.2.3\",\n      \"moduleUrls\": [\"http://www.qos.ch\"]\n    },\n    {\n      \"moduleName\": \"ch.qos.logback:logback-core\",\n      \"moduleVersion\": \"1.2.3\",\n      \"moduleUrls\": [\"http://www.qos.ch\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-v10.html, http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License - v 1.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-v10.html\"\n        },\n        {\n          \"moduleLicense\": \"GNU Lesser General Public License\",\n          \"moduleLicenseUrl\": \"http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.core:jackson-annotations\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"http://github.com/FasterXML/jackson\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.core:jackson-core\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"https://github.com/FasterXML/jackson-core\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.core:jackson-databind\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"http://github.com/FasterXML/jackson\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.datatype:jackson-datatype-jdk8\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.datatype:jackson-datatype-jsr310\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\"https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml.jackson.module:jackson-module-parameter-names\",\n      \"moduleVersion\": \"2.10.4\",\n      \"moduleUrls\": [\n        \"https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"com.fasterxml:classmate\",\n      \"moduleVersion\": \"1.5.1\",\n      \"moduleUrls\": [\"https://github.com/FasterXML/java-classmate\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"The Apache Software License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"jakarta.annotation:jakarta.annotation-api\",\n      \"moduleVersion\": \"1.3.5\",\n      \"moduleUrls\": [\"https://projects.eclipse.org/projects/ee4j.ca\", \"https://www.eclipse.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"jakarta.validation:jakarta.validation-api\",\n      \"moduleVersion\": \"2.0.2\",\n      \"moduleUrls\": [\"https://beanvalidation.org\", \"https://www.eclipse.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"jakarta.ws.rs:jakarta.ws.rs-api\",\n      \"moduleVersion\": \"2.1.6\",\n      \"moduleUrls\": [\n        \"https://github.com/eclipse-ee4j/jaxrs-api\",\n        \"https://www.eclipse.org/org/foundation/\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.logging.log4j:log4j-api\",\n      \"moduleVersion\": \"2.12.1\",\n      \"moduleUrls\": [\"https://www.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.logging.log4j:log4j-to-slf4j\",\n      \"moduleVersion\": \"2.12.1\",\n      \"moduleUrls\": [\"https://www.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.tomcat.embed:tomcat-embed-core\",\n      \"moduleVersion\": \"9.0.36\",\n      \"moduleUrls\": [\"https://tomcat.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0\",\n          \"moduleLicenseUrl\": \"https://opensource.org/licenses/CDDL-1.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.tomcat.embed:tomcat-embed-el\",\n      \"moduleVersion\": \"9.0.36\",\n      \"moduleUrls\": [\"https://tomcat.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.apache.tomcat.embed:tomcat-embed-websocket\",\n      \"moduleVersion\": \"9.0.36\",\n      \"moduleUrls\": [\"https://tomcat.apache.org/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.glassfish.hk2.external:jakarta.inject\",\n      \"moduleVersion\": \"2.6.1\",\n      \"moduleUrls\": [\"http://www.oracle.com\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.glassfish.hk2:osgi-resource-locator\",\n      \"moduleVersion\": \"1.0.3\",\n      \"moduleUrls\": [\"https://www.eclipse.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.glassfish.jersey.core:jersey-client\",\n      \"moduleVersion\": \"2.29.1\",\n      \"moduleUrls\": [\"https://www.eclipse.org/org/foundation/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html, http://www.eclipse.org/org/documents/edl-v10.php, https://opensource.org/licenses/BSD-2-Clause, http://www.apache.org/licenses/LICENSE-2.0.html, https://creativecommons.org/publicdomain/zero/1.0/, http://asm.objectweb.org/license.html, jquery.org/license, http://www.opensource.org/licenses/mit-license.php, https://www.w3.org/Consortium/Legal/copyright-documents-19990405\"\n        },\n        {\n          \"moduleLicense\": \"Apache License, 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"\n        },\n        {\n          \"moduleLicense\": \"BSD 2-Clause\",\n          \"moduleLicenseUrl\": \"https://opensource.org/licenses/BSD-2-Clause\"\n        },\n        {\n          \"moduleLicense\": \"EDL 1.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/org/documents/edl-v10.php\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"MIT license\",\n          \"moduleLicenseUrl\": \"http://www.opensource.org/licenses/mit-license.php\"\n        },\n        {\n          \"moduleLicense\": \"Modified BSD\",\n          \"moduleLicenseUrl\": \"http://asm.objectweb.org/license.html\"\n        },\n        {\n          \"moduleLicense\": \"Public Domain\",\n          \"moduleLicenseUrl\": \"https://creativecommons.org/publicdomain/zero/1.0/\"\n        },\n        {\n          \"moduleLicense\": \"W3C license\",\n          \"moduleLicenseUrl\": \"https://www.w3.org/Consortium/Legal/copyright-documents-19990405\"\n        },\n        {\n          \"moduleLicense\": \"jQuery license\",\n          \"moduleLicenseUrl\": \"jquery.org/license\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.glassfish.jersey.core:jersey-common\",\n      \"moduleVersion\": \"2.29.1\",\n      \"moduleUrls\": [\"https://www.eclipse.org/org/foundation/\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": null,\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0, https://www.gnu.org/software/classpath/license.html, http://www.apache.org/licenses/LICENSE-2.0.html, https://creativecommons.org/publicdomain/zero/1.0/\"\n        },\n        {\n          \"moduleLicense\": \"Apache License, 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"\n        },\n        {\n          \"moduleLicense\": \"BSD 2-Clause\",\n          \"moduleLicenseUrl\": \"https://opensource.org/licenses/BSD-2-Clause\"\n        },\n        {\n          \"moduleLicense\": \"EDL 1.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/org/documents/edl-v10.php\"\n        },\n        {\n          \"moduleLicense\": \"EPL 2.0\",\n          \"moduleLicenseUrl\": \"http://www.eclipse.org/legal/epl-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Eclipse Public License v. 2.0\",\n          \"moduleLicenseUrl\": \"https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"GNU General Public License, version 2 with the GNU Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"GPL2 w/ CPE\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"MIT license\",\n          \"moduleLicenseUrl\": \"http://www.opensource.org/licenses/mit-license.php\"\n        },\n        {\n          \"moduleLicense\": \"Modified BSD\",\n          \"moduleLicenseUrl\": \"http://asm.objectweb.org/license.html\"\n        },\n        {\n          \"moduleLicense\": \"Public Domain\",\n          \"moduleLicenseUrl\": \"https://creativecommons.org/publicdomain/zero/1.0/\"\n        },\n        {\n          \"moduleLicense\": \"The GNU General Public License (GPL), Version 2, With Classpath Exception\",\n          \"moduleLicenseUrl\": \"https://www.gnu.org/software/classpath/license.html\"\n        },\n        {\n          \"moduleLicense\": \"W3C license\",\n          \"moduleLicenseUrl\": \"https://www.w3.org/Consortium/Legal/copyright-documents-19990405\"\n        },\n        {\n          \"moduleLicense\": \"jQuery license\",\n          \"moduleLicenseUrl\": \"jquery.org/license\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.hibernate.validator:hibernate-validator\",\n      \"moduleVersion\": \"6.0.20.Final\",\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.jboss.logging:jboss-logging\",\n      \"moduleVersion\": \"3.4.1.Final\",\n      \"moduleUrls\": [\"http://www.jboss.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        },\n        {\n          \"moduleLicense\": \"Apache License, version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        },\n        {\n          \"moduleLicense\": \"Public Domain\",\n          \"moduleLicenseUrl\": \"http://repository.jboss.org/licenses/cc0-1.0.txt\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.slf4j:jul-to-slf4j\",\n      \"moduleVersion\": \"1.7.30\",\n      \"moduleUrls\": [\"http://www.slf4j.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"MIT License\",\n          \"moduleLicenseUrl\": \"http://www.opensource.org/licenses/mit-license.php\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.slf4j:slf4j-api\",\n      \"moduleVersion\": \"1.7.30\",\n      \"moduleUrls\": [\"http://www.slf4j.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"MIT License\",\n          \"moduleLicenseUrl\": \"http://www.opensource.org/licenses/mit-license.php\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-autoconfigure\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-autoconfigure\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-devtools\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-devtools\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-json\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-json\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-logging\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-logging\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-tomcat\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-tomcat\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-validation\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-validation\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework.boot:spring-boot-starter-web\",\n      \"moduleVersion\": \"2.2.8.RELEASE\",\n      \"moduleUrls\": [\n        \"https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-starters/spring-boot-starter-web\"\n      ],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-aop\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-beans\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-context\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-core\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-expression\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-jcl\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-web\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.springframework:spring-webmvc\",\n      \"moduleVersion\": \"5.2.7.RELEASE\",\n      \"moduleUrls\": [\"https://github.com/spring-projects/spring-framework\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"https://www.apache.org/licenses/LICENSE-2.0\"\n        }\n      ]\n    },\n    {\n      \"moduleName\": \"org.yaml:snakeyaml\",\n      \"moduleVersion\": \"1.25\",\n      \"moduleUrls\": [\"http://www.snakeyaml.org\"],\n      \"moduleLicenses\": [\n        {\n          \"moduleLicense\": \"Apache License, Version 2.0\",\n          \"moduleLicenseUrl\": \"http://www.apache.org/licenses/LICENSE-2.0.txt\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "src/test/resources/deprecated_multilicense_project/package.json",
    "content": "{\n  \"name\": \"test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"An example module to illustrate the usage of a package.json\",\n  \"author\": \"Your Name <you.name@example.org>\",\n  \"dependencies\": {\n    \"some-module\": \"^1.7.1\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "src/test/resources/deprecated_project/package.json",
    "content": "{\n  \"name\": \"test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"An example module to illustrate the usage of a package.json\",\n  \"author\": \"Your Name <you.name@example.org>\",\n  \"dependencies\": {\n    \"dynamic-dedupe\": \"^0.3.0\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "src/test/resources/example/package.json",
    "content": "{\n  \"name\": \"test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"An example module to illustrate the usage of a package.json\",\n  \"author\": \"Your Name <you.name@example.org>\",\n  \"dependencies\": {\n    \"angular\": \"^1.4.3\",\n    \"angular-ui-router\": \"~0.2.18\",\n    \"angular-ui-bootstrap\": \"1.1.2\",\n    \"arangojs\": \"5.6.0\"\n  },\n  \"devDependencies\": {\n    \"gulp\": \"^3.9.1\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "src/test/resources/example_nested/example/package.json",
    "content": "{\n  \"name\": \"test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"An example module to illustrate the usage of a package.json\",\n  \"author\": \"Your Name <you.name@example.org>\",\n  \"dependencies\": {\n    \"angular\": \"^1.4.3\",\n    \"angular-ui-router\": \"~0.2.18\",\n    \"angular-ui-bootstrap\": \"1.1.2\",\n    \"arangojs\": \"5.6.0\"\n  },\n  \"devDependencies\": {\n    \"gulp\": \"^3.9.1\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "src/test/resources/no_package_json/.gitkeep",
    "content": ""
  },
  {
    "path": "src/test/resources/spdc_license_list.json",
    "content": "{\n  \"Glide\": {\n    \"name\": \"3dfx Glide License\",\n    \"url\": \"http://www.users.on.net/~triforce/glidexp/COPYING.txt\",\n    \"osiApproved\": false\n  },\n  \"Abstyles\": {\n    \"name\": \"Abstyles License\",\n    \"url\": \"https://fedoraproject.org/wiki/Licensing/Abstyles\",\n    \"osiApproved\": false\n  },\n  \"AFL-1.1\": {\n    \"name\": \"Academic Free License v1.1\",\n    \"url\": \"http://opensource.linux-mirror.org/licenses/afl-1.1.txt\",\n    \"osiApproved\": true\n  },\n  \"AFL-1.2\": {\n    \"name\": \"Academic Free License v1.2\",\n    \"url\": \"http://opensource.linux-mirror.org/licenses/afl-1.2.txt\",\n    \"osiApproved\": true\n  },\n  \"ZPL-2.1\": {\n    \"name\": \"Zope Public License 2.1\",\n    \"url\": \"http://old.zope.org/Resources/ZPL/\",\n    \"osiApproved\": false\n  }\n}\n"
  },
  {
    "path": "src/test/resources/test.pom",
    "content": "EMPTY\n"
  },
  {
    "path": "test-server.js",
    "content": "const http = require(\"http\"),\n  httpProxy = require(\"http-proxy\"),\n  fs = require(\"fs\");\nconst proxy = httpProxy.createProxyServer({});\n\n// Setup proxy to remove content-security-policy header from responses\nproxy.on(\"proxyRes\", function (proxyRes, req, res) {\n  delete proxyRes.headers[\"content-security-policy\"];\n});\n\nconst server = http.createServer(function (req, res) {\n  let match = req.url.match(/\\/static\\/licensecheck\\/(\\w+).js/);\n  if (match) {\n    res.writeHead(200, { \"Content-Type\": \"application/javascript\" });\n    fs.readFile(`./target/classes/static/${match[1]}.js`, \"utf8\", function (err, data) {\n      if (err) {\n        return console.log(err);\n      }\n      res.write(data);\n      res.end();\n    });\n  } else {\n    proxy.web(req, res, { target: \"http://127.0.0.1:9000\" });\n  }\n});\nconsole.log(\"listening on port 5050\");\nserver.listen(5050);\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require(\"path\");\n\nmodule.exports = {\n  entry: {\n    configuration: \"./src/main/web/configuration.js\",\n    dashboard: \"./src/main/web/dashboard.js\",\n  },\n  output: {\n    path: path.resolve(__dirname, \"target/classes/static/\"),\n    filename: \"[name].js\",\n  },\n  resolve: {\n    extensions: [\".js\", \".jsx\", \".json\"],\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|jsx)$/,\n        exclude: /node_modules/,\n        use: {\n          loader: \"babel-loader\",\n          options: {\n            presets: [\"@babel/preset-env\", \"@babel/preset-react\"],\n          },\n        },\n      },\n      {\n        test: /\\.css$/,\n        use: [\"style-loader\", \"css-loader\"],\n      },\n    ],\n  },\n};\n"
  }
]