├── .gitattributes ├── ballerina ├── icon.png ├── CompilerPlugin.toml ├── Ballerina.toml ├── cache_errors.bal ├── README.md ├── Dependencies.toml ├── abstract_cache.bal ├── build.gradle ├── cache.bal └── tests │ └── test.bal ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── load-tests └── order_management_service │ ├── deployment │ ├── kustomization.yaml │ └── ingress.yaml │ ├── src │ ├── Ballerina.toml │ ├── Cloud.toml │ ├── resources │ │ └── order_service │ │ │ ├── public.crt │ │ │ └── private.key │ ├── order_service.bal │ └── client_service.bal │ └── scripts │ ├── run.sh │ └── http-post-request.jmx ├── compiler-plugin-tests ├── src │ └── test │ │ ├── resources │ │ ├── diagnostics │ │ │ ├── sample1 │ │ │ │ ├── Ballerina.toml │ │ │ │ └── main.bal │ │ │ ├── sample2 │ │ │ │ ├── Ballerina.toml │ │ │ │ └── main.bal │ │ │ ├── sample3 │ │ │ │ ├── Ballerina.toml │ │ │ │ └── main.bal │ │ │ ├── sample4 │ │ │ │ ├── Ballerina.toml │ │ │ │ └── main.bal │ │ │ ├── sample5 │ │ │ │ ├── Ballerina.toml │ │ │ │ └── main.bal │ │ │ ├── sample6 │ │ │ │ ├── Ballerina.toml │ │ │ │ └── modules │ │ │ │ │ ├── config │ │ │ │ │ └── config.bal │ │ │ │ │ └── code │ │ │ │ │ └── code.bal │ │ │ └── sample7 │ │ │ │ ├── Ballerina.toml │ │ │ │ └── main.bal │ │ └── testng.xml │ │ └── java │ │ └── io │ │ └── ballerina │ │ └── stdlib │ │ └── cache │ │ └── compiler │ │ └── CompilerPluginTest.java └── build.gradle ├── codecov.yml ├── .github ├── pull_request_template.md ├── CODEOWNERS └── workflows │ ├── trivy-scan.yml │ ├── pull-request.yml │ ├── build-timestamped-master.yml │ ├── publish-release.yml │ ├── fossa_scan.yml │ ├── process-load-test-result.yml │ ├── central-publish.yml │ ├── trigger-load-tests.yml │ ├── build-with-bal-test-graalvm.yml │ └── update_specs.yml ├── gradle.properties ├── .gitignore ├── spotbugs-exclude.xml ├── native ├── src │ ├── main │ │ └── java │ │ │ ├── module-info.java │ │ │ └── io │ │ │ └── ballerina │ │ │ └── stdlib │ │ │ └── cache │ │ │ └── nativeimpl │ │ │ ├── concurrentlinkedhashmap │ │ │ ├── Weigher.java │ │ │ ├── Linked.java │ │ │ ├── Weighers.java │ │ │ ├── LinkedDeque.java │ │ │ └── ConcurrentLinkedHashMap.java │ │ │ └── Cache.java │ └── test │ │ ├── resources │ │ └── testng.xml │ │ └── java │ │ └── io │ │ └── ballerina │ │ └── stdlib │ │ └── cache │ │ └── CacheTest.java ├── spotbugs-exclude.xml └── build.gradle ├── compiler-plugin ├── src │ └── main │ │ └── java │ │ ├── module-info.java │ │ └── io │ │ └── ballerina │ │ └── stdlib │ │ └── cache │ │ └── compiler │ │ ├── CacheCompilerPlugin.java │ │ ├── CacheCodeAnalyzer.java │ │ ├── Constants.java │ │ ├── DiagnosticsCodes.java │ │ └── CacheConfigValidator.java └── build.gradle ├── changelog.md ├── settings.gradle ├── gradlew.bat ├── docs └── spec │ └── spec.md ├── README.md ├── gradlew └── LICENSE /.gitattributes: -------------------------------------------------------------------------------- 1 | # Ensure all Java files use LF. 2 | *.java eol=lf 3 | -------------------------------------------------------------------------------- /ballerina/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerina-cache/HEAD/ballerina/icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ballerina-platform/module-ballerina-cache/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /load-tests/order_management_service/deployment/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - order_management_service.yaml 3 | - ingress.yaml 4 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample1/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "cache_test" 3 | name = "sample1" 4 | version = "0.1.0" 5 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample2/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "cache_test" 3 | name = "sample2" 4 | version = "0.1.0" 5 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample3/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "cache_test" 3 | name = "sample3" 4 | version = "0.1.0" 5 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample4/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "cache_test" 3 | name = "sample4" 4 | version = "0.1.0" 5 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample5/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "cache_test" 3 | name = "sample5" 4 | version = "0.1.0" 5 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample6/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "cache_test" 3 | name = "sample6" 4 | version = "0.1.0" 5 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample7/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "cache_test" 3 | name = "sample7" 4 | version = "0.1.0" 5 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "load-tests" 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "60...80" 8 | status: 9 | project: 10 | default: 11 | target: 80 12 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "cache" 3 | name = "order_management_service" 4 | version = "1.0.0" 5 | 6 | [build-options] 7 | observabilityIncluded = false 8 | cloud = "k8s" 9 | -------------------------------------------------------------------------------- /ballerina/CompilerPlugin.toml: -------------------------------------------------------------------------------- 1 | [plugin] 2 | id = "cache-compiler-plugin" 3 | class = "io.ballerina.stdlib.cache.compiler.CacheCompilerPlugin" 4 | 5 | [[dependency]] 6 | path = "../compiler-plugin/build/libs/cache-compiler-plugin-3.10.0.jar" 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | ## Examples 4 | 5 | ## Checklist 6 | - [ ] Linked to an issue 7 | - [ ] Updated the changelog 8 | - [ ] Added tests 9 | - [ ] Updated the spec 10 | - [ ] Checked native-image compatibility 11 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # See: https://help.github.com/articles/about-codeowners/ 5 | 6 | # These owners will be the default owners for everything in the repo. 7 | * @NipunaRanasinghe @ayeshLK @shafreenAnfar 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/Cloud.toml: -------------------------------------------------------------------------------- 1 | [container.image] 2 | repository="ballerina" 3 | name="order_management_service" 4 | 5 | [cloud.deployment] 6 | min_memory="256Mi" 7 | max_memory="512Mi" 8 | min_cpu="200m" 9 | max_cpu="1000m" 10 | 11 | [cloud.deployment.autoscaling] 12 | min_replicas=1 13 | max_replicas=1 14 | -------------------------------------------------------------------------------- /.github/workflows/trivy-scan.yml: -------------------------------------------------------------------------------- 1 | name: Trivy 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "30 20 * * *" 7 | 8 | jobs: 9 | call_workflow: 10 | name: Run Trivy Scan Workflow 11 | if: ${{ github.repository_owner == 'ballerina-platform' }} 12 | uses: ballerina-platform/ballerina-library/.github/workflows/trivy-scan-template.yml@main 13 | secrets: inherit 14 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.caching=true 2 | group=io.ballerina.stdlib 3 | version=3.10.1-SNAPSHOT 4 | ballerinaLangVersion=2201.12.0 5 | stdlibTaskVersion=2.7.0 6 | stdlibTimeVersion=2.7.0 7 | stdlibConstraintVersion=1.7.0 8 | 9 | puppycrawlCheckstyleVersion=10.12.0 10 | ballerinaGradlePluginVersion=2.3.0 11 | testngVersion=7.6.1 12 | jacocoVersion=0.8.10 13 | spotbugsPluginVersion=6.0.18 14 | shadowJarPluginVersion=8.1.1 15 | downloadPluginVersion=5.4.0 16 | releasePluginVersion=2.8.0 17 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: PR Build 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} 5 | cancel-in-progress: true 6 | 7 | on: [pull_request] 8 | 9 | jobs: 10 | call_workflow: 11 | name: Run PR Build Workflow 12 | if: ${{ github.repository_owner == 'ballerina-platform' }} 13 | uses: ballerina-platform/ballerina-library/.github/workflows/pull-request-build-template.yml@main 14 | secrets: inherit 15 | -------------------------------------------------------------------------------- /.github/workflows/build-timestamped-master.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - "*.md" 9 | - "docs/**" 10 | - "load-tests/**" 11 | workflow_dispatch: 12 | 13 | jobs: 14 | call_workflow: 15 | name: Run Build Workflow 16 | if: ${{ github.repository_owner == 'ballerina-platform' }} 17 | uses: ballerina-platform/ballerina-library/.github/workflows/build-timestamp-master-template.yml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | workflow_dispatch: 5 | repository_dispatch: 6 | types: [stdlib-release-pipeline] 7 | 8 | jobs: 9 | call_workflow: 10 | name: Run Release Workflow 11 | if: ${{ github.repository_owner == 'ballerina-platform' }} 12 | uses: ballerina-platform/ballerina-library/.github/workflows/release-package-template.yml@main 13 | secrets: inherit 14 | with: 15 | package-name: cache 16 | package-org: ballerina 17 | -------------------------------------------------------------------------------- /.github/workflows/fossa_scan.yml: -------------------------------------------------------------------------------- 1 | name: Fossa Scan 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: '30 18 * * *' # 00:00 in LK time (GMT+5:30) 6 | jobs: 7 | fossa-scan: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: fossas/fossa-action@main 12 | env: 13 | packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} 14 | packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} 15 | with: 16 | api-key: ${{secrets.FOSSA_APIKEY}} 17 | -------------------------------------------------------------------------------- /load-tests/order_management_service/deployment/ingress.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: networking.k8s.io/v1 3 | kind: Ingress 4 | metadata: 5 | name: order-management-service 6 | annotations: 7 | kubernetes.io/ingress.class: nginx 8 | spec: 9 | rules: 10 | - host: bal.perf.test 11 | http: 12 | paths: 13 | - path: "/" 14 | pathType: Prefix 15 | backend: 16 | service: 17 | name: order-managemen 18 | port: 19 | number: 9098 20 | -------------------------------------------------------------------------------- /ballerina/Ballerina.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | org = "ballerina" 3 | name = "cache" 4 | version = "3.10.0" 5 | authors = ["Ballerina"] 6 | keywords = ["cache", "LRU"] 7 | repository = "https://github.com/ballerina-platform/module-ballerina-cache" 8 | icon = "icon.png" 9 | license = ["Apache-2.0"] 10 | distribution = "2201.12.0" 11 | 12 | [platform.java21] 13 | graalvmCompatible = true 14 | 15 | [[platform.java21.dependency]] 16 | groupId = "io.ballerina.stdlib" 17 | artifactId = "cache-native" 18 | version = "3.10.0" 19 | path = "../native/build/libs/cache-native-3.10.0.jar" 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | build 26 | .gradle/ 27 | target 28 | # IDEA Files 29 | .idea/ 30 | *.iml 31 | *.ipr 32 | *.iws 33 | 34 | # MacOS 35 | *.DS_Store 36 | 37 | # Ballerina 38 | velocity.log* 39 | *Ballerina.lock 40 | -------------------------------------------------------------------------------- /.github/workflows/process-load-test-result.yml: -------------------------------------------------------------------------------- 1 | name: Process load test results 2 | 3 | on: 4 | repository_dispatch: 5 | types: [cache-load-test] 6 | 7 | jobs: 8 | call_stdlib_process_load_test_results_workflow: 9 | name: Run StdLib Process Load Test Results Workflow 10 | uses: ballerina-platform/ballerina-library/.github/workflows/process-load-test-results-template.yml@main 11 | with: 12 | results: ${{ toJson(github.event.client_payload.results) }} 13 | secrets: 14 | ballerina_bot_token: ${{ secrets.BALLERINA_BOT_TOKEN }} 15 | ballerina_reviewer_bot_token: ${{ secrets.BALLERINA_REVIEWER_BOT_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/central-publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to the Ballerina central 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | environment: 7 | type: choice 8 | description: Select Environment 9 | required: true 10 | options: 11 | - DEV CENTRAL 12 | - STAGE CENTRAL 13 | 14 | jobs: 15 | call_workflow: 16 | name: Run Central Publish Workflow 17 | if: ${{ github.repository_owner == 'ballerina-platform' }} 18 | uses: ballerina-platform/ballerina-library/.github/workflows/central-publish-template.yml@main 19 | secrets: inherit 20 | with: 21 | environment: ${{ github.event.inputs.environment }} 22 | -------------------------------------------------------------------------------- /spotbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample5/main.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | 19 | cache:Cache httpClientCache = new; 20 | -------------------------------------------------------------------------------- /native/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | module io.ballerina.stdlib.cache { 20 | requires io.ballerina.runtime; 21 | exports io.ballerina.stdlib.cache.nativeimpl; 22 | } 23 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | module io.ballerina.stdlib.cache.compiler { 20 | requires io.ballerina.lang; 21 | requires io.ballerina.tools.api; 22 | requires io.ballerina.parser; 23 | } 24 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample6/modules/config/config.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | public const int CAPACITY = 10; 18 | public const float EVICTION_FACTOR = 0.2; 19 | public const decimal DEFAULT_MAX_AGE = 30; 20 | public const decimal CLEANUP_INTERVAL = 60; 21 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample6/modules/code/code.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 LLC. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | import sample6.config; 19 | 20 | final cache:CacheConfig cacheConfig = { 21 | capacity: 10, 22 | evictionFactor: config:EVICTION_FACTOR, 23 | defaultMaxAge: config:DEFAULT_MAX_AGE, 24 | cleanupInterval: config:CLEANUP_INTERVAL 25 | }; 26 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample2/main.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | 19 | public function main() returns error? { 20 | 21 | cache:Cache cache = new(capacity = -1, evictionFactor = 2, defaultMaxAge = -2, cleanupInterval = -1, 22 | evictionPolicy = "LRU"); 23 | check cache.put("hi", "Ballerina"); 24 | } 25 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample7/main.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, WSO2 Inc. (http://www.wso2.org). 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | 19 | configurable int capacity = 300; 20 | const decimal CACHE_CLEANUP_INTERVAL = 900.0; 21 | 22 | public function main() returns error? { 23 | cache:Cache cache = new(capacity = capacity , evictionFactor = 0.2, defaultMaxAge = 86400, cleanupInterval = CACHE_CLEANUP_INTERVAL); 24 | } 25 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample3/main.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | 18 | import ballerina/cache; 19 | 20 | cache:CacheConfig config = { 21 | capacity:-1, 22 | evictionFactor: 2, 23 | defaultMaxAge: -2, 24 | cleanupInterval: -1, 25 | evictionPolicy: "LRU" 26 | }; 27 | 28 | public function main() returns error? { 29 | cache:Cache cache = new(config); 30 | check cache.put("hi", "Ballerina"); 31 | } 32 | -------------------------------------------------------------------------------- /load-tests/order_management_service/scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Copyright 2021 WSO2 Inc. (http://wso2.org) 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # ---------------------------------------------------------------------------- 17 | # Execution script for ballerina performance tests 18 | # ---------------------------------------------------------------------------- 19 | set -e 20 | source base-scenario.sh 21 | 22 | jmeter -n -t "$scriptsDir/"http-post-request.jmx -l "$resultsDir/"original.jtl -Jusers=10 -Jduration=1200 -Jhost=bal.perf.test -Jport=80 -Jprotocol=http -Jpath=serv $payload_flags 23 | -------------------------------------------------------------------------------- /native/src/test/resources/testng.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample1/main.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | 19 | public function main() returns error? { 20 | 21 | cache:CacheConfig config = { 22 | capacity:-1, 23 | evictionFactor: 2, 24 | defaultMaxAge: -2, 25 | cleanupInterval: -1, 26 | evictionPolicy: "LRU" 27 | }; 28 | cache:Cache cache = new(config); 29 | check cache.put("hi", "Ballerina"); 30 | } 31 | -------------------------------------------------------------------------------- /ballerina/cache_errors.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | # Represents Cache related errors. This will be returned if an error occurred while doing any of the cache operations. 18 | public type Error distinct error; 19 | 20 | # Prepare the `error` as a `cache:Error`. 21 | # 22 | # + message - Error message 23 | # + return - Prepared `Error` instance 24 | isolated function prepareError(string message) returns Error { 25 | Error cacheError = error Error(message); 26 | return cacheError; 27 | } 28 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/testng.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /native/spotbugs-exclude.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/java/io/ballerina/stdlib/cache/compiler/CacheCompilerPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package io.ballerina.stdlib.cache.compiler; 20 | 21 | import io.ballerina.projects.plugins.CompilerPlugin; 22 | import io.ballerina.projects.plugins.CompilerPluginContext; 23 | 24 | /** 25 | * Compiler plugin for Cache object. 26 | */ 27 | public class CacheCompilerPlugin extends CompilerPlugin { 28 | @Override 29 | public void init(CompilerPluginContext compilerPluginContext) { 30 | compilerPluginContext.addCodeAnalyzer(new CacheCodeAnalyzer()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/resources/diagnostics/sample4/main.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | 19 | public type CacheConfig record { 20 | int capacity; 21 | float evictionFactor; 22 | }; 23 | 24 | public isolated class HttpCache { 25 | 26 | final cache:Cache cache; 27 | 28 | public isolated function init(CacheConfig cacheConfig) { 29 | cache:CacheConfig config = { 30 | capacity: cacheConfig.capacity, 31 | evictionFactor: cacheConfig.evictionFactor, 32 | cleanupInterval: -1, 33 | evictionPolicy: "LRU" 34 | }; 35 | self.cache = new cache:Cache(config); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/resources/order_service/public.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdzCCAl+gAwIBAgIEfP3e8zANBgkqhkiG9w0BAQsFADBkMQswCQYDVQQGEwJV 3 | UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxDTALBgNVBAoT 4 | BFdTTzIxDTALBgNVBAsTBFdTTzIxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzEw 5 | MjQwNTQ3NThaFw0zNzEwMTkwNTQ3NThaMGQxCzAJBgNVBAYTAlVTMQswCQYDVQQI 6 | EwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzENMAsGA1UEChMEV1NPMjENMAsG 7 | A1UECxMEV1NPMjESMBAGA1UEAxMJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF 8 | AAOCAQ8AMIIBCgKCAQEAgVyi6fViVLiZKEnw59xzNi1lcYh6z9dZnug+F9gKqFIg 9 | mdcPe+qtS7gZc1jYTjWMCbx13sFLkZqNHeDUadpmtKo3TDduOl1sqM6cz3yXb6L3 10 | 4k/leh50mzIPNmaaXxd3vOQoK4OpkgO1n32mh6+tkp3sbHmfYqDQrkVK1tmYNtPJ 11 | ffSCLT+CuIhnJUJco7N0unax+ySZN67/AX++sJpqAhAIZJzrRi6ueN3RFCIxYDXS 12 | MvxrEmOdn4gOC0o1Ar9u5Bp9N52sqqGbN1x6jNKi3bfUj122Hu5e+Y9KOmfbchhQ 13 | il2P81cIi30VKgyDn5DeWEuDoYredk4+6qAZrxMw+wIDAQABozEwLzAOBgNVHQ8B 14 | Af8EBAMCBaAwHQYDVR0OBBYEFNmtrQ36j6tUGhKrfW9qWWE7KFzMMA0GCSqGSIb3 15 | DQEBCwUAA4IBAQAv3yOwgbtOu76eJMl1BCcgTFgaMUBZoUjK9Un6HGjKEgYz/YWS 16 | ZFlY/qH5rT01DWQevUZB626d5ZNdzSBZRlpsxbf9IE/ursNHwHx9ua6fB7yHUCzC 17 | 1ZMp1lvBHABi7wcA+5nbV6zQ7HDmBXFhJfbgH1iVmA1KcvDeBPSJ/scRGasZ5q2W 18 | 3IenDNrfPIUhD74tFiCiqNJO91qD/LO+++3XeZzfPh8NRKkiPX7dB8WJ3YNBuQAv 19 | gRWTISpSSXLmqMb+7MPQVgecsepZdk8CwkRLxh3RKPJMjigmCgyvkSaoDMKAYC3i 20 | YjfUTiJ57UeqoSl0IaOFJ0wfZRFh+UytlDZa 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /.github/workflows/trigger-load-tests.yml: -------------------------------------------------------------------------------- 1 | name: Trigger Load Tests 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tests: 7 | description: > 8 | List of test names. This needs to be filled only if you want to run a specific set of tests. Example: foo,bar 9 | required: false 10 | clusterName: 11 | description: 'Cluster name' 12 | default: 'cache-perf-cluster-test' 13 | required: false 14 | branch: 15 | description: 'Branch of the given repository' 16 | default: '' 17 | required: false 18 | schedule: 19 | - cron: '0 2 * * *' 20 | 21 | jobs: 22 | call_stdlib_trigger_load_test_workflow: 23 | name: Run StdLib Load Test Workflow 24 | if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} 25 | uses: ballerina-platform/ballerina-library/.github/workflows/trigger-load-tests-template.yml@main 26 | with: 27 | repo_name: 'module-ballerina-cache' 28 | runtime_artifacts_url: 'https://api.github.com/repos/ballerina-platform/module-ballerina-cache/actions/artifacts' 29 | dispatch_type: 'cache-load-test' 30 | cluster_name: ${{ inputs.clusterName }} 31 | tests: ${{ inputs.tests }} 32 | branch: ${{ inputs.branch }} 33 | secrets: 34 | ballerina_bot_token: ${{ secrets.BALLERINA_BOT_TOKEN }} 35 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/cache/nativeimpl/concurrentlinkedhashmap/Weigher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | package io.ballerina.stdlib.cache.nativeimpl.concurrentlinkedhashmap; 19 | 20 | /** 21 | * A class that can determine the weight of a value. The total weight threshold is used to 22 | * determine when an eviction is required. 23 | * 24 | * @param the type of values to weigh 25 | */ 26 | interface Weigher { 27 | 28 | /** 29 | * Measures an object's weight to determine how many units of capacity that the value 30 | * consumes. A value must consume a minimum of one unit. 31 | * 32 | * @param value the object to weigh 33 | * @return the object's weight 34 | */ 35 | int weightOf(V value); 36 | } 37 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/java/io/ballerina/stdlib/cache/compiler/CacheCodeAnalyzer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package io.ballerina.stdlib.cache.compiler; 20 | 21 | import io.ballerina.compiler.syntax.tree.SyntaxKind; 22 | import io.ballerina.projects.plugins.CodeAnalysisContext; 23 | import io.ballerina.projects.plugins.CodeAnalyzer; 24 | 25 | import java.util.List; 26 | 27 | /** 28 | * Cache Code Analyzer. 29 | */ 30 | public class CacheCodeAnalyzer extends CodeAnalyzer { 31 | 32 | @Override 33 | public void init(CodeAnalysisContext codeAnalysisContext) { 34 | codeAnalysisContext.addSyntaxNodeAnalysisTask(new CacheConfigValidator(), 35 | List.of(SyntaxKind.LOCAL_VAR_DECL, SyntaxKind.MODULE_VAR_DECL)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | This file contains all the notable changes done to the Ballerina Cache package through the releases. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [unreleased] 8 | ### Fixed 9 | - [Fix the compilation failure when constants and configurables are used in cache config as included params](https://github.com/ballerina-platform/ballerina-library/issues/6036) 10 | 11 | ## [3.7.1] - 2024-01-11 12 | 13 | ### Fixed 14 | - [Fix the compilation failure when constants are used in cache config](https://github.com/ballerina-platform/ballerina-library/issues/5915) 15 | 16 | ## [2.1.0-beta.1] - 2021-06-02 17 | 18 | ### Added 19 | - [Introduced the compiler plugin to validate cache configurations](https://github.com/ballerina-platform/ballerina-standard-library/issues/1435) 20 | 21 | ### Changed 22 | - [API docs updated](https://github.com/ballerina-platform/ballerina-standard-library/issues/3463) 23 | 24 | ## [2.0.0-alpha5] - 2021-03-19 25 | 26 | ### Added 27 | - [Introduced the new configuration as “EvictionPolicy“ to set the eviction policy](https://github.com/ballerina-platform/ballerina-standard-library/issues/1027) 28 | 29 | ### Removed 30 | - [Remove the “AbstractEvictionPolicy” Object](https://github.com/ballerina-platform/ballerina-standard-library/issues/1027) 31 | 32 | ### Changed 33 | - Update for Time API changes 34 | - Update for Task API changes -------------------------------------------------------------------------------- /.github/workflows/build-with-bal-test-graalvm.yml: -------------------------------------------------------------------------------- 1 | name: GraalVM Check 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | lang_tag: 7 | description: Branch/Release Tag of the Ballerina Lang 8 | required: true 9 | default: master 10 | lang_version: 11 | description: Ballerina Lang Version (If given ballerina lang buid will be skipped) 12 | required: false 13 | default: '' 14 | native_image_options: 15 | description: Default native-image options 16 | required: false 17 | default: '' 18 | schedule: 19 | - cron: '30 18 * * *' 20 | pull_request: 21 | branches: 22 | - master 23 | types: [opened, synchronize, reopened, labeled, unlabeled] 24 | 25 | concurrency: 26 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} 27 | cancel-in-progress: true 28 | 29 | jobs: 30 | call_stdlib_workflow: 31 | name: Run StdLib Workflow 32 | if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} 33 | uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-template.yml@main 34 | with: 35 | lang_tag: ${{ inputs.lang_tag }} 36 | lang_version: ${{ inputs.lang_version }} 37 | native_image_options: ${{ inputs.native_image_options }} 38 | additional_ubuntu_build_flags: '-x :cache-native:test -x :cache-compiler-plugin-tests:test' 39 | additional_windows_build_flags: '-x :cache-native:test -x :cache-compiler-plugin-tests:test' 40 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/java/io/ballerina/stdlib/cache/compiler/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | package io.ballerina.stdlib.cache.compiler; 19 | 20 | /** 21 | * Constants used in compiler plugin. 22 | */ 23 | public class Constants { 24 | public static final String BALLERINA = "ballerina"; 25 | public static final String CACHE = "cache"; 26 | public static final String CACHE_CONFIG = "CacheConfig"; 27 | 28 | public static final String CAPACITY = "capacity"; 29 | public static final String CLEAN_UP_INTERVAL = "cleanupInterval"; 30 | public static final String EVICTION_FACTOR = "evictionFactor"; 31 | public static final String EVICTION_POLICY = "evictionPolicy"; 32 | public static final String DEFAULT_MAX_AGE = "defaultMaxAge"; 33 | public static final String POLICY_VALUE = "cache:LRU"; 34 | public static final String UNNECESSARY_CHARS_REGEX = "\"|\\n"; 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/update_specs.yml: -------------------------------------------------------------------------------- 1 | name: Update Specifications 2 | 3 | env: 4 | SPEC_FOLDER_PATH: 'docs/spec' 5 | 6 | on: 7 | workflow_dispatch: 8 | push: 9 | branches: 10 | - master 11 | paths: 12 | - 'docs/spec/**' 13 | 14 | jobs: 15 | update_specs: 16 | name: Update Specifications 17 | if: github.repository_owner == 'ballerina-platform' 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout Repository 22 | uses: actions/checkout@v2 23 | 24 | - name: Get current date 25 | id: date 26 | run: echo "::set-output name=date::$(date +'%Y-%m-%d')" 27 | 28 | - name: Get Repo Name 29 | id: repo_name 30 | run: | 31 | MODULE=${{ github.event.repository.name }} 32 | echo "::set-output name=short_name::${MODULE##*-}" 33 | 34 | - name: Trigger Workflow 35 | run: | 36 | curl --request POST \ 37 | 'https://api.github.com/repos/ballerina-platform/ballerina-dev-website/dispatches' \ 38 | -H 'Accept: application/vnd.github.v3+json' \ 39 | -H 'Authorization: Bearer ${{ secrets.BALLERINA_BOT_TOKEN }}' \ 40 | --data "{ 41 | \"event_type\": \"update-stdlib-specs\", 42 | \"client_payload\": { 43 | \"module_name\": \"${{ github.event.repository.name }}\", 44 | \"short_name\": \"${{ steps.repo_name.outputs.short_name }}\", 45 | \"file_dir\": \"${{ github.event.repository.name }}/${{ env.SPEC_FOLDER_PATH }}\", 46 | \"release_date\": \"${{ steps.date.outputs.date }}\" 47 | } 48 | }" 49 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/cache/nativeimpl/concurrentlinkedhashmap/Linked.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | package io.ballerina.stdlib.cache.nativeimpl.concurrentlinkedhashmap; 19 | 20 | /** 21 | * An element that is linked on the Deque. 22 | * 23 | * @param the type of elements held in this collection 24 | */ 25 | interface Linked> { 26 | 27 | /** 28 | * Retrieves the previous element or null if either the element is unlinked 29 | * or the first element on the deque. 30 | */ 31 | T getPrevious(); 32 | 33 | /** Sets the previous element or null if there is no link. */ 34 | void setPrevious(T prev); 35 | 36 | /** 37 | * Retrieves the next element or null if either the element is unlinked or 38 | * the last element on the deque. 39 | */ 40 | T getNext(); 41 | 42 | /** Sets the next element or null if there is no link. */ 43 | void setNext(T next); 44 | } 45 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/cache/nativeimpl/concurrentlinkedhashmap/Weighers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | package io.ballerina.stdlib.cache.nativeimpl.concurrentlinkedhashmap; 19 | 20 | /** 21 | * A common set of {@link Weigher} implementations. 22 | * 23 | */ 24 | final class Weighers { 25 | 26 | private Weighers() {} 27 | 28 | /** 29 | * A weigher where a value has a weight of 1. A map bounded with this weigher 30 | * will evict when the number of key-value pairs exceeds the capacity. 31 | * 32 | * @return A weigher where a value takes one unit of capacity. 33 | */ 34 | @SuppressWarnings("unchecked") 35 | public static Weigher singleton() { 36 | return (Weigher) SingletonWeigher.INSTANCE; 37 | } 38 | 39 | private enum SingletonWeigher implements Weigher { 40 | INSTANCE; 41 | 42 | public int weightOf(Object value) { 43 | return 1; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/resources/order_service/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCBXKLp9WJUuJko 3 | SfDn3HM2LWVxiHrP11me6D4X2AqoUiCZ1w976q1LuBlzWNhONYwJvHXewUuRmo0d 4 | 4NRp2ma0qjdMN246XWyozpzPfJdvovfiT+V6HnSbMg82ZppfF3e85Cgrg6mSA7Wf 5 | faaHr62SnexseZ9ioNCuRUrW2Zg208l99IItP4K4iGclQlyjs3S6drH7JJk3rv8B 6 | f76wmmoCEAhknOtGLq543dEUIjFgNdIy/GsSY52fiA4LSjUCv27kGn03nayqoZs3 7 | XHqM0qLdt9SPXbYe7l75j0o6Z9tyGFCKXY/zVwiLfRUqDIOfkN5YS4Ohit52Tj7q 8 | oBmvEzD7AgMBAAECggEAXM/F4u23OummmQ1T1kaIMpqnaalt06jCGAywYBMUsmca 9 | FMYDyfg5lVXkjKl1p8crTeD1AHjWawTjskgYnkmf3ocxXXF3mFBnIUX7o7HURLg7 10 | +RcxoUgwiRiFaZZ7szX3JoLbfzzbcHNQ37kavccBVWwQsFMiU3Tlw+LbKwK6/row 11 | LYsQPx7gT4u7hViat4vQDTYcgyjvvFCiek4ndL6O9K49MxIMU678UXB6ia5iUevy 12 | vgEfcYkKQ5EQ38qS3ZwsubPvj4633jvAJRr/hJD8XINZC74kTXeV3BGH2LlpQOEq 13 | kWkOypwYNjnXtt1JO8+Iu6mEXKUoiIBPfGrJ3vDSQQKBgQDmYPc7kfYan/LHjJRv 14 | iE2CwbC26yVA6+BEPQv9z7jChO9Q6cUbGvM8EEVNpC9nmFogkslzJhz55HP84QZL 15 | u3ptU+D96ncq6zkBqxBfRnZG++D36+XRXIwzz3h+g1Nwrl0y0MFbwlkMm3ZqJdd6 16 | pZz1FZGd6zvQftW8m7jPSKHuswKBgQCPv6czFOZR6bI+qCQdaORpe9JGoAduOD+4 17 | YKl96s0eiAKhkGhFCrMd6GJwWRkpNcfwB+J9sMahORbfvwiYanI56h7Vi30DFPRb 18 | m1m8dLkr6z+8bxMxKJaMXIIjy3UDamgDr7QHInNUih2iGvtB8QqZ0aobsB2XIxZg 19 | qESTMcpYmQKBgHSwSqneraQgvgz7FLhFdtUzHDoacr0mfGqz7R37F99XDAyUy+SF 20 | ywvyRdgkwGodjhEPqH/tnyGn6GP+6nxzknhL0xtppkCT8kT5C4rmmsQrknChCL/5 21 | u34GqUaTaDEb8FLrz/SVRRuQpvLvBey2dADjkuVFH//kLoig64P6iyLnAoGBAIlF 22 | g+2L78YZXVXoS1SqbjUtQUigWXgvzunLpQ/Rwb9+MsUGmgwUg6fz2s1eyGBKM3xM 23 | i0VsIsKjOezBCPxD6oDTyk4yvlbLE+7HE5KcBJikNmFD0RgIonu3e6+jA0MXweyD 24 | RW/qviflHRdInNgDzxPE3KVEMX26zAvRpGrMCWdBAoGAdQ5SvX+mAC3cKqoQ9Zal 25 | lSqWoyjfzP5EaVRG8dtoLxbznQGTTvtHXc65/MznX/L9qkWCS6Eb4HH5M3hFNY46 26 | LNIzGQLznE1odwv7H5B8c0/m3DrKTxbh8bYcrR1BW5/nKZNNW7k1O6OjEozvAajK 27 | JQdp3KBU9S8CmBjGrRpJ2qw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/order_service.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/cache; 18 | import ballerina/http; 19 | 20 | final http:JwtValidatorConfig config = { 21 | issuer: "wso2", 22 | audience: "vEwzbcasJVQm1jVYHUHCjhxZ4tYa", 23 | clockSkew: 60, 24 | scopeKey: "action", 25 | signatureConfig: { 26 | certFile: "./resources/order_service/public.crt" 27 | }, 28 | cacheConfig: { 29 | capacity: 10, 30 | evictionFactor: 0.25, 31 | evictionPolicy: cache:LRU, 32 | defaultMaxAge: -1 33 | } 34 | }; 35 | 36 | listener http:Listener orderEP = new (9097, 37 | secureSocket = { 38 | key: { 39 | certFile: "./resources/order_service/public.crt", 40 | keyFile: "./resources/order_service/private.key" 41 | } 42 | } 43 | ); 44 | 45 | isolated service /'order on orderEP { 46 | 47 | @http:ResourceConfig { 48 | auth: [ 49 | { 50 | jwtValidatorConfig: config, 51 | scopes: "add_order" 52 | } 53 | ] 54 | } 55 | isolated resource function post .(@http:Payload json payload) returns json { 56 | return payload; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.3/userguide/multi_project_builds.html 8 | */ 9 | 10 | pluginManagement { 11 | plugins { 12 | id "com.github.spotbugs" version "${spotbugsPluginVersion}" 13 | id "com.github.johnrengelman.shadow" version "${shadowJarPluginVersion}" 14 | id "de.undercouch.download" version "${downloadPluginVersion}" 15 | id "net.researchgate.release" version "${releasePluginVersion}" 16 | id "io.ballerina.plugin" version "${ballerinaGradlePluginVersion}" 17 | } 18 | 19 | repositories { 20 | gradlePluginPortal() 21 | maven { 22 | url = 'https://maven.pkg.github.com/ballerina-platform/*' 23 | credentials { 24 | username System.getenv("packageUser") 25 | password System.getenv("packagePAT") 26 | } 27 | } 28 | } 29 | } 30 | 31 | plugins { 32 | id "com.gradle.enterprise" version "3.13.2" 33 | } 34 | 35 | rootProject.name = 'cache' 36 | include(':checkstyle') 37 | include ':cache-native' 38 | include ':cache-compiler-plugin' 39 | include ':cache-ballerina' 40 | include ':cache-compiler-plugin-tests' 41 | 42 | project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") 43 | project(':cache-compiler-plugin').projectDir = file('compiler-plugin') 44 | project(':cache-native').projectDir = file('native') 45 | project(':cache-ballerina').projectDir = file('ballerina') 46 | project(':cache-compiler-plugin-tests').projectDir = file('compiler-plugin-tests') 47 | 48 | gradleEnterprise { 49 | buildScan { 50 | termsOfServiceUrl = 'https://gradle.com/terms-of-service' 51 | termsOfServiceAgree = 'yes' 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/java/io/ballerina/stdlib/cache/compiler/DiagnosticsCodes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | package io.ballerina.stdlib.cache.compiler; 19 | 20 | import io.ballerina.tools.diagnostics.DiagnosticSeverity; 21 | 22 | import static io.ballerina.tools.diagnostics.DiagnosticSeverity.ERROR; 23 | 24 | /** 25 | * Enum class to hold cache module diagnostic codes. 26 | */ 27 | public enum DiagnosticsCodes { 28 | 29 | CACHE_101("invalid value: a greater than zero value is expected", "CACHE_101", ERROR), 30 | CACHE_102("invalid value: a value between 0 (exclusive) and 1 (inclusive) is expected", 31 | "CACHE_102", ERROR), 32 | CACHE_103("invalid value: a greater than 0 value or -1(to indicate forever valid) value is expected", 33 | "CACHE_103", ERROR), 34 | CACHE_104("invalid value: a greater than zero value is expected", "CACHE_104", ERROR), 35 | CACHE_105("invalid value: only 'cache:LRU' value is supported", "CACHE_105", ERROR), 36 | CACHE_106("invalid value: ", "CACHE_106", ERROR); 37 | 38 | private final String error; 39 | private final String errorCode; 40 | private final DiagnosticSeverity severity; 41 | 42 | DiagnosticsCodes(String error, String errorCode, DiagnosticSeverity severity) { 43 | this.error = error; 44 | this.errorCode = errorCode; 45 | this.severity = severity; 46 | } 47 | 48 | public String getError() { 49 | return error; 50 | } 51 | 52 | public String getErrorCode() { 53 | return errorCode; 54 | } 55 | 56 | public DiagnosticSeverity getSeverity() { 57 | return severity; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /native/src/test/java/io/ballerina/stdlib/cache/CacheTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package io.ballerina.stdlib.cache; 20 | 21 | import io.ballerina.stdlib.cache.nativeimpl.concurrentlinkedhashmap.ConcurrentLinkedHashMap; 22 | import org.testng.Assert; 23 | import org.testng.annotations.BeforeTest; 24 | import org.testng.annotations.Test; 25 | 26 | /** 27 | * Test native functions for concurrent linked hash map call. 28 | */ 29 | public class CacheTest { 30 | 31 | private ConcurrentLinkedHashMap cacheMap; 32 | 33 | @BeforeTest() 34 | public void setupCache() { 35 | cacheMap = new ConcurrentLinkedHashMap<>(10); 36 | } 37 | 38 | @Test() 39 | public void testPutWithNullKey() { 40 | Assert.assertNull(cacheMap.put(null, "value")); 41 | } 42 | 43 | @Test() 44 | public void testPutWithNullValue() { 45 | Assert.assertNull(cacheMap.put("test", null)); 46 | } 47 | 48 | @Test() 49 | public void testContainsKeyWithNullKey() { 50 | Assert.assertFalse(cacheMap.containsKey(null)); 51 | } 52 | 53 | @Test() 54 | public void testGetWithNullKey() { 55 | Assert.assertNull(cacheMap.get(null)); 56 | } 57 | 58 | @Test() 59 | public void testRemoveWithNullKey() { 60 | Assert.assertNull(cacheMap.remove(null)); 61 | } 62 | 63 | @Test() 64 | public void testContainValue() { 65 | cacheMap.put("key", "value"); 66 | Assert.assertTrue(cacheMap.containsValue("value")); 67 | } 68 | 69 | @Test() 70 | public void negativeTestContainValue() { 71 | Assert.assertFalse(cacheMap.containsValue("value1")); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ballerina/README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | This module provides APIs for in-memory caching by using a semi-persistent mapping from keys to values. Cache entries are added to the cache manually and are stored in the cache until either evicted or invalidated manually. 4 | 5 | This is based on the Least Recently Used (LRU) eviction algorithm by using a `map` data structure and defining the most basic operations on a collection of cache entries, which entails basic reading, writing, and deleting individual cache items. 6 | It does not allow the `()` as a key or value of the cache and entries can be accessed safely by multiple concurrent threads as it is thread-safe. 7 | 8 | The cache can be defined with optional configurations as follows: 9 | ```ballerina 10 | cache:Cache cache = new (capacity = 10, evictionFactor = 0.2, defaultMaxAge = 0.5, cleanupInterval = 1); 11 | ``` 12 | 13 | The Cache entries will be evicted in case of the following scenarios: 14 | 15 | - When using the `get` API, if the returning cache entry has expired, it gets removed. 16 | - When using the `put` API, if the cache size has reached its capacity, the number of entries that get removed will be based on the `eviction policy` and the `eviction factor`. 17 | - If `cleanupInterval` (optional property) is configured, the recurrence task will remove the expired cache entries based on the configured interval. The main benefit of this property is that you can optimize the memory usage while adding some additional CPU costs and vice versa. The default behaviour is the CPU-optimized method. 18 | 19 | The `cache:AbstractCache` object has the common APIs for the caching functionalities. Custom implementations of the cache can be done with different data storages like file, database, etc., with the structural equivalency to the `cache:AbstractCacheObject` object. 20 | 21 | ```ballerina 22 | public type AbstractCache object { 23 | public function put(string key, any value, int maxAgeInSeconds) returns Error?; 24 | public function get(string key) returns any|Error; 25 | public function invalidate(string key) returns Error?; 26 | public function invalidateAll() returns Error?; 27 | public function hasKey(string key) returns boolean; 28 | public function keys() returns string[]; 29 | public function size() returns int; 30 | public function capacity() returns int; 31 | }; 32 | ``` 33 | The Ballerina Cache package provides the `cache:Cache` class, which is a `map` data structure based implementation of the `cache:AbstractCache` object. 34 | -------------------------------------------------------------------------------- /compiler-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | plugins { 20 | id 'java' 21 | id 'checkstyle' 22 | id 'com.github.spotbugs' 23 | } 24 | 25 | description = 'Ballerina - Cache Compiler Plugin' 26 | 27 | dependencies { 28 | checkstyle project(':checkstyle') 29 | checkstyle "com.puppycrawl.tools:checkstyle:${puppycrawlCheckstyleVersion}" 30 | 31 | implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" 32 | implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" 33 | implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" 34 | } 35 | 36 | def excludePattern = '**/module-info.java' 37 | tasks.withType(Checkstyle) { 38 | exclude excludePattern 39 | } 40 | 41 | checkstyle { 42 | toolVersion "${project.puppycrawlCheckstyleVersion}" 43 | configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") 44 | configProperties = ["suppressionFile" : file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] 45 | } 46 | 47 | checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") 48 | 49 | spotbugsMain { 50 | def classLoader = plugins["com.github.spotbugs"].class.classLoader 51 | def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence") 52 | def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort") 53 | ignoreFailures = true 54 | effort = SpotBugsEffort.MAX 55 | reportLevel = SpotBugsConfidence.LOW 56 | reportsDir = file("$project.buildDir/reports/spotbugs") 57 | reports { 58 | html.enabled true 59 | text.enabled = true 60 | } 61 | def excludeFile = file("${rootDir}/spotbugs-exclude.xml") 62 | if(excludeFile.exists()) { 63 | excludeFilter = excludeFile 64 | } 65 | } 66 | 67 | compileJava { 68 | doFirst { 69 | options.compilerArgs = [ 70 | '--module-path', classpath.asPath, 71 | ] 72 | classpath = files() 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /ballerina/Dependencies.toml: -------------------------------------------------------------------------------- 1 | # AUTO-GENERATED FILE. DO NOT MODIFY. 2 | 3 | # This file is auto-generated by Ballerina for managing dependency versions. 4 | # It should not be modified by hand. 5 | 6 | [ballerina] 7 | dependencies-toml-version = "2" 8 | distribution-version = "2201.12.0" 9 | 10 | [[package]] 11 | org = "ballerina" 12 | name = "cache" 13 | version = "3.10.0" 14 | dependencies = [ 15 | {org = "ballerina", name = "constraint"}, 16 | {org = "ballerina", name = "jballerina.java"}, 17 | {org = "ballerina", name = "lang.runtime"}, 18 | {org = "ballerina", name = "task"}, 19 | {org = "ballerina", name = "test"}, 20 | {org = "ballerina", name = "time"} 21 | ] 22 | modules = [ 23 | {org = "ballerina", packageName = "cache", moduleName = "cache"} 24 | ] 25 | 26 | [[package]] 27 | org = "ballerina" 28 | name = "constraint" 29 | version = "1.7.0" 30 | dependencies = [ 31 | {org = "ballerina", name = "jballerina.java"} 32 | ] 33 | modules = [ 34 | {org = "ballerina", packageName = "constraint", moduleName = "constraint"} 35 | ] 36 | 37 | [[package]] 38 | org = "ballerina" 39 | name = "jballerina.java" 40 | version = "0.0.0" 41 | modules = [ 42 | {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} 43 | ] 44 | 45 | [[package]] 46 | org = "ballerina" 47 | name = "lang.__internal" 48 | version = "0.0.0" 49 | scope = "testOnly" 50 | dependencies = [ 51 | {org = "ballerina", name = "jballerina.java"}, 52 | {org = "ballerina", name = "lang.object"} 53 | ] 54 | 55 | [[package]] 56 | org = "ballerina" 57 | name = "lang.array" 58 | version = "0.0.0" 59 | scope = "testOnly" 60 | dependencies = [ 61 | {org = "ballerina", name = "jballerina.java"}, 62 | {org = "ballerina", name = "lang.__internal"} 63 | ] 64 | 65 | [[package]] 66 | org = "ballerina" 67 | name = "lang.error" 68 | version = "0.0.0" 69 | scope = "testOnly" 70 | dependencies = [ 71 | {org = "ballerina", name = "jballerina.java"} 72 | ] 73 | 74 | [[package]] 75 | org = "ballerina" 76 | name = "lang.object" 77 | version = "0.0.0" 78 | scope = "testOnly" 79 | 80 | [[package]] 81 | org = "ballerina" 82 | name = "lang.runtime" 83 | version = "0.0.0" 84 | scope = "testOnly" 85 | dependencies = [ 86 | {org = "ballerina", name = "jballerina.java"} 87 | ] 88 | modules = [ 89 | {org = "ballerina", packageName = "lang.runtime", moduleName = "lang.runtime"} 90 | ] 91 | 92 | [[package]] 93 | org = "ballerina" 94 | name = "task" 95 | version = "2.7.0" 96 | dependencies = [ 97 | {org = "ballerina", name = "jballerina.java"}, 98 | {org = "ballerina", name = "time"} 99 | ] 100 | modules = [ 101 | {org = "ballerina", packageName = "task", moduleName = "task"} 102 | ] 103 | 104 | [[package]] 105 | org = "ballerina" 106 | name = "test" 107 | version = "0.0.0" 108 | scope = "testOnly" 109 | dependencies = [ 110 | {org = "ballerina", name = "jballerina.java"}, 111 | {org = "ballerina", name = "lang.array"}, 112 | {org = "ballerina", name = "lang.error"} 113 | ] 114 | modules = [ 115 | {org = "ballerina", packageName = "test", moduleName = "test"} 116 | ] 117 | 118 | [[package]] 119 | org = "ballerina" 120 | name = "time" 121 | version = "2.7.0" 122 | dependencies = [ 123 | {org = "ballerina", name = "jballerina.java"} 124 | ] 125 | modules = [ 126 | {org = "ballerina", packageName = "time", moduleName = "time"} 127 | ] 128 | 129 | -------------------------------------------------------------------------------- /ballerina/abstract_cache.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | # The `cache:AbstractCache` object is used for custom implementations of the Ballerina cache. 18 | # Any custom cache implementation should be object-wise similar. 19 | public type AbstractCache object { 20 | 21 | # Adds the given key value pair to the cache. If the cache previously contained a value associated with the key, the 22 | # old value is replaced by the new value. 23 | # 24 | # + key - Key of the value to be cached 25 | # + value - Value to be cached 26 | # + maxAge - The time in seconds during which the cache entry is valid. '-1' means, the entry is valid forever 27 | # + return - `()` if successfully added to the cache or `Error` if any error occurred while inserting the entry 28 | # to the cache 29 | public isolated function put(string key, any value, decimal maxAge = -1) returns Error?; 30 | 31 | # Returns the cached value associated with the provided key. 32 | # 33 | # + key - The key used to retrieve the cached value 34 | # + return - The cached value associated with the given key or an `Error` if the provided cache key is not 35 | # available or if any error occurred while retrieving from the cache 36 | public isolated function get(string key) returns any|Error; 37 | 38 | # Discards a cached value from the cache. 39 | # 40 | # + key - Key of the cache entry which needs to be discarded 41 | # + return - `()` if successfully discarded or an `Error` if the provided cache key is not available or if any 42 | # error occurred while discarding from the cache 43 | public isolated function invalidate(string key) returns Error?; 44 | 45 | # Discards all the cached values from the cache. 46 | # 47 | # + return - `()` if successfully discarded all or an `Error` if any error occurred while discarding all from the 48 | # cache 49 | public isolated function invalidateAll() returns Error?; 50 | 51 | # Checks whether the given key has an associated cache value. 52 | # 53 | # + key - The key to be checked 54 | # + return - `true` if an associated cache value is available for the provided key or `false` if there is not a 55 | # cache value associated with the provided key 56 | public isolated function hasKey(string key) returns boolean; 57 | 58 | # Returns all keys from the cache. 59 | # 60 | # + return - Array of all the keys from the cache 61 | public isolated function keys() returns string[]; 62 | 63 | # Returns the current size of the cache. 64 | # 65 | # + return - The size of the cache 66 | public isolated function size() returns int; 67 | 68 | # Returns the capacity of the cache. 69 | # 70 | # + return - The capacity of the cache 71 | public isolated function capacity() returns int; 72 | }; 73 | -------------------------------------------------------------------------------- /native/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | plugins { 19 | id 'java' 20 | id 'checkstyle' 21 | id 'com.github.spotbugs' 22 | } 23 | 24 | description = 'Ballerina - Cache Java Utils' 25 | 26 | dependencies { 27 | checkstyle project(':checkstyle') 28 | checkstyle "com.puppycrawl.tools:checkstyle:${puppycrawlCheckstyleVersion}" 29 | 30 | implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" 31 | implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" 32 | testImplementation group: 'org.testng', name: 'testng', version: "${testngVersion}" 33 | } 34 | 35 | test { 36 | useTestNG() { 37 | suites 'src/test/resources/testng.xml' 38 | } 39 | testLogging.showStandardStreams = true 40 | testLogging { 41 | events "PASSED", "FAILED", "SKIPPED" 42 | afterSuite { desc, result -> 43 | if (!desc.parent) { // will match the outermost suite 44 | def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" 45 | def startItem = '| ', endItem = ' |' 46 | def repeatLength = startItem.length() + output.length() + endItem.length() 47 | println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) 48 | } 49 | } 50 | } 51 | finalizedBy jacocoTestReport 52 | } 53 | 54 | jacoco { 55 | toolVersion = "${jacocoVersion}" 56 | } 57 | 58 | jacocoTestReport { 59 | dependsOn test 60 | reports { 61 | xml.required = true 62 | } 63 | } 64 | 65 | checkstyle { 66 | toolVersion '7.8.2' 67 | configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") 68 | configProperties = ["suppressionFile" : file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] 69 | } 70 | 71 | checkstyleMain.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") 72 | checkstyleTest.dependsOn(":checkstyle:downloadCheckstyleRuleFiles") 73 | 74 | def excludePattern = '**/module-info.java' 75 | tasks.withType(Checkstyle) { 76 | exclude excludePattern 77 | } 78 | 79 | spotbugsMain { 80 | def classLoader = plugins["com.github.spotbugs"].class.classLoader 81 | def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence") 82 | def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort") 83 | effort = SpotBugsEffort.MAX 84 | reportLevel = SpotBugsConfidence.LOW 85 | reportsDir = file("$project.buildDir/reports/spotbugs") 86 | reports { 87 | html.enabled true 88 | text.enabled = true 89 | } 90 | def excludeFile = file('spotbugs-exclude.xml') 91 | if(excludeFile.exists()) { 92 | excludeFilter = excludeFile 93 | } 94 | } 95 | 96 | spotbugsTest { 97 | enabled = false 98 | } 99 | 100 | compileJava { 101 | doFirst { 102 | options.compilerArgs = [ 103 | '--module-path', classpath.asPath, 104 | ] 105 | classpath = files() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /load-tests/order_management_service/src/client_service.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/http; 18 | 19 | final http:Client securedEP = check new("https://localhost:9097", 20 | auth = { 21 | username: "ballerina", 22 | issuer: "wso2", 23 | audience: "vEwzbcasJVQm1jVYHUHCjhxZ4tYa", 24 | keyId: "NTAxZmMxNDMyZDg3MTU1ZGM0MzEzODJhZWI4NDNlZDU1OGFkNjFiMQ", 25 | customClaims: { 26 | "action": "add_order", "scp": "admin", "scp1": "admin", "scp2": "admin", "scp3": "admin", 27 | "scp4": "admin", "scp5": "admin", "scp6": "admin", "scp7": "admin", "scp8": "admin", "scp9": "admin", 28 | "scp10": "admin", "scp11": "admin", "scp12": "admin", "scp13": "admin", "scp14": "admin", 29 | "scp15": "admin", "scp16": "admin", "scp17": "admin", "scp18": "admin", "scp19": "admin", 30 | "scp20": "admin", "scp21": "admin", "scp22": "admin", "scp23": "admin", "scp24": "admin", 31 | "scp25": "admin", "scp26": "admin", "scp27": "admin", "scp28": "admin", "scp29": "admin", 32 | "scp30": "admin", "scp31": "admin", "scp32": "admin", "scp33": "admin", "scp34": "admin", 33 | "scp35": "admin", "scp36": "admin", "scp37": "admin", "scp38": "admin", "scp39": "admin", 34 | "scp40": "admin", "scp41": "admin", "scp42": "admin", "scp43": "admin", "scp44": "admin", 35 | "scp45": "admin", "scp46": "admin", "scp47": "admin", "scp50": "admin", "scp51": "admin", 36 | "scp52": "admin", "scp53": "admin", "scp54": "admin", "scp55": "admin", "scp56": "admin", 37 | "scp57": "admin", "scp60": "admin", "scp61": "admin", "scp62": "admin", "scp63": "admin", 38 | "scp64": "admin", "scp65": "admin", "scp66": "admin", "scp67": "admin", "scp70": "admin", 39 | "scp71": "admin", "scp72": "admin", "scp73": "admin", "scp74": "admin", "scp75": "admin", 40 | "scp76": "admin", "scp77": "admin", "scp80": "admin", "scp81": "admin", "scp82": "admin", 41 | "scp83": "admin", "scp84": "admin", "scp85": "admin", "scp86": "admin", "scp87": "admin", 42 | "scp90": "admin", "scp91": "admin", "scp92": "admin", "scp93": "admin", "scp94": "admin", 43 | "scp95": "admin", "scp96": "admin", "scp97": "admin", "scp100": "admin", "scp101": "admin", 44 | "scp102": "admin", "scp103": "admin", "scp104": "admin", "scp105": "admin", "scp106": "admin", 45 | "scp107": "admin", "scp110": "admin", "scp111": "admin", "scp112": "admin", "scp113": "admin", 46 | "scp114": "admin", "scp115": "admin", "scp116": "admin", "scp117": "admin", "scp12p": "admin", 47 | "scp121": "admin", "scp122": "admin", "scp123": "admin", "scp124": "admin", "scp125": "admin", 48 | "scp126": "admin", "scp127": "admin" 49 | }, 50 | expTime: 3600, 51 | signatureConfig: { 52 | config: { 53 | keyFile: "./resources/order_service/private.key" 54 | } 55 | } 56 | }, 57 | secureSocket = { 58 | cert: "./resources/order_service/public.crt" 59 | } 60 | ); 61 | 62 | isolated service /serv on new http:Listener(9098) { 63 | 64 | isolated resource function post .(@http:Payload json payload) returns json|error { 65 | return securedEP->post("/order", payload); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ballerina/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | import org.apache.tools.ant.taskdefs.condition.Os 19 | 20 | plugins { 21 | id 'io.ballerina.plugin' 22 | } 23 | 24 | description = 'Ballerina - Cache Ballerina Generator' 25 | 26 | def packageName = "cache" 27 | def packageOrg = "ballerina" 28 | def tomlVersion = stripBallerinaExtensionVersion("${project.version}") 29 | def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") 30 | def compilerPluginTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/CompilerPlugin.toml") 31 | def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") 32 | def compilerPluginTomlFile = new File("$project.projectDir/CompilerPlugin.toml") 33 | 34 | def stripBallerinaExtensionVersion(String extVersion) { 35 | if (extVersion.matches(project.ext.timestampedVersionRegex)) { 36 | def splitVersion = extVersion.split('-') 37 | if (splitVersion.length > 3) { 38 | def strippedValues = splitVersion[0..-4] 39 | return strippedValues.join('-') 40 | } else { 41 | return extVersion 42 | } 43 | } else { 44 | return extVersion.replace("${project.ext.snapshotVersion}", "") 45 | } 46 | } 47 | 48 | ballerina { 49 | packageOrganization = packageOrg 50 | module = packageName 51 | langVersion = ballerinaLangVersion 52 | } 53 | 54 | task updateTomlFiles { 55 | doLast { 56 | def newConfig = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) 57 | newConfig = newConfig.replace("@toml.version@", tomlVersion) 58 | ballerinaTomlFile.text = newConfig 59 | 60 | def newCompilerPluginToml = compilerPluginTomlFilePlaceHolder.text.replace("@project.version@", project.version) 61 | compilerPluginTomlFile.text = newCompilerPluginToml 62 | } 63 | } 64 | 65 | task commitTomlFiles { 66 | doLast { 67 | project.exec { 68 | ignoreExitValue true 69 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 70 | commandLine 'cmd', '/c', "git commit -m \"[Automated] Update native jar versions in toml files\" Ballerina.toml Dependencies.toml CompilerPlugin.toml" 71 | } else { 72 | commandLine 'sh', '-c', "git commit -m \"[Automated] Update native jar versions in toml files\" Ballerina.toml Dependencies.toml CompilerPlugin.toml" 73 | } 74 | } 75 | } 76 | } 77 | 78 | publishing { 79 | publications { 80 | maven(MavenPublication) { 81 | artifact source: createArtifactZip, extension: 'zip' 82 | } 83 | } 84 | 85 | repositories { 86 | maven { 87 | name = "GitHubPackages" 88 | url = uri("https://maven.pkg.github.com/ballerina-platform/module-${packageOrg}-${packageName}") 89 | credentials { 90 | username = System.getenv("publishUser") 91 | password = System.getenv("publishPAT") 92 | } 93 | } 94 | } 95 | } 96 | 97 | updateTomlFiles.dependsOn copyStdlibs 98 | 99 | build.dependsOn "generatePomFileForMavenPublication" 100 | build.dependsOn ":${packageName}-native:build" 101 | build.dependsOn ":${packageName}-compiler-plugin:build" 102 | 103 | test.dependsOn ":${packageName}-native:build" 104 | test.dependsOn ":${packageName}-compiler-plugin:build" 105 | -------------------------------------------------------------------------------- /load-tests/order_management_service/scripts/http-post-request.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | continue 17 | 18 | false 19 | -1 20 | 21 | ${__P(users)} 22 | ${__P(rampUpPeriod,60)} 23 | true 24 | ${__P(duration)} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | ${__P(payload)} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ${__P(host,localhost)} 42 | ${__P(port,9098)} 43 | ${__P(protocol,http)} 44 | 45 | ${__P(path)} 46 | POST 47 | true 48 | false 49 | true 50 | false 51 | 52 | HttpClient4 53 | 10000 54 | 120000 55 | 56 | 57 | 58 | 59 | 200 60 | 61 | 62 | Assertion.response_code 63 | false 64 | 16 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/cache/nativeimpl/concurrentlinkedhashmap/LinkedDeque.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | package io.ballerina.stdlib.cache.nativeimpl.concurrentlinkedhashmap; 19 | 20 | /** 21 | * This class provides a doubly-linked list that is optimized for the virtual 22 | * machine. The first and last elements are manipulated instead of a slightly 23 | * more convenient sentinel element to avoid the insertion of null checks with 24 | * NullPointerException throws in the byte code. The links to a removed 25 | * element are cleared to help a generational garbage collector if the 26 | * discarded elements inhabit more than one generation. 27 | * 28 | * @param the type of elements held in this collection 29 | */ 30 | public class LinkedDeque> { 31 | 32 | /** 33 | * Pointer to first node. Invariant: (first == null && last == null) || (first.prev == 34 | * null) 35 | */ 36 | E first = null; 37 | 38 | /** 39 | * Pointer to last node. Invariant: (first == null && last == null) || (last.next == 40 | * null) 41 | */ 42 | E last = null; 43 | 44 | public LinkedDeque() {} 45 | 46 | /** 47 | * Links the element to the back of the deque so that it becomes the last element. 48 | * 49 | * @param e the unlinked element 50 | */ 51 | private void linkLast(final E e) { 52 | final E l = last; 53 | last = e; 54 | 55 | if (l == null) { 56 | first = e; 57 | } else { 58 | l.setNext(e); 59 | e.setPrevious(l); 60 | } 61 | } 62 | 63 | /** Unlinks the non-null first element. */ 64 | E unlinkFirst() { 65 | final E f = first; 66 | final E next = f.getNext(); 67 | f.setNext(null); 68 | 69 | first = next; 70 | if (next == null) { 71 | last = null; 72 | } else { 73 | next.setPrevious(null); 74 | } 75 | return f; 76 | } 77 | 78 | /** Unlinks the non-null element. */ 79 | private void unlink(E e) { 80 | final E prev = e.getPrevious(); 81 | final E next = e.getNext(); 82 | 83 | if (prev == null) { 84 | first = next; 85 | } else { 86 | prev.setNext(next); 87 | e.setPrevious(null); 88 | } 89 | 90 | if (next == null) { 91 | last = prev; 92 | } else { 93 | next.setPrevious(prev); 94 | e.setNext(null); 95 | } 96 | } 97 | 98 | public boolean isEmpty() { 99 | return (first == null); 100 | } 101 | 102 | public boolean contains(Object o) { 103 | return (o instanceof Linked) && contains((Linked) o); 104 | } 105 | 106 | // A fast-path containment check 107 | boolean contains(Linked e) { 108 | return (e.getPrevious() != null) || (e.getNext() != null) || (e == first); 109 | } 110 | 111 | /** 112 | * Moves the element to the back of the deque so that it becomes the last element. 113 | * 114 | * @param e the linked element 115 | */ 116 | public void moveToBack(E e) { 117 | if (e != last) { 118 | unlink(e); 119 | linkLast(e); 120 | } 121 | } 122 | 123 | public boolean offerLast(E e) { 124 | if (contains(e)) { 125 | return false; 126 | } 127 | linkLast(e); 128 | return true; 129 | } 130 | 131 | public boolean add(E e) { 132 | return offerLast(e); 133 | } 134 | 135 | public E poll() { 136 | return pollFirst(); 137 | } 138 | 139 | public E pollFirst() { 140 | if (isEmpty()) { 141 | return null; 142 | } 143 | return unlinkFirst(); 144 | } 145 | 146 | @SuppressWarnings("unchecked") 147 | public boolean remove(Object o) { 148 | if (contains(o)) { 149 | unlink((E) o); 150 | return true; 151 | } 152 | return false; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /compiler-plugin-tests/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | plugins { 20 | id 'java' 21 | id 'checkstyle' 22 | id 'com.github.spotbugs' 23 | } 24 | 25 | description = 'Ballerina - Cache Compiler Plugin Tests' 26 | 27 | dependencies { 28 | checkstyle project(':checkstyle') 29 | checkstyle "com.puppycrawl.tools:checkstyle:${puppycrawlCheckstyleVersion}" 30 | 31 | implementation project(':cache-compiler-plugin') 32 | 33 | testImplementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" 34 | testImplementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" 35 | testImplementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" 36 | 37 | implementation group: 'org.testng', name: 'testng', version: "${testngVersion}" 38 | } 39 | 40 | tasks.withType(JavaCompile) { 41 | options.encoding = 'UTF-8' 42 | } 43 | 44 | sourceCompatibility = JavaVersion.VERSION_21 45 | 46 | test { 47 | systemProperty "ballerina.offline.flag", "true" 48 | useTestNG() { 49 | suites 'src/test/resources/testng.xml' 50 | } 51 | testLogging.showStandardStreams = true 52 | testLogging { 53 | events "PASSED", "FAILED", "SKIPPED" 54 | afterSuite { desc, result -> 55 | if (!desc.parent) { // will match the outermost suite 56 | def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} successes, ${result.failedTestCount} failures, ${result.skippedTestCount} skipped)" 57 | def startItem = '| ', endItem = ' |' 58 | def repeatLength = startItem.length() + output.length() + endItem.length() 59 | println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength)) 60 | } 61 | } 62 | } 63 | } 64 | 65 | spotbugsTest { 66 | def classLoader = plugins["com.github.spotbugs"].class.classLoader 67 | def SpotBugsConfidence = classLoader.findLoadedClass("com.github.spotbugs.snom.Confidence") 68 | def SpotBugsEffort = classLoader.findLoadedClass("com.github.spotbugs.snom.Effort") 69 | ignoreFailures = true 70 | effort = SpotBugsEffort.MAX 71 | reportLevel = SpotBugsConfidence.LOW 72 | reportsDir = file("$project.buildDir/reports/spotbugs") 73 | def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml") 74 | if (excludeFile.exists()) { 75 | it.excludeFilter = excludeFile 76 | } 77 | reports { 78 | text.enabled = true 79 | } 80 | } 81 | 82 | spotbugsMain { 83 | enabled false 84 | } 85 | 86 | task validateSpotbugs() { 87 | doLast { 88 | if (spotbugsMain.reports.size() > 0 && 89 | spotbugsMain.reports[0].destination.exists() && 90 | spotbugsMain.reports[0].destination.text.readLines().size() > 0) { 91 | spotbugsMain.reports[0].destination?.eachLine { 92 | println 'Failure: ' + it 93 | } 94 | throw new GradleException("Spotbugs rule violations were found."); 95 | } 96 | } 97 | } 98 | 99 | tasks.withType(Checkstyle) { 100 | exclude '**/module-info.java' 101 | } 102 | 103 | checkstyle { 104 | toolVersion "${project.puppycrawlCheckstyleVersion}" 105 | configFile rootProject.file("build-config/checkstyle/build/checkstyle.xml") 106 | configProperties = ["suppressionFile": file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] 107 | } 108 | 109 | checkstyleMain { 110 | enabled false 111 | } 112 | 113 | spotbugsTest.finalizedBy validateSpotbugs 114 | checkstyleTest.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' 115 | 116 | compileJava { 117 | doFirst { 118 | options.compilerArgs = [ 119 | '--module-path', classpath.asPath, 120 | ] 121 | classpath = files() 122 | } 123 | } 124 | 125 | test.dependsOn ":cache-ballerina:build" 126 | build.dependsOn ":cache-ballerina:build" 127 | -------------------------------------------------------------------------------- /docs/spec/spec.md: -------------------------------------------------------------------------------- 1 | # Specification: Ballerina Cache Library 2 | 3 | _Owners_: @daneshk @kalaiyarasiganeshalingam 4 | _Reviewers_: @daneshk 5 | _Created_: 2021/12/01 6 | _Updated_: 2022/02/17 7 | _Edition_: Swan Lake 8 | 9 | ## Introduction 10 | This is the specification for the Cache standard library of [Ballerina language](https://ballerina.io/), which provides a mechanism to manage frequently accessed data in-memory by using a semi-persistent mapping from key to value. 11 | 12 | The Cache library specification has evolved and may continue to evolve in the future. The released versions of the specification can be found under the relevant GitHub tag. 13 | 14 | If you have any feedback or suggestions about the library, start a discussion via a [GitHub issue](https://github.com/ballerina-platform/ballerina-standard-library/issues) or in the [Discord server](https://discord.gg/ballerinalang). Based on the outcome of the discussion, the specification and implementation can be updated. Community feedback is always welcome. Any accepted proposal, which affects the specification is stored under `/docs/proposals`. Proposals under discussion can be found with the label `type/proposal` in GitHub. 15 | 16 | The conforming implementation of the specification is released and included in the distribution. Any deviation from the specification is considered a bug. 17 | 18 | ## Contents 19 | 1. [Overview](#1-overview) 20 | 2. [Eviction](#2-eviction) 21 | 3. [Operations](#3-operations) 22 | * 3.1. [Put](#31-put) 23 | * 3.2. [Get](#32-get) 24 | * 3.3. [invalidate](#33-invalidate) 25 | * 3.4. [invalidateAll](#34-invalidateall) 26 | * 3.5. [hasKey](#35-haskey) 27 | * 3.6. [keys](#36-keys) 28 | * 3.7. [size](#37-size) 29 | * 3.8. [capacity](#38-capacity) 30 | 31 | ## 1. Overview 32 | This specification elaborates functionalities available in the Cache library. 33 | 34 | This library is based on the [Least Recently Used (LRU)](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)) algorithm and can be initialized by configuring the following properties: 35 | 36 | - capacity - Maximum number of entries allowed in the cache. 37 | - evictionFactor - The factor by which the entries will be evicted once the cache is full. 38 | - evictionPolicy - The policy which is used to evict entries once the cache is full. 39 | - defaultMaxAge - The max-age (in second) which all the cache entries are valid. '-1' means, the entries are valid forever. 40 | - cleanupInterval - The interval (in seconds) of the recurrence task, which will clean up the cache. 41 | 42 | ## 2. Eviction 43 | The cache eviction is a process to eliminate entry/entries from the cache by following the mechanism. The entries will be evicted in case of the following scenarios: 44 | 45 | - When getting the entry, if the returning cache entry has expired, it gets removed. 46 | - When putting the entry, if the cache size has reached its capacity, the number of entries gets removed. Entries are eliminated in terms of LRU policy, and the number of entries is also calculated by the capacity of the cache and the eviction factor. 47 | - If `cleanupInterval` (optional property of the `cacheConfig`) is configured, the recurrence task will remove the expired cache entries based on the configured interval. 48 | 49 | ## 3. Operations 50 | The cache defines the most basic operations on a collection of cache entries, which entails basic reading, writing, and deleting individual cache items. This is thread-safe. Hence, data can be safely accessed by multiple concurrent threads. 51 | 52 | ### 3.1. Put 53 | This adds the given key-value pair to the cache with an entry expiration time. The value can be in any of the ballerina types, but it is not allowed 54 | to insert `()` as the value of the cache since it doesn't make sense to have nil value. If the cache previously contained a value associated with the provided key, the old value will be replaced by the newly-provided value. 55 | ```ballerina 56 | check cache.put("key", "value"); 57 | ``` 58 | 59 | ### 3.2. Get 60 | This is used to fetch the cached value associated with the provided key. 61 | 62 | ```ballerina 63 | any value = check cache.get("key"); 64 | ``` 65 | 66 | ### 3.3. Invalidate 67 | This is used to discard a cached entry from the cache by its unique key. 68 | 69 | ```ballerina 70 | check cache.invalidate("key"); 71 | ``` 72 | 73 | ### 3.4. InvalidateAll 74 | This is used to discard all the cached values from the cache. 75 | 76 | ```ballerina 77 | check cache.invalidateAll(); 78 | ``` 79 | 80 | ### 3.5. HasKey 81 | This is used to check whether the given key has an associated cached value. 82 | 83 | ```ballerina 84 | boolean result = cache.hasKey("key"); 85 | ``` 86 | 87 | ### 3.6. Keys 88 | This is used to get a list of all the keys in the cache. 89 | 90 | ```ballerina 91 | string[] keys = cache.keys(); 92 | ``` 93 | 94 | ### 3.7. Size 95 | This is used to get the size of the cache. 96 | ```ballerina 97 | int result = cache.size(); 98 | ``` 99 | 100 | ### 3.8. Capacity 101 | This is used to get the capacity of the cache. 102 | ```ballerina 103 | int result = cache.capacity(); 104 | ``` 105 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/cache/nativeimpl/Cache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package io.ballerina.stdlib.cache.nativeimpl; 20 | 21 | import io.ballerina.runtime.api.creators.ValueCreator; 22 | import io.ballerina.runtime.api.utils.StringUtils; 23 | import io.ballerina.runtime.api.values.BArray; 24 | import io.ballerina.runtime.api.values.BDecimal; 25 | import io.ballerina.runtime.api.values.BMap; 26 | import io.ballerina.runtime.api.values.BObject; 27 | import io.ballerina.runtime.api.values.BString; 28 | import io.ballerina.stdlib.cache.nativeimpl.concurrentlinkedhashmap.ConcurrentLinkedHashMap; 29 | 30 | import java.util.Map; 31 | 32 | /** 33 | * Class to handle ballerina external functions in Cache library. 34 | * 35 | * @since 2.0.0 36 | */ 37 | public class Cache { 38 | 39 | private static ConcurrentLinkedHashMap> cacheMap; 40 | private static final String MAX_CAPACITY = "maxCapacity"; 41 | private static final String EVICTION_FACTOR = "evictionFactor"; 42 | private static final String EXPIRE_TIME = "expTime"; 43 | private static final String CACHE = "CACHE"; 44 | 45 | private Cache() {} 46 | 47 | public static void externInit(BObject cache) { 48 | int capacity = (int) cache.getIntValue(StringUtils.fromString(MAX_CAPACITY)); 49 | cacheMap = new ConcurrentLinkedHashMap<>(capacity); 50 | cache.addNativeData(CACHE, cacheMap); 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | public static void externPut(BObject cache, BString key, BMap value) { 55 | int capacity = (int) cache.getIntValue(StringUtils.fromString(MAX_CAPACITY)); 56 | float evictionFactor = (float) cache.getFloatValue(StringUtils.fromString(EVICTION_FACTOR)); 57 | cacheMap = (ConcurrentLinkedHashMap>) cache.getNativeData(CACHE); 58 | if (cacheMap.size() >= capacity) { 59 | int evictionKeysCount = (int) Math.ceil(capacity * evictionFactor); 60 | cacheMap.setCapacity((capacity - evictionKeysCount)); 61 | cacheMap.setCapacity(capacity); 62 | } 63 | cacheMap.put(key, value); 64 | } 65 | 66 | @SuppressWarnings("unchecked") 67 | public static BMap externGet(BObject cache, BString key, BDecimal currentTime) { 68 | cacheMap = (ConcurrentLinkedHashMap>) cache.getNativeData(CACHE); 69 | BMap value = cacheMap.get(key); 70 | if (value != null && value.get(StringUtils.fromString(EXPIRE_TIME)) != null) { 71 | Long time = ((BDecimal) value.get(StringUtils.fromString(EXPIRE_TIME))).decimalValue().longValue(); 72 | if (time != -1 && time <= currentTime.decimalValue().longValue()) { 73 | cacheMap.remove(key); 74 | return null; 75 | } 76 | } 77 | return value; 78 | } 79 | 80 | @SuppressWarnings("unchecked") 81 | public static void externRemove(BObject cache, BString key) { 82 | cacheMap = (ConcurrentLinkedHashMap>) cache.getNativeData(CACHE); 83 | cacheMap.remove(key); 84 | } 85 | 86 | @SuppressWarnings("unchecked") 87 | public static void externRemoveAll(BObject cache) { 88 | cacheMap = (ConcurrentLinkedHashMap>) cache.getNativeData(CACHE); 89 | cacheMap.clear(); 90 | } 91 | 92 | @SuppressWarnings("unchecked") 93 | public static boolean externHasKey(BObject cache, BString key) { 94 | cacheMap = (ConcurrentLinkedHashMap>) cache.getNativeData(CACHE); 95 | return cacheMap.containsKey(key); 96 | } 97 | 98 | @SuppressWarnings("unchecked") 99 | public static BArray externKeys(BObject cache) { 100 | cacheMap = (ConcurrentLinkedHashMap>) cache.getNativeData(CACHE); 101 | return ValueCreator.createArrayValue(cacheMap.keySet().toArray(new BString[0])); 102 | } 103 | 104 | @SuppressWarnings("unchecked") 105 | public static int externSize(BObject cache) { 106 | cacheMap = (ConcurrentLinkedHashMap>) cache.getNativeData(CACHE); 107 | return cacheMap.size(); 108 | } 109 | 110 | @SuppressWarnings("unchecked") 111 | public static void externCleanUp(BObject cache, BDecimal currentTime) { 112 | cacheMap = (ConcurrentLinkedHashMap>) cache.getNativeData(CACHE); 113 | for (Map.Entry> entry : cacheMap.entrySet()) { 114 | BMap value = entry.getValue(); 115 | Long time = ((BDecimal) value.get(StringUtils.fromString(EXPIRE_TIME))).decimalValue().longValue(); 116 | if (time != -1 && time <= currentTime.decimalValue().longValue()) { 117 | cacheMap.remove(entry.getKey()); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ballerina Cache Library 2 | =================== 3 | 4 | [![Build](https://github.com/ballerina-platform/module-ballerina-cache/actions/workflows/build-timestamped-master.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerina-cache/actions/workflows/build-timestamped-master.yml) 5 | [![codecov](https://codecov.io/gh/ballerina-platform/module-ballerina-cache/branch/master/graph/badge.svg)](https://codecov.io/gh/ballerina-platform/module-ballerina-cache) 6 | [![Trivy](https://github.com/ballerina-platform/module-ballerina-cache/actions/workflows/trivy-scan.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerina-cache/actions/workflows/trivy-scan.yml) 7 | [![GraalVM Check](https://github.com/ballerina-platform/module-ballerina-cache/actions/workflows/build-with-bal-test-graalvm.yml/badge.svg)](https://github.com/ballerina-platform/module-ballerina-cache/actions/workflows/build-with-bal-test-graalvm.yml) 8 | [![GitHub Last Commit](https://img.shields.io/github/last-commit/ballerina-platform/module-ballerina-cache.svg)](https://github.com/ballerina-platform/module-ballerina-cache/commits/master) 9 | [![Github issues](https://img.shields.io/github/issues/ballerina-platform/ballerina-standard-library/module/cache.svg?label=Open%20Issues)](https://github.com/ballerina-platform/ballerina-standard-library/labels/module%2Fcache) 10 | 11 | This library provides APIs for in-memory caching by using a semi-persistent mapping from keys to values. Cache entries are added to the cache manually and are stored in the cache until either evicted or invalidated manually. 12 | 13 | This is based on the Least Recently Used (LRU) eviction algorithm by using a `map` data structure and defining the most basic operations on a collection of cache entries, which entails basic reading, writing, and deleting individual cache items. 14 | It does not allow the `()` as a key or value of the cache and entries can be accessed safely by multiple concurrent threads as it is thread-safe. 15 | 16 | The cache can be defined with optional configurations as follows: 17 | ```ballerina 18 | cache:Cache cache = new (capacity = 10, evictionFactor = 0.2, defaultMaxAge = 0.5, cleanupInterval = 1); 19 | ``` 20 | 21 | The Cache entries will be evicted in case of the following scenarios: 22 | 23 | - When using the `get` API, if the returning cache entry has expired, it gets removed. 24 | - When using the `put` API, if the cache size has reached its capacity, the number of entries that get removed will be based on the `eviction policy` and the `eviction factor`. 25 | - If `cleanupInterval` (optional property) is configured, the recurrence task will remove the expired cache entries based on the configured interval. The main benefit of this property is that you can optimize the memory usage while adding some additional CPU costs and vice versa. The default behaviour is the CPU-optimized method. 26 | 27 | The `cache:AbstractCache` object has the common APIs for the caching functionalities. Custom implementations of the cache can be done with different data storages like file, database, etc., with the structural equivalency to the `cache:AbstractCacheObject` object. 28 | 29 | ```ballerina 30 | public type AbstractCache object { 31 | public function put(string key, any value, int maxAgeInSeconds) returns Error?; 32 | public function get(string key) returns any|Error; 33 | public function invalidate(string key) returns Error?; 34 | public function invalidateAll() returns Error?; 35 | public function hasKey(string key) returns boolean; 36 | public function keys() returns string[]; 37 | public function size() returns int; 38 | public function capacity() returns int; 39 | }; 40 | ``` 41 | 42 | For example demonstrations of the usage, go to [Ballerina By Examples](https://ballerina.io/learn/by-example). 43 | 44 | ## Issues and projects 45 | 46 | Issues and Project are disabled for this repository as this is part of the Ballerina Standard Library. To report bugs, request new features, start new discussions, view project boards, etc. please visit Ballerina Standard Library [parent repository](https://github.com/ballerina-platform/ballerina-standard-library). 47 | 48 | This repository only contains the source code for the package. 49 | 50 | ## Build from the source 51 | 52 | ### Set up the prerequisites 53 | 54 | 1. Download and install Java SE Development Kit (JDK) version 21 (from one of the following locations). 55 | * [Oracle](https://www.oracle.com/java/technologies/downloads/) 56 | 57 | * [OpenJDK](https://adoptium.net/) 58 | 59 | > **Note:** Set the JAVA_HOME environment variable to the path name of the directory into which you installed JDK. 60 | 61 | ### Build the source 62 | 63 | Execute the commands below to build from the source. 64 | 65 | 1. To build the library: 66 | 67 | ./gradlew clean build 68 | 69 | 2. To run the tests: 70 | 71 | ./gradlew clean test 72 | 73 | 3. To build the package without tests: 74 | 75 | ./gradlew clean build -x test 76 | 77 | 4. To run a group of tests: 78 | 79 | ./gradlew clean test -Pgroups= 80 | 81 | 5. To debug package implementation: 82 | 83 | ./gradlew clean build -Pdebug= 84 | 85 | 6. To debug the package with Ballerina language: 86 | 87 | ./gradlew clean build -PbalJavaDebug= 88 | 89 | 7. Publish ZIP artifact to the local `.m2` repository: 90 | 91 | ./gradlew clean build publishToMavenLocal 92 | 93 | 94 | 8. Publish the generated artifacts to the local Ballerina central repository: 95 | 96 | ./gradlew clean build -PpublishToLocalCentral=true 97 | 98 | 9. Publish the generated artifacts to the Ballerina central repository: 99 | 100 | ./gradlew clean build -PpublishToCentral=true 101 | 102 | ## Contribute to Ballerina 103 | 104 | As an open source project, Ballerina welcomes contributions from the community. 105 | 106 | For more information, go to the [contribution guidelines](https://github.com/ballerina-platform/ballerina-lang/blob/master/CONTRIBUTING.md). 107 | 108 | ## Code of conduct 109 | 110 | All contributors are encouraged to read the [Ballerina Code of Conduct](https://ballerina.io/code-of-conduct). 111 | 112 | ## Useful links 113 | 114 | * For more information go to the [`cache` library](https://lib.ballerina.io/ballerina/cache/latest). 115 | * For example demonstrations of the usage, go to [Ballerina By Examples](https://ballerina.io/learn/by-example/). 116 | * Chat live with us via our [Discord server](https://discord.gg/ballerinalang). 117 | * Post all technical questions on Stack Overflow with the [#ballerina](https://stackoverflow.com/questions/tagged/ballerina) tag. 118 | -------------------------------------------------------------------------------- /compiler-plugin-tests/src/test/java/io/ballerina/stdlib/cache/compiler/CompilerPluginTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | 19 | package io.ballerina.stdlib.cache.compiler; 20 | 21 | import io.ballerina.projects.DiagnosticResult; 22 | import io.ballerina.projects.Package; 23 | import io.ballerina.projects.ProjectEnvironmentBuilder; 24 | import io.ballerina.projects.directory.BuildProject; 25 | import io.ballerina.projects.environment.Environment; 26 | import io.ballerina.projects.environment.EnvironmentBuilder; 27 | import io.ballerina.tools.diagnostics.Diagnostic; 28 | import io.ballerina.tools.diagnostics.DiagnosticInfo; 29 | import io.ballerina.tools.diagnostics.DiagnosticSeverity; 30 | import org.testng.Assert; 31 | import org.testng.annotations.Test; 32 | 33 | import java.nio.file.Path; 34 | import java.nio.file.Paths; 35 | import java.util.List; 36 | import java.util.stream.Collectors; 37 | 38 | /** 39 | * Tests the custom cache compiler plugin. 40 | */ 41 | public class CompilerPluginTest { 42 | 43 | private static ProjectEnvironmentBuilder getEnvironmentBuilder() { 44 | Path distributionPath = Paths.get("../", "target", "ballerina-runtime") 45 | .toAbsolutePath(); 46 | Environment environment = EnvironmentBuilder.getBuilder().setBallerinaHome(distributionPath).build(); 47 | return ProjectEnvironmentBuilder.getBuilder(environment); 48 | } 49 | 50 | private Package loadPackage(String path) { 51 | Path projectDirPath = Paths.get("src", "test", "resources", "diagnostics"). 52 | toAbsolutePath().resolve(path); 53 | BuildProject project = BuildProject.load(getEnvironmentBuilder(), projectDirPath); 54 | return project.currentPackage(); 55 | } 56 | 57 | @Test 58 | public void testInvalidConfig1() { 59 | DiagnosticResult diagnosticResult = loadPackage("sample1").getCompilation().diagnosticResult(); 60 | List errorDiagnosticsList = diagnosticResult.diagnostics().stream() 61 | .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) 62 | .collect(Collectors.toList()); 63 | assertValues(errorDiagnosticsList); 64 | } 65 | 66 | @Test 67 | public void testInvalidConfig2() { 68 | DiagnosticResult diagnosticResult = loadPackage("sample2").getCompilation().diagnosticResult(); 69 | List errorDiagnosticsList = diagnosticResult.diagnostics().stream() 70 | .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) 71 | .collect(Collectors.toList()); 72 | assertValues(errorDiagnosticsList); 73 | } 74 | 75 | @Test 76 | public void testInvalidConfig3() { 77 | DiagnosticResult diagnosticResult = loadPackage("sample3").getCompilation().diagnosticResult(); 78 | List errorDiagnosticsList = diagnosticResult.diagnostics().stream() 79 | .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) 80 | .collect(Collectors.toList()); 81 | assertValues(errorDiagnosticsList); 82 | } 83 | 84 | @Test 85 | public void testInvalidConfig4() { 86 | DiagnosticResult diagnosticResult = loadPackage("sample4").getCompilation().diagnosticResult(); 87 | List errorDiagnosticsList = diagnosticResult.diagnostics().stream() 88 | .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) 89 | .collect(Collectors.toList()); 90 | Assert.assertEquals(errorDiagnosticsList.size(), 2); 91 | DiagnosticInfo invalidCleanupInterval = errorDiagnosticsList.get(0).diagnosticInfo(); 92 | Assert.assertEquals(invalidCleanupInterval.code(), DiagnosticsCodes.CACHE_104.getErrorCode()); 93 | Assert.assertEquals(invalidCleanupInterval.messageFormat(), 94 | "invalid value: a greater than zero value is expected"); 95 | 96 | DiagnosticInfo invalidPolicy = errorDiagnosticsList.get(1).diagnosticInfo(); 97 | Assert.assertEquals(invalidPolicy.code(), DiagnosticsCodes.CACHE_105.getErrorCode()); 98 | Assert.assertEquals(invalidPolicy.messageFormat(), 99 | "invalid value: only 'cache:LRU' value is supported"); 100 | } 101 | 102 | @Test 103 | public void testConfigWithOutVariables() { 104 | DiagnosticResult diagnosticResult = loadPackage("sample5").getCompilation().diagnosticResult(); 105 | List errorDiagnosticsList = diagnosticResult.diagnostics().stream() 106 | .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) 107 | .collect(Collectors.toList()); 108 | Assert.assertEquals(errorDiagnosticsList.size(), 0); 109 | } 110 | 111 | @Test 112 | public void testConfigWithConstants() { 113 | DiagnosticResult diagnosticResult = loadPackage("sample6").getCompilation().diagnosticResult(); 114 | List errorDiagnosticsList = diagnosticResult.diagnostics().stream() 115 | .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) 116 | .collect(Collectors.toList()); 117 | Assert.assertEquals(errorDiagnosticsList.size(), 0); 118 | } 119 | 120 | @Test(description = "Tests whether there are no compilation failures for constants and configurables as included " + 121 | "params. Those validations will be ignored.") 122 | public void testConfigWithIncludedParams() { 123 | DiagnosticResult diagnosticResult = loadPackage("sample7").getCompilation().diagnosticResult(); 124 | List errorDiagnosticsList = diagnosticResult.diagnostics().stream() 125 | .filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR)) 126 | .collect(Collectors.toList()); 127 | Assert.assertEquals(errorDiagnosticsList.size(), 0); 128 | } 129 | 130 | private void assertValues(List errorDiagnosticsList) { 131 | long availableErrors = errorDiagnosticsList.size(); 132 | Assert.assertEquals(availableErrors, 5); 133 | DiagnosticInfo invalidCapacity = errorDiagnosticsList.get(0).diagnosticInfo(); 134 | Assert.assertEquals(invalidCapacity.code(), DiagnosticsCodes.CACHE_101.getErrorCode()); 135 | Assert.assertEquals(invalidCapacity.messageFormat(), 136 | "invalid value: a greater than zero value is expected"); 137 | 138 | DiagnosticInfo invalidEvictionFactor = errorDiagnosticsList.get(1).diagnosticInfo(); 139 | Assert.assertEquals(invalidEvictionFactor.code(), DiagnosticsCodes.CACHE_102.getErrorCode()); 140 | Assert.assertEquals(invalidEvictionFactor.messageFormat(), 141 | "invalid value: a value between 0 (exclusive) and 1 (inclusive) is expected"); 142 | 143 | 144 | DiagnosticInfo invalidDefaultMaxAge = errorDiagnosticsList.get(2).diagnosticInfo(); 145 | Assert.assertEquals(invalidDefaultMaxAge.code(), DiagnosticsCodes.CACHE_103.getErrorCode()); 146 | Assert.assertEquals(invalidDefaultMaxAge.messageFormat(), 147 | "invalid value: a greater than 0 value or -1(to indicate forever valid) value is expected"); 148 | 149 | DiagnosticInfo invalidCleanupInterval = errorDiagnosticsList.get(3).diagnosticInfo(); 150 | Assert.assertEquals(invalidCleanupInterval.code(), DiagnosticsCodes.CACHE_104.getErrorCode()); 151 | Assert.assertEquals(invalidCleanupInterval.messageFormat(), 152 | "invalid value: a greater than zero value is expected"); 153 | 154 | DiagnosticInfo invalidPolicy = errorDiagnosticsList.get(4).diagnosticInfo(); 155 | Assert.assertEquals(invalidPolicy.code(), DiagnosticsCodes.CACHE_105.getErrorCode()); 156 | Assert.assertEquals(invalidPolicy.messageFormat(), 157 | "invalid value: only 'cache:LRU' value is supported"); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Use the maximum available, or set MAX_FD != -1 to use that value. 89 | MAX_FD=maximum 90 | 91 | warn () { 92 | echo "$*" 93 | } >&2 94 | 95 | die () { 96 | echo 97 | echo "$*" 98 | echo 99 | exit 1 100 | } >&2 101 | 102 | # OS specific support (must be 'true' or 'false'). 103 | cygwin=false 104 | msys=false 105 | darwin=false 106 | nonstop=false 107 | case "$( uname )" in #( 108 | CYGWIN* ) cygwin=true ;; #( 109 | Darwin* ) darwin=true ;; #( 110 | MSYS* | MINGW* ) msys=true ;; #( 111 | NONSTOP* ) nonstop=true ;; 112 | esac 113 | 114 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 115 | 116 | 117 | # Determine the Java command to use to start the JVM. 118 | if [ -n "$JAVA_HOME" ] ; then 119 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 120 | # IBM's JDK on AIX uses strange locations for the executables 121 | JAVACMD=$JAVA_HOME/jre/sh/java 122 | else 123 | JAVACMD=$JAVA_HOME/bin/java 124 | fi 125 | if [ ! -x "$JAVACMD" ] ; then 126 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 127 | 128 | Please set the JAVA_HOME variable in your environment to match the 129 | location of your Java installation." 130 | fi 131 | else 132 | JAVACMD=java 133 | if ! command -v java >/dev/null 2>&1 134 | then 135 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 136 | 137 | Please set the JAVA_HOME variable in your environment to match the 138 | location of your Java installation." 139 | fi 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | 201 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 202 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 203 | 204 | # Collect all arguments for the java command; 205 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 206 | # shell script including quotes and variable substitutions, so put them in 207 | # double quotes to make sure that they get re-expanded; and 208 | # * put everything else in single quotes, so that it's not re-expanded. 209 | 210 | set -- \ 211 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 212 | -classpath "$CLASSPATH" \ 213 | org.gradle.wrapper.GradleWrapperMain \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /ballerina/cache.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/constraint; 18 | import ballerina/jballerina.java; 19 | import ballerina/task; 20 | import ballerina/time; 21 | 22 | # Represents configurations for the `cache:Cache` object. 23 | # 24 | # + capacity - Maximum number of entries allowed in the cache 25 | # + evictionFactor - The factor by which the entries will be evicted once the cache is full 26 | # + evictionPolicy - The policy which is used to evict entries once the cache is full 27 | # + defaultMaxAge - The max-age in seconds which all the cache entries are valid. '-1' means, the entries are 28 | # valid forever. This will be overwritten by the `maxAge` property set when inserting item into 29 | # the cache 30 | # + cleanupInterval - Interval (in seconds) of the timer task, which will clean up the cache 31 | public type CacheConfig record {| 32 | @constraint:Int { 33 | minValue: 1 34 | } 35 | int capacity = 100; 36 | @constraint:Float { 37 | minValueExclusive: 0, 38 | maxValue: 1 39 | } 40 | float evictionFactor = 0.25; 41 | EvictionPolicy evictionPolicy = LRU; 42 | @constraint:Number { 43 | minValue: -1 44 | } 45 | decimal defaultMaxAge = -1; 46 | @constraint:Number { 47 | minValueExclusive: 0 48 | } 49 | decimal cleanupInterval?; 50 | |}; 51 | 52 | # Possible types of eviction policy that can be passed into the `EvictionPolicy`. 53 | public enum EvictionPolicy { 54 | LRU 55 | } 56 | 57 | type CacheEntry record {| 58 | any data; 59 | decimal expTime; // exp time since epoch. calculated based on the `maxAge` parameter when inserting to map 60 | |}; 61 | 62 | // Cleanup service which cleans the cache entries periodically. 63 | boolean cleanupInProgress = false; 64 | 65 | class Cleanup { 66 | 67 | *task:Job; 68 | private Cache cache; 69 | 70 | public function execute() { 71 | // This check will skip the processes triggered while the clean up in progress. 72 | if !cleanupInProgress { 73 | cleanupInProgress = true; 74 | time:Utc currentUtc = time:utcNow(); 75 | externCleanUp(self.cache, currentUtc[0] + currentUtc[1]); 76 | cleanupInProgress = false; 77 | } 78 | } 79 | 80 | public isolated function init(Cache cache) { 81 | self.cache = cache; 82 | } 83 | } 84 | 85 | # The `cache:Cache` object, which is used for all the cache-related operations. It is not recommended to insert `()` 86 | # as the value of the cache since it doesn't make any sense to cache a nil. 87 | public isolated class Cache { 88 | 89 | *AbstractCache; 90 | 91 | private final int maxCapacity; 92 | private final EvictionPolicy evictionPolicy; 93 | private final float evictionFactor; 94 | private final decimal defaultMaxAge; 95 | 96 | # Initializes new `cache:Cache` instance. 97 | # ```ballerina 98 | # cache:Cache cache = new(capacity = 10, evictionFactor = 0.2); 99 | # ``` 100 | # 101 | # + cacheConfig - Configurations for the `cache:Cache` object 102 | public isolated function init(*CacheConfig cacheConfig) { 103 | CacheConfig|error validatedConfig = constraint:validate(cacheConfig); 104 | if validatedConfig is error { 105 | panic prepareError(validatedConfig.message()); 106 | } 107 | self.maxCapacity = validatedConfig.capacity; 108 | self.evictionPolicy = validatedConfig.evictionPolicy; 109 | self.evictionFactor = validatedConfig.evictionFactor; 110 | self.defaultMaxAge = validatedConfig.defaultMaxAge; 111 | 112 | externInit(self); 113 | decimal? interval = cacheConfig?.cleanupInterval; 114 | if interval is decimal { 115 | time:Utc currentUtc = time:utcNow(); 116 | time:Utc newTime = time:utcAddSeconds(currentUtc, interval); 117 | time:Civil time = time:utcToCivil(newTime); 118 | var result = task:scheduleJobRecurByFrequency(new Cleanup(self), interval, startTime = time); 119 | if (result is task:Error) { 120 | panic prepareError(string `Failed to schedule the cleanup task: ${result.message()}`); 121 | } 122 | } 123 | } 124 | 125 | # Adds the given key value pair to the cache. If the cache previously contained a value associated with the 126 | # provided key, the old value will be replaced by the newly-provided value. 127 | # ```ballerina 128 | # check cache.put("Hello", "Ballerina"); 129 | # ``` 130 | # 131 | # + key - Key of the value to be cached 132 | # + value - Value to be cached. Value should not be `()` 133 | # + maxAge - The time in seconds for which the cache entry is valid. If the value is '-1', the entry is 134 | # valid forever. 135 | # + return - `()` if successfully added to the cache or a `cache:Error` if a `()` value is inserted to the cache. 136 | public isolated function put(string key, any value, decimal maxAge = -1) returns Error? { 137 | if value is () { 138 | return prepareError("Unsupported cache value '()' for the key: " + key + "."); 139 | } 140 | 141 | time:Utc currentUtc = time:utcNow(); 142 | // Calculate the `expTime` of the cache entry based on the `maxAgeInSeconds` property and 143 | // `defaultMaxAge` property. 144 | decimal calculatedExpTime = -1; 145 | if maxAge != -1d && maxAge > 0d { 146 | time:Utc newTime = time:utcAddSeconds(currentUtc, maxAge); 147 | calculatedExpTime = newTime[0] + newTime[1]; 148 | } else { 149 | if self.defaultMaxAge != -1d { 150 | time:Utc newTime = time:utcAddSeconds(currentUtc, self.defaultMaxAge); 151 | calculatedExpTime = newTime[0] + newTime[1]; 152 | } 153 | } 154 | 155 | CacheEntry entry = { 156 | data: value, 157 | expTime: calculatedExpTime 158 | }; 159 | return externPut(self, key, entry); 160 | } 161 | 162 | # Returns the cached value associated with the provided key. 163 | # ```ballerina 164 | # any value = check cache.get(key); 165 | # ``` 166 | # 167 | # + key - Key of the cached value, which should be retrieved 168 | # + return - The cached value associated with the provided key or a `cache:Error` if the provided cache key is not 169 | # exisiting in the cache or any error occurred while retrieving the value from the cache. 170 | public isolated function get(string key) returns any|Error { 171 | time:Utc currentUtc = time:utcNow(); 172 | any? entry = externGet(self, key, currentUtc[0] + currentUtc[1]); 173 | if entry is CacheEntry { 174 | return entry.data; 175 | } else { 176 | return prepareError("Cache entry from the given key: " + key + ", is not available."); 177 | } 178 | } 179 | 180 | # Discards a cached value from the cache. 181 | # ```ballerina 182 | # check cache.invalidate(key); 183 | # ``` 184 | # 185 | # + key - Key of the cache value, which needs to be discarded from the cache 186 | # + return - `()` if successfully discarded the value or a `cache:Error` if the provided cache key is not present 187 | # in the cache 188 | public isolated function invalidate(string key) returns Error? { 189 | if !self.hasKey(key) { 190 | return prepareError("Cache entry from the given key: " + key + ", is not available."); 191 | } 192 | externRemove(self, key); 193 | } 194 | 195 | # Discards all the cached values from the cache. 196 | # ```ballerina 197 | # check cache.invalidateAll(); 198 | # ``` 199 | # 200 | # + return - `()` if successfully discarded all the values from the cache or a `cache:Error` if any error 201 | # occurred while discarding all the values from the cache. 202 | public isolated function invalidateAll() returns Error? { 203 | externRemoveAll(self); 204 | } 205 | 206 | # Checks whether the given key has an associated cached value. 207 | # ```ballerina 208 | # boolean result = cache.hasKey(key); 209 | # ``` 210 | # 211 | # + key - The key to be checked in the cache 212 | # + return - `true` if a cached value is available for the provided key or `false` if there is no cached value 213 | # associated for the given key 214 | public isolated function hasKey(string key) returns boolean { 215 | return externHasKey(self, key); 216 | } 217 | 218 | # Returns a list of all the keys from the cache. 219 | # ```ballerina 220 | # string[] keys = cache.keys(); 221 | # ``` 222 | # 223 | # + return - Array of all the keys from the cache 224 | public isolated function keys() returns string[] { 225 | return externKeys(self); 226 | } 227 | 228 | # Returns the size of the cache. 229 | # ```ballerina 230 | # int result = cache.size(); 231 | # ``` 232 | # 233 | # + return - The size of the cache 234 | public isolated function size() returns int { 235 | return externSize(self); 236 | } 237 | 238 | # Returns the capacity of the cache. 239 | # ```ballerina 240 | # int result = cache.capacity(); 241 | # ``` 242 | # 243 | # + return - The capacity of the cache 244 | public isolated function capacity() returns int { 245 | return self.maxCapacity; 246 | } 247 | } 248 | 249 | isolated function externInit(Cache cache) = @java:Method { 250 | 'class: "io.ballerina.stdlib.cache.nativeimpl.Cache" 251 | } external; 252 | 253 | isolated function externRemoveAll(Cache cache) = @java:Method { 254 | 'class: "io.ballerina.stdlib.cache.nativeimpl.Cache" 255 | } external; 256 | 257 | isolated function externHasKey(Cache cache, string key) returns boolean = @java:Method { 258 | 'class: "io.ballerina.stdlib.cache.nativeimpl.Cache" 259 | } external; 260 | 261 | isolated function externKeys(Cache cache) returns string[] = @java:Method { 262 | 'class: "io.ballerina.stdlib.cache.nativeimpl.Cache" 263 | } external; 264 | 265 | isolated function externSize(Cache cache) returns int = @java:Method { 266 | 'class: "io.ballerina.stdlib.cache.nativeimpl.Cache" 267 | } external; 268 | 269 | isolated function externPut(Cache cache, string key, any value) = @java:Method { 270 | 'class: "io.ballerina.stdlib.cache.nativeimpl.Cache" 271 | } external; 272 | 273 | isolated function externGet(Cache cache, string key, decimal currentTime) returns CacheEntry? = @java:Method { 274 | 'class: "io.ballerina.stdlib.cache.nativeimpl.Cache" 275 | } external; 276 | 277 | isolated function externRemove(Cache cache, string key) = @java:Method { 278 | 'class: "io.ballerina.stdlib.cache.nativeimpl.Cache" 279 | } external; 280 | 281 | isolated function externCleanUp(Cache cache, decimal currentTime) = @java:Method { 282 | 'class: "io.ballerina.stdlib.cache.nativeimpl.Cache" 283 | } external; 284 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ballerina/tests/test.bal: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 2 | // 3 | // WSO2 Inc. licenses this file to you under the Apache License, 4 | // Version 2.0 (the "License"); you may not use this file except 5 | // in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, 11 | // software distributed under the License is distributed on an 12 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 13 | // KIND, either express or implied. See the License for the 14 | // specific language governing permissions and limitations 15 | // under the License. 16 | 17 | import ballerina/lang.runtime as runtime; 18 | import ballerina/test; 19 | 20 | @test:Config { 21 | groups: ["create"] 22 | } 23 | isolated function testCreateCache() { 24 | CacheConfig config = { 25 | capacity: 10, 26 | evictionFactor: 0.2, 27 | defaultMaxAge: 3600, 28 | cleanupInterval: 5, 29 | evictionPolicy: LRU 30 | }; 31 | Cache|error cache = trap new(config); 32 | if cache is Cache { 33 | test:assertEquals(cache.size(), 0); 34 | } else { 35 | test:assertFail(cache.toString()); 36 | } 37 | } 38 | 39 | @test:Config { 40 | groups: ["create", "put", "size"] 41 | } 42 | isolated function testPutNewEntry() returns error? { 43 | CacheConfig config = { 44 | capacity: 10, 45 | evictionFactor: 0.2 46 | }; 47 | Cache cache = new(config); 48 | check cache.put("Hello", "Ballerina"); 49 | test:assertEquals(cache.size(), 1); 50 | } 51 | 52 | @test:Config { 53 | groups: ["create", "put", "size", "entry"] 54 | } 55 | isolated function testPutExistingEntry() returns error? { 56 | CacheConfig config = { 57 | capacity: 10, 58 | evictionFactor: 0.2 59 | }; 60 | string key = "Hello"; 61 | Cache cache = new(config); 62 | check cache.put(key, "Random value"); 63 | check cache.put(key, "Ballerina"); 64 | test:assertEquals(cache.size(), 1); 65 | any results = check cache.get(key); 66 | test:assertEquals(results.toString(), "Ballerina"); 67 | } 68 | 69 | @test:Config { 70 | groups: ["create", "put", "size", "age"] 71 | } 72 | isolated function testPutWithMaxAge() returns error? { 73 | decimal maxAge = 5; 74 | CacheConfig config = { 75 | capacity: 10, 76 | evictionFactor: 0.2 77 | }; 78 | Cache cache = new(config); 79 | check cache.put("Hello", "Ballerina", maxAge); 80 | decimal sleepTime = maxAge * 2 + 1; 81 | runtime:sleep(sleepTime); 82 | test:assertEquals(cache.size(), 1); 83 | } 84 | 85 | @test:Config { 86 | groups: ["create", "get"] 87 | } 88 | isolated function testGetExistingEntry() returns error? { 89 | CacheConfig config = { 90 | capacity: 10, 91 | evictionFactor: 0.2 92 | }; 93 | string key = "Hello"; 94 | string value = "Ballerina"; 95 | Cache cache = new(config); 96 | _ = check cache.put(key, value); 97 | any expected = check cache.get(key); 98 | test:assertEquals(expected.toString(), value); 99 | } 100 | 101 | @test:Config { 102 | groups: ["create", "get"] 103 | } 104 | isolated function testGetNonExistingEntry() { 105 | CacheConfig config = { 106 | capacity: 10, 107 | evictionFactor: 0.2 108 | }; 109 | Cache cache = new(config); 110 | any|error expected = cache.get("Hello"); 111 | if expected is error { 112 | test:assertEquals(expected.message(), "Cache entry from the given key: Hello, is not available."); 113 | } else { 114 | test:assertFail("Output mismatched"); 115 | } 116 | } 117 | 118 | @test:Config { 119 | groups: ["create", "put", "size", "expired", "get"] 120 | } 121 | isolated function testGetExpiredEntry() returns error? { 122 | CacheConfig config = { 123 | capacity: 10, 124 | evictionFactor: 0.2 125 | }; 126 | string key = "Hello"; 127 | string value = "Ballerina"; 128 | Cache cache = new(config); 129 | decimal maxAgeInSeconds = 1; 130 | check cache.put(key, value, maxAgeInSeconds); 131 | decimal sleepTime = maxAgeInSeconds * 2 + 1; 132 | runtime:sleep(sleepTime); 133 | any|error expected = cache.get(key); 134 | test:assertTrue(expected is error); 135 | if expected is error { 136 | test:assertEquals(expected.message(), "Cache entry from the given key: Hello, is not available."); 137 | } else { 138 | test:assertFail("Output mismatched"); 139 | } 140 | } 141 | 142 | @test:Config { 143 | groups: ["create", "put", "size", "remove", "invalidate"] 144 | } 145 | isolated function testRemove() returns error? { 146 | CacheConfig config = { 147 | capacity: 10, 148 | evictionFactor: 0.2 149 | }; 150 | Cache cache = new(config); 151 | string key = "Hello"; 152 | string value = "Ballerina"; 153 | check cache.put(key, value); 154 | check cache.invalidate(key); 155 | test:assertEquals(cache.size(), 0); 156 | } 157 | 158 | @test:Config { 159 | groups: ["create", "put", "size", "remove", "invalidate"] 160 | } 161 | isolated function testRemoveAll() returns error? { 162 | CacheConfig config = { 163 | capacity: 10, 164 | evictionFactor: 0.2 165 | }; 166 | Cache cache = new(config); 167 | string key1 = "Hello"; 168 | string value1 = "Ballerina"; 169 | check cache.put(key1, value1); 170 | string key2 = "Ballerina"; 171 | string value2 = "Language"; 172 | check cache.put(key2, value2); 173 | check cache.invalidateAll(); 174 | test:assertEquals(cache.size(), 0); 175 | } 176 | 177 | @test:Config { 178 | groups: ["create", "get", "key"] 179 | } 180 | isolated function testHasKey() returns error? { 181 | CacheConfig config = { 182 | capacity: 10, 183 | evictionFactor: 0.2 184 | }; 185 | Cache cache = new(config); 186 | string key = "Hello"; 187 | string value = "Ballerina"; 188 | check cache.put(key, value); 189 | test:assertTrue(cache.hasKey(key)); 190 | } 191 | 192 | @test:Config { 193 | groups: ["create", "get", "keys"] 194 | } 195 | isolated function testKeys() returns error? { 196 | CacheConfig config = { 197 | capacity: 10, 198 | evictionFactor: 0.2 199 | }; 200 | Cache cache = new(config); 201 | string key1 = "Hello"; 202 | string value1 = "Ballerina"; 203 | string key2 = "Ballerina"; 204 | string value2 = "Language"; 205 | string[] keys = [key1, key2]; 206 | check cache.put(key1, value1); 207 | check cache.put(key2, value2); 208 | test:assertEquals(cache.keys(), keys); 209 | } 210 | 211 | @test:Config { 212 | groups: ["create", "capacity"] 213 | } 214 | isolated function testCapacity() { 215 | CacheConfig config = { 216 | capacity: 10, 217 | evictionFactor: 0.2 218 | }; 219 | Cache cache = new(config); 220 | test:assertEquals(cache.capacity(), 10); 221 | } 222 | 223 | @test:Config { 224 | groups: ["cache", "resize"] 225 | } 226 | isolated function testSize() returns error? { 227 | CacheConfig config = { 228 | capacity: 10, 229 | evictionFactor: 0.2 230 | }; 231 | Cache cache = new(config); 232 | string key1 = "Hello"; 233 | string value1 = "Ballerina"; 234 | string key2 = "Ballerina"; 235 | string value2 = "Language"; 236 | check cache.put(key1, value1); 237 | check cache.put(key2, value2); 238 | test:assertEquals(cache.size(), 2); 239 | } 240 | 241 | @test:Config { 242 | groups: ["cache", "capacity", "policy"] 243 | } 244 | isolated function testCacheEvictionWithCapacity1() returns error? { 245 | CacheConfig config = { 246 | capacity: 10, 247 | evictionFactor: 0.2 248 | }; 249 | string[] keys = ["C", "D", "E", "F", "G", "H", "I", "J", "K"]; 250 | Cache cache = new(config); 251 | check cache.put("A", "1"); 252 | check cache.put("B", "2"); 253 | check cache.put("C", "3"); 254 | check cache.put("D", "4"); 255 | check cache.put("E", "5"); 256 | check cache.put("F", "6"); 257 | check cache.put("G", "7"); 258 | check cache.put("H", "8"); 259 | check cache.put("I", "9"); 260 | check cache.put("J", "10"); 261 | check cache.put("K", "11"); 262 | test:assertEquals(cache.size(), keys.length()); 263 | test:assertEquals(cache.keys(), keys); 264 | } 265 | 266 | @test:Config { 267 | groups: ["cache", "capacity", "policy"] 268 | } 269 | isolated function testCacheEvictionWithCapacity2() returns error? { 270 | CacheConfig config = { 271 | capacity: 10, 272 | evictionFactor: 0.2 273 | }; 274 | string[] keys = ["A", "D", "E", "F", "G", "H", "I", "J", "K"]; 275 | Cache cache = new(config); 276 | check cache.put("A", "1"); 277 | check cache.put("B", "2"); 278 | check cache.put("C", "3"); 279 | check cache.put("D", "4"); 280 | check cache.put("E", "5"); 281 | check cache.put("F", "6"); 282 | check cache.put("G", "7"); 283 | check cache.put("H", "8"); 284 | check cache.put("I", "9"); 285 | check cache.put("J", "10"); 286 | _ = check cache.get("A"); 287 | check cache.put("K", "11"); 288 | test:assertEquals(cache.size(), keys.length()); 289 | test:assertEquals(cache.keys(), keys); 290 | } 291 | 292 | @test:Config { 293 | groups: ["cache", "capacity", "policy"] 294 | } 295 | isolated function testCacheEvictionWithTimer1() returns error? { 296 | decimal cleanupInterval = 2; 297 | CacheConfig config = { 298 | capacity: 10, 299 | evictionFactor: 0.2, 300 | defaultMaxAge: 1, 301 | cleanupInterval: cleanupInterval 302 | }; 303 | Cache cache = new(config); 304 | check cache.put("A", "1"); 305 | check cache.put("B", "2"); 306 | check cache.put("C", "3"); 307 | string[] keys = []; 308 | decimal sleepTime = cleanupInterval * 2 + 2; 309 | runtime:sleep(sleepTime); 310 | test:assertEquals(cache.size(), keys.length()); 311 | test:assertEquals(cache.keys(), keys); 312 | } 313 | 314 | @test:Config { 315 | groups: ["cache", "capacity", "policy"] 316 | } 317 | isolated function testCacheEvictionWithTimer2() returns error? { 318 | decimal cleanupInterval = 2; 319 | CacheConfig config = { 320 | capacity: 10, 321 | evictionFactor: 0.2, 322 | defaultMaxAge: 1, 323 | cleanupInterval: cleanupInterval 324 | }; 325 | Cache cache = new(config); 326 | check cache.put("A", "1"); 327 | check cache.put("B", "2", 3600); 328 | check cache.put("C", "3"); 329 | string[] keys = ["B"]; 330 | decimal sleepTime = cleanupInterval * 2 + 1; 331 | runtime:sleep(sleepTime); 332 | test:assertEquals(cache.size(), keys.length()); 333 | test:assertEquals(cache.keys(), keys); 334 | } 335 | 336 | @test:Config { 337 | groups: ["cache", "put", "negative"] 338 | } 339 | isolated function testPutWithNullValue() { 340 | Cache cache = new(); 341 | error? result = cache.put("A", ()); 342 | test:assertTrue(result is error); 343 | if (result is error) { 344 | test:assertEquals(result.message(), "Unsupported cache value '()' for the key: A."); 345 | } else { 346 | test:assertFail("Output mismatched"); 347 | } 348 | } 349 | 350 | @test:Config { 351 | groups: ["cache", "invalidate", "negative"] 352 | } 353 | isolated function testInvalidateWithNonExistingValue() { 354 | Cache cache = new(); 355 | error? result = cache.invalidate("A"); 356 | test:assertTrue(result is error); 357 | if (result is error) { 358 | test:assertEquals(result.message(), "Cache entry from the given key: A, is not available."); 359 | } else { 360 | test:assertFail("Output mismatched"); 361 | } 362 | } 363 | 364 | @test:Config { 365 | groups: ["cache", "Eviction"] 366 | } 367 | isolated function testEvictionCount() returns error? { 368 | CacheConfig config = { 369 | capacity: 1, 370 | evictionFactor: 0.1 371 | }; 372 | Cache cache = new(config); 373 | check cache.put("A", "1"); 374 | check cache.put("B", "2"); 375 | check cache.put("C", "3"); 376 | string[] keys = ["C"]; 377 | test:assertEquals(cache.size(), keys.length(), "Cache size did not match"); 378 | test:assertEquals(cache.keys(), keys, "Cache keys did not match"); 379 | } 380 | -------------------------------------------------------------------------------- /compiler-plugin/src/main/java/io/ballerina/stdlib/cache/compiler/CacheConfigValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | package io.ballerina.stdlib.cache.compiler; 19 | 20 | import io.ballerina.compiler.api.symbols.ConstantSymbol; 21 | import io.ballerina.compiler.api.symbols.ModuleSymbol; 22 | import io.ballerina.compiler.api.symbols.Symbol; 23 | import io.ballerina.compiler.api.symbols.TypeDescKind; 24 | import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; 25 | import io.ballerina.compiler.api.symbols.TypeSymbol; 26 | import io.ballerina.compiler.api.symbols.UnionTypeSymbol; 27 | import io.ballerina.compiler.api.symbols.VariableSymbol; 28 | import io.ballerina.compiler.syntax.tree.BasicLiteralNode; 29 | import io.ballerina.compiler.syntax.tree.ExpressionNode; 30 | import io.ballerina.compiler.syntax.tree.FunctionArgumentNode; 31 | import io.ballerina.compiler.syntax.tree.ImplicitNewExpressionNode; 32 | import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; 33 | import io.ballerina.compiler.syntax.tree.MappingFieldNode; 34 | import io.ballerina.compiler.syntax.tree.ModuleVariableDeclarationNode; 35 | import io.ballerina.compiler.syntax.tree.NamedArgumentNode; 36 | import io.ballerina.compiler.syntax.tree.Node; 37 | import io.ballerina.compiler.syntax.tree.ParenthesizedArgList; 38 | import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; 39 | import io.ballerina.compiler.syntax.tree.SeparatedNodeList; 40 | import io.ballerina.compiler.syntax.tree.SpecificFieldNode; 41 | import io.ballerina.compiler.syntax.tree.UnaryExpressionNode; 42 | import io.ballerina.compiler.syntax.tree.VariableDeclarationNode; 43 | import io.ballerina.projects.plugins.AnalysisTask; 44 | import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; 45 | import io.ballerina.tools.diagnostics.Diagnostic; 46 | import io.ballerina.tools.diagnostics.DiagnosticFactory; 47 | import io.ballerina.tools.diagnostics.DiagnosticInfo; 48 | import io.ballerina.tools.diagnostics.DiagnosticSeverity; 49 | import io.ballerina.tools.diagnostics.Location; 50 | 51 | import java.util.List; 52 | import java.util.Optional; 53 | 54 | /** 55 | * CacheConfigAnalyzer. 56 | */ 57 | public class CacheConfigValidator implements AnalysisTask { 58 | 59 | @Override 60 | public void perform(SyntaxNodeAnalysisContext ctx) { 61 | List diagnostics = ctx.semanticModel().diagnostics(); 62 | for (Diagnostic diagnostic : diagnostics) { 63 | if (diagnostic.diagnosticInfo().severity() == DiagnosticSeverity.ERROR) { 64 | return; 65 | } 66 | } 67 | Optional varSymOptional = ctx.semanticModel() 68 | .symbol(ctx.node()); 69 | if (varSymOptional.isPresent()) { 70 | TypeSymbol typeSymbol = ((VariableSymbol) varSymOptional.get()).typeDescriptor(); 71 | if (!isCacheConfigVariable(typeSymbol)) { 72 | return; 73 | } 74 | 75 | // Initiated with a record 76 | Optional optionalInitializer; 77 | if ((ctx.node() instanceof VariableDeclarationNode)) { 78 | // Function level variables 79 | optionalInitializer = ((VariableDeclarationNode) ctx.node()).initializer(); 80 | } else { 81 | // Module level variables 82 | optionalInitializer = ((ModuleVariableDeclarationNode) ctx.node()).initializer(); 83 | } 84 | if (optionalInitializer.isEmpty()) { 85 | return; 86 | } 87 | ExpressionNode initializer = optionalInitializer.get(); 88 | if (initializer instanceof ImplicitNewExpressionNode) { 89 | Optional parenthesizedArgList = 90 | ((ImplicitNewExpressionNode) initializer).parenthesizedArgList(); 91 | if (parenthesizedArgList.isPresent()) { 92 | SeparatedNodeList fields = parenthesizedArgList.get().arguments(); 93 | for (FunctionArgumentNode field : fields) { 94 | if (field instanceof NamedArgumentNode) { 95 | NamedArgumentNode fieldNode = (NamedArgumentNode) field; 96 | validateConfig(fieldNode.argumentName().toSourceCode().trim(), 97 | fieldNode.expression().toSourceCode().trim(), ctx, field.location()); 98 | } 99 | } 100 | } 101 | } else if (initializer instanceof MappingConstructorExpressionNode) { 102 | SeparatedNodeList fields = 103 | ((MappingConstructorExpressionNode) initializer).fields(); 104 | for (MappingFieldNode field : fields) { 105 | SpecificFieldNode fieldNode = (SpecificFieldNode) field; 106 | String name = fieldNode.fieldName().toString() 107 | .trim().replaceAll(Constants.UNNECESSARY_CHARS_REGEX, ""); 108 | Optional expressionNode = fieldNode.valueExpr(); 109 | if (expressionNode.isPresent()) { 110 | ExpressionNode valueNode = expressionNode.get(); 111 | String value = getTerminalNodeValue(valueNode, ctx); 112 | if (value != null) { 113 | validateConfig(name, value, ctx, valueNode.location()); 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | private String getTerminalNodeValue(Node valueNode, SyntaxNodeAnalysisContext ctx) { 122 | String value = null; 123 | if (valueNode instanceof BasicLiteralNode) { 124 | value = ((BasicLiteralNode) valueNode).literalToken().text(); 125 | } else if (valueNode instanceof QualifiedNameReferenceNode) { 126 | if (ctx.semanticModel().symbol(valueNode).get() instanceof ConstantSymbol constantSymbol) { 127 | value = constantSymbol.constValue().toString(); 128 | } else { 129 | QualifiedNameReferenceNode qualifiedNameReferenceNode = (QualifiedNameReferenceNode) valueNode; 130 | value = qualifiedNameReferenceNode.toString(); 131 | } 132 | } else if (valueNode instanceof UnaryExpressionNode) { 133 | UnaryExpressionNode unaryExpressionNode = (UnaryExpressionNode) valueNode; 134 | value = unaryExpressionNode.unaryOperator() + 135 | ((BasicLiteralNode) unaryExpressionNode.expression()).literalToken().text(); 136 | } 137 | if (value != null) { 138 | return value.replaceAll(Constants.UNNECESSARY_CHARS_REGEX, ""); 139 | } 140 | return null; 141 | } 142 | 143 | private boolean isCacheConfigVariable(TypeSymbol type) { 144 | if (type.typeKind() == TypeDescKind.UNION) { 145 | return ((UnionTypeSymbol) type).memberTypeDescriptors().stream() 146 | .filter(typeDescriptor -> typeDescriptor instanceof TypeReferenceTypeSymbol) 147 | .map(typeReferenceTypeSymbol -> (TypeReferenceTypeSymbol) typeReferenceTypeSymbol) 148 | .anyMatch(this::isCacheConfigVariable); 149 | } 150 | if (type.typeKind() == TypeDescKind.TYPE_REFERENCE) { 151 | return isCacheConfigVariable((TypeReferenceTypeSymbol) type); 152 | } 153 | return false; 154 | } 155 | 156 | private boolean isCacheConfigVariable(TypeReferenceTypeSymbol typeSymbol) { 157 | TypeDescKind typeDescKind = typeSymbol.typeDescriptor().typeKind(); 158 | Optional module = typeSymbol.getModule(); 159 | if (module.isPresent()) { 160 | ModuleSymbol moduleSymbol = module.get(); 161 | Optional name = moduleSymbol.getName(); 162 | Optional typeName = typeSymbol.definition().getName(); 163 | if (name.isPresent() && typeName.isPresent()) { 164 | if (typeDescKind == TypeDescKind.RECORD) { 165 | return Constants.CACHE.equals(name.get()) && 166 | Constants.BALLERINA.equals(moduleSymbol.id().orgName()) && 167 | typeName.get().equals(Constants.CACHE_CONFIG); 168 | } else if (typeDescKind == TypeDescKind.OBJECT) { 169 | return Constants.CACHE.equals(name.get()) && 170 | Constants.BALLERINA.equals(moduleSymbol.id().orgName()) 171 | && typeName.get().equalsIgnoreCase(Constants.CACHE); 172 | } else { 173 | return false; 174 | } 175 | } 176 | } 177 | return false; 178 | } 179 | 180 | private void validateConfig(String name, String value, SyntaxNodeAnalysisContext ctx, Location location) { 181 | try { 182 | switch (name) { 183 | case Constants.CAPACITY: 184 | int maxCapacity = Integer.parseInt(value); 185 | if (maxCapacity <= 0) { 186 | reportDiagnostic(ctx, location, DiagnosticsCodes.CACHE_101.getErrorCode(), 187 | DiagnosticsCodes.CACHE_101.getError(), DiagnosticsCodes.CACHE_101.getSeverity()); 188 | } 189 | break; 190 | case Constants.EVICTION_FACTOR: 191 | float evictionFactor = Float.parseFloat(value); 192 | if (evictionFactor < 0 || evictionFactor >= 1) { 193 | reportDiagnostic(ctx, location, DiagnosticsCodes.CACHE_102.getErrorCode(), 194 | DiagnosticsCodes.CACHE_102.getError(), DiagnosticsCodes.CACHE_102.getSeverity()); 195 | } 196 | break; 197 | case Constants.DEFAULT_MAX_AGE: 198 | float defaultMaxAge = Float.parseFloat(value); 199 | if (defaultMaxAge != -1 && defaultMaxAge < 0) { 200 | reportDiagnostic(ctx, location, DiagnosticsCodes.CACHE_103.getErrorCode(), 201 | DiagnosticsCodes.CACHE_103.getError(), DiagnosticsCodes.CACHE_103.getSeverity()); 202 | } 203 | break; 204 | case Constants.CLEAN_UP_INTERVAL: 205 | float cleanUpInterval = Float.parseFloat(value); 206 | if (cleanUpInterval <= 0) { 207 | reportDiagnostic(ctx, location, DiagnosticsCodes.CACHE_104.getErrorCode(), 208 | DiagnosticsCodes.CACHE_104.getError(), DiagnosticsCodes.CACHE_104.getSeverity()); 209 | } 210 | break; 211 | case Constants.EVICTION_POLICY: 212 | if (!value.equals(Constants.POLICY_VALUE)) { 213 | reportDiagnostic(ctx, location, DiagnosticsCodes.CACHE_105.getErrorCode(), 214 | DiagnosticsCodes.CACHE_105.getError(), DiagnosticsCodes.CACHE_105.getSeverity()); 215 | } 216 | break; 217 | default: 218 | break; 219 | } 220 | } catch (NumberFormatException e) { 221 | // This occurs when there is a variable reference in the value in scenarios like having a 222 | // constant or configurable. In such cases like configurable, we cannot validate the value as the value is 223 | // resolved at runtime. Hence, ignoring the validation. And for constants, if they are not in the same file, 224 | // we cannot read them from the compiler plugin. Hence, ignoring the validation. These will be validated 225 | // runtime. 226 | } 227 | } 228 | 229 | private void reportDiagnostic(SyntaxNodeAnalysisContext ctx, Location location, String code, 230 | String message, DiagnosticSeverity diagnosticSeverity) { 231 | DiagnosticInfo diagnosticInfo = new DiagnosticInfo(code, message, diagnosticSeverity); 232 | ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, location)); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /native/src/main/java/io/ballerina/stdlib/cache/nativeimpl/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. 3 | * 4 | * WSO2 Inc. licenses this file to you under the Apache License, 5 | * Version 2.0 (the "License"); you may not use this file except 6 | * in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, 12 | * software distributed under the License is distributed on an 13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | * KIND, either express or implied. See the License for the 15 | * specific language governing permissions and limitations 16 | * under the License. 17 | */ 18 | package io.ballerina.stdlib.cache.nativeimpl.concurrentlinkedhashmap; 19 | 20 | import java.io.InvalidObjectException; 21 | import java.io.ObjectInputStream; 22 | import java.io.Serializable; 23 | import java.util.AbstractMap; 24 | import java.util.AbstractSet; 25 | import java.util.Collection; 26 | import java.util.Iterator; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.Queue; 30 | import java.util.Set; 31 | import java.util.concurrent.AbstractExecutorService; 32 | import java.util.concurrent.ConcurrentHashMap; 33 | import java.util.concurrent.ConcurrentLinkedQueue; 34 | import java.util.concurrent.ConcurrentMap; 35 | import java.util.concurrent.ExecutorService; 36 | import java.util.concurrent.TimeUnit; 37 | import java.util.concurrent.atomic.AtomicIntegerArray; 38 | import java.util.concurrent.atomic.AtomicReference; 39 | import java.util.concurrent.locks.Lock; 40 | import java.util.concurrent.locks.ReentrantLock; 41 | import java.util.function.BiConsumer; 42 | import java.util.function.BiFunction; 43 | import java.util.function.Function; 44 | 45 | /** 46 | * Implementation of the ConcurrentLinked Hash Map. 47 | * @see concurrentlinkedhashmap 49 | * 50 | * @param the type of keys maintained by this map 51 | * @param the type of mapped values 52 | */ 53 | public class ConcurrentLinkedHashMap implements ConcurrentMap, Serializable { 54 | 55 | /** The maximum weighted capacity of the map. */ 56 | static final int MAXIMUM_CAPACITY = 1 << 30; 57 | 58 | /** The maximum number of pending operations per buffer. */ 59 | static final int MAXIMUM_BUFFER_SIZE = 1 << 20; 60 | 61 | /** The number of pending operations per buffer before attempting to drain. */ 62 | static final int BUFFER_THRESHOLD = 16; 63 | 64 | /** The number of buffers to use. */ 65 | static final int NUMBER_OF_BUFFERS; 66 | 67 | /** Mask value for indexing into the buffers. */ 68 | static final int BUFFER_MASK; 69 | 70 | /** The maximum number of operations to perform per amortized drain. */ 71 | static final int AMORTIZED_DRAIN_THRESHOLD; 72 | 73 | static { 74 | int buffers = ceilingNextPowerOfTwo(Runtime.getRuntime().availableProcessors()); 75 | AMORTIZED_DRAIN_THRESHOLD = (1 + buffers) * BUFFER_THRESHOLD; 76 | NUMBER_OF_BUFFERS = buffers; 77 | BUFFER_MASK = buffers - 1; 78 | } 79 | 80 | static int ceilingNextPowerOfTwo(int x) { 81 | // From Hacker's Delight, Chapter 3, Harry S. Warren Jr. 82 | return 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(x - 1)); 83 | } 84 | 85 | /** The draining status of the buffers. */ 86 | enum DrainStatus { 87 | 88 | /** A drain is not taking place. */ 89 | IDLE, 90 | 91 | /** A drain is required due to a pending write modification. */ 92 | REQUIRED, 93 | 94 | /** A drain is in progress. */ 95 | PROCESSING 96 | } 97 | 98 | // The backing data store holding the key-value associations 99 | final ConcurrentHashMap data; 100 | 101 | // These fields provide support to bound the map by a maximum capacity 102 | transient LinkedDeque evictionDeque; 103 | 104 | // must write under lock 105 | volatile int weightedSize; 106 | 107 | // must write under lock 108 | volatile int capacity; 109 | 110 | volatile int nextOrder; 111 | int drainedOrder; 112 | 113 | final transient Lock evictionLock; 114 | final Queue[] buffers; 115 | transient ExecutorService executor = new DisabledExecutorService();; 116 | final Weigher weigher; 117 | final AtomicIntegerArray bufferLengths; 118 | final AtomicReference drainStatus; 119 | 120 | transient Set keySet; 121 | transient Set> entrySet; 122 | 123 | /** 124 | * Creates an instance based on the builder's configuration. 125 | */ 126 | @SuppressWarnings({ 127 | "unchecked", "cast" 128 | }) 129 | public ConcurrentLinkedHashMap(int maximumCapacity) { 130 | // The data store and its maximum capacity 131 | capacity = maximumCapacity; 132 | data = new ConcurrentHashMap<>( 133 | 3, 134 | 0.75f, 135 | 16); 136 | 137 | // The eviction support 138 | weigher = Weighers.singleton(); 139 | nextOrder = Integer.MIN_VALUE; 140 | drainedOrder = Integer.MIN_VALUE; 141 | evictionLock = new ReentrantLock(); 142 | evictionDeque = new LinkedDeque<>(); 143 | drainStatus = new AtomicReference<>(DrainStatus.IDLE); 144 | 145 | buffers = (Queue[]) new Queue[NUMBER_OF_BUFFERS]; 146 | bufferLengths = new AtomicIntegerArray(NUMBER_OF_BUFFERS); 147 | for (int i = 0; i < NUMBER_OF_BUFFERS; i++) { 148 | buffers[i] = new ConcurrentLinkedQueue<>(); 149 | } 150 | } 151 | 152 | /* ---------------- Eviction Support -------------- */ 153 | 154 | /** 155 | * Sets the maximum weighted capacity of the map and eagerly evicts entries until it 156 | * shrinks to the appropriate size. 157 | * 158 | * @param capacity the maximum weighted capacity of the map 159 | * @throws IllegalArgumentException if the capacity is negative 160 | */ 161 | public void setCapacity(int capacity) { 162 | evictionLock.lock(); 163 | try { 164 | this.capacity = Math.min(capacity, MAXIMUM_CAPACITY); 165 | drainBuffers(AMORTIZED_DRAIN_THRESHOLD); 166 | evict(); 167 | } finally { 168 | evictionLock.unlock(); 169 | } 170 | } 171 | 172 | /** Determines whether the map has exceeded its capacity. */ 173 | private boolean hasOverflowed() { 174 | return weightedSize > capacity; 175 | } 176 | 177 | /** 178 | * Evicts entries from the map while it exceeds the capacity and appends evicted 179 | * entries to the notification queue for processing. 180 | */ 181 | private void evict() { 182 | // Attempts to evict entries from the map if it exceeds the maximum 183 | // capacity. If the eviction fails due to a concurrent removal of the 184 | // victim, that removal may cancel out the addition that triggered this 185 | // eviction. The victim is eagerly unlinked before the removal task so 186 | // that if an eviction is still required then a new victim will be chosen 187 | // for removal. 188 | while (hasOverflowed()) { 189 | Node node = evictionDeque.pollFirst(); 190 | data.remove(node.key, node); 191 | node.makeDead(); 192 | } 193 | } 194 | 195 | /** 196 | * Performs the post-processing work required after the map operation. 197 | * 198 | * @param task the pending operation to be applied 199 | */ 200 | private void afterCompletion(Task task) { 201 | boolean delayable = schedule(task); 202 | if (shouldDrainBuffers(delayable)) { 203 | tryToDrainBuffers(AMORTIZED_DRAIN_THRESHOLD); 204 | } 205 | } 206 | 207 | /** 208 | * Schedules the task to be applied to the page replacement policy. 209 | * 210 | * @param task the pending operation 211 | * @return if the draining of the buffers can be delayed 212 | */ 213 | private boolean schedule(Task task) { 214 | int index = bufferIndex(); 215 | int buffered = bufferLengths.incrementAndGet(index); 216 | 217 | if (task.isWrite()) { 218 | buffers[index].add(task); 219 | drainStatus.set(DrainStatus.REQUIRED); 220 | return false; 221 | } 222 | 223 | // A buffer may discard a read task if its length exceeds a tolerance level 224 | if (buffered <= MAXIMUM_BUFFER_SIZE) { 225 | buffers[index].add(task); 226 | return (buffered <= BUFFER_THRESHOLD); 227 | } else { // not optimized for fail-safe scenario 228 | bufferLengths.decrementAndGet(index); 229 | return false; 230 | } 231 | } 232 | 233 | /** Returns the index to the buffer that the task should be scheduled on. */ 234 | private static int bufferIndex() { 235 | // A buffer is chosen by the thread's id so that tasks are distributed in a 236 | // pseudo evenly manner. This helps avoid hot entries causing contention due 237 | // to other threads trying to append to the same buffer. 238 | return (int) Thread.currentThread().getId() & BUFFER_MASK; 239 | } 240 | 241 | /** Returns the ordering value to assign to a task. */ 242 | public int nextOrdering() { 243 | // The next ordering is acquired in a racy fashion as the increment is not 244 | // atomic with the insertion into a buffer. This means that concurrent tasks 245 | // can have the same ordering and the buffers are in a weakly sorted order. 246 | return nextOrder++; 247 | } 248 | 249 | /** 250 | * Determines whether the buffers should be drained. 251 | * 252 | * @param delayable if a drain should be delayed until required 253 | * @return if a drain should be attempted 254 | */ 255 | private boolean shouldDrainBuffers(boolean delayable) { 256 | if (executor.isShutdown()) { 257 | DrainStatus status = drainStatus.get(); 258 | return (status != DrainStatus.PROCESSING) 259 | && (!delayable || (status == DrainStatus.REQUIRED)); 260 | } 261 | return false; 262 | } 263 | 264 | /** 265 | * Attempts to acquire the eviction lock and apply the pending operations to the page 266 | * replacement policy. 267 | * 268 | * @param maxToDrain the maximum number of operations to drain 269 | */ 270 | private void tryToDrainBuffers(int maxToDrain) { 271 | if (evictionLock.tryLock()) { 272 | try { 273 | drainStatus.set(DrainStatus.PROCESSING); 274 | drainBuffers(maxToDrain); 275 | } finally { 276 | drainStatus.compareAndSet(DrainStatus.PROCESSING, DrainStatus.IDLE); 277 | evictionLock.unlock(); 278 | } 279 | } 280 | } 281 | 282 | /** 283 | * Drains the buffers and applies the pending operations. 284 | * 285 | * @param maxToDrain the maximum number of operations to drain 286 | */ 287 | private void drainBuffers(int maxToDrain) { 288 | // A mostly strict ordering is achieved by observing that each buffer 289 | // contains tasks in a weakly sorted order starting from the last drain. 290 | // The buffers can be merged into a sorted list in O(n) time by using 291 | // counting sort and chaining on a collision. 292 | 293 | // The output is capped to the expected number of tasks plus additional 294 | // slack to optimistically handle the concurrent additions to the buffers. 295 | Task[] tasks = new Task[maxToDrain]; 296 | 297 | // Moves the tasks into the output array, applies them, and updates the 298 | // marker for the starting order of the next drain. 299 | int maxTaskIndex = moveTasksFromBuffers(tasks); 300 | runTasks(tasks, maxTaskIndex); 301 | updateDrainedOrder(tasks, maxTaskIndex); 302 | } 303 | 304 | /** 305 | * Moves the tasks from the buffers into the output array. 306 | * 307 | * @param tasks the ordered array of the pending operations 308 | * @return the highest index location of a task that was added to the array 309 | */ 310 | private int moveTasksFromBuffers(Task[] tasks) { 311 | int maxTaskIndex = -1; 312 | for (int i = 0; i < buffers.length; i++) { 313 | int maxIndex = moveTasksFromBuffer(tasks, i); 314 | maxTaskIndex = Math.max(maxIndex, maxTaskIndex); 315 | } 316 | return maxTaskIndex; 317 | } 318 | 319 | /** 320 | * Moves the tasks from the specified buffer into the output array. 321 | * 322 | * @param tasks the ordered array of the pending operations 323 | * @param bufferIndex the buffer to drain into the tasks array 324 | * @return the highest index location of a task that was added to the array 325 | */ 326 | private int moveTasksFromBuffer(Task[] tasks, int bufferIndex) { 327 | // While a buffer is being drained it may be concurrently appended to. The 328 | // number of tasks removed are tracked so that the length can be decremented 329 | // by the delta rather than set to zero. 330 | Queue buffer = buffers[bufferIndex]; 331 | int removedFromBuffer = 0; 332 | 333 | Task task; 334 | int maxIndex = -1; 335 | while ((task = buffer.poll()) != null) { 336 | removedFromBuffer++; 337 | 338 | // The index into the output array is determined by calculating the offset 339 | // since the last drain 340 | int index = task.getOrder() - drainedOrder; 341 | if (index < 0) { 342 | // The task was missed by the last drain and can be run immediately 343 | task.run(); 344 | } else if (index >= tasks.length) { 345 | // Due to concurrent additions, the order exceeds the capacity of the 346 | // output array. It is added to the end as overflow and the remaining 347 | // tasks in the buffer will be handled by the next drain. 348 | maxIndex = tasks.length - 1; 349 | addTaskToChain(tasks, task, maxIndex); 350 | break; 351 | } else { 352 | maxIndex = Math.max(index, maxIndex); 353 | addTaskToChain(tasks, task, index); 354 | } 355 | } 356 | bufferLengths.addAndGet(bufferIndex, -removedFromBuffer); 357 | return maxIndex; 358 | } 359 | 360 | /** 361 | * Adds the task as the head of the chain at the index location. 362 | * 363 | * @param tasks the ordered array of the pending operations 364 | * @param task the pending operation to add 365 | * @param index the array location 366 | */ 367 | private void addTaskToChain(Task[] tasks, Task task, int index) { 368 | task.setNext(tasks[index]); 369 | tasks[index] = task; 370 | } 371 | 372 | /** 373 | * Runs the pending page replacement policy operations. 374 | * 375 | * @param tasks the ordered array of the pending operations 376 | * @param maxTaskIndex the maximum index of the array 377 | */ 378 | private void runTasks(Task[] tasks, int maxTaskIndex) { 379 | for (int i = 0; i <= maxTaskIndex; i++) { 380 | runTasksInChain(tasks[i]); 381 | } 382 | } 383 | 384 | /** 385 | * Runs the pending operations on the linked chain. 386 | * 387 | * @param task the first task in the chain of operations 388 | */ 389 | private void runTasksInChain(Task task) { 390 | while (task != null) { 391 | Task current = task; 392 | task = task.getNext(); 393 | current.setNext(null); 394 | current.run(); 395 | } 396 | } 397 | 398 | /** 399 | * Updates the order to start the next drain from. 400 | * 401 | * @param tasks the ordered array of operations 402 | * @param maxTaskIndex the maximum index of the array 403 | */ 404 | private void updateDrainedOrder(Task[] tasks, int maxTaskIndex) { 405 | if (maxTaskIndex >= 0) { 406 | Task task = tasks[maxTaskIndex]; 407 | drainedOrder = task.getOrder() + 1; 408 | } 409 | } 410 | 411 | /** Updates the node's location in the page replacement policy. */ 412 | private class ReadTask extends AbstractTask { 413 | 414 | final Node node; 415 | 416 | ReadTask(Node node) { 417 | this.node = node; 418 | } 419 | 420 | public void run() { 421 | // An entry may scheduled for reordering despite having been previously 422 | // removed. This can occur when the entry was concurrently read while a 423 | // writer was removing it. If the entry is no longer linked then it does 424 | // not need to be processed. 425 | if (evictionDeque.contains(node)) { 426 | evictionDeque.moveToBack(node); 427 | } 428 | } 429 | 430 | public boolean isWrite() { 431 | return false; 432 | } 433 | } 434 | 435 | /** Adds the node to the page replacement policy. */ 436 | private final class AddTask extends AbstractTask { 437 | 438 | final Node node; 439 | final int weight; 440 | 441 | AddTask(Node node, int weight) { 442 | this.weight = weight; 443 | this.node = node; 444 | } 445 | 446 | public void run() { 447 | weightedSize += weight; 448 | 449 | // ignore out-of-order write operations 450 | if (node.get().isAlive()) { 451 | evictionDeque.add(node); 452 | } 453 | } 454 | 455 | public boolean isWrite() { 456 | return true; 457 | } 458 | } 459 | 460 | /** Removes a node from the page replacement policy. */ 461 | private final class RemovalTask extends AbstractTask { 462 | 463 | final Node node; 464 | 465 | RemovalTask(Node node) { 466 | this.node = node; 467 | } 468 | 469 | public void run() { 470 | // add may not have been processed yet 471 | evictionDeque.remove(node); 472 | node.makeDead(); 473 | } 474 | 475 | public boolean isWrite() { 476 | return true; 477 | } 478 | } 479 | 480 | /** Updates the weighted size and evicts an entry on overflow. */ 481 | private final class UpdateTask extends ReadTask { 482 | 483 | final int weightDifference; 484 | 485 | public UpdateTask(Node node, int weightDifference) { 486 | super(node); 487 | this.weightDifference = weightDifference; 488 | } 489 | 490 | @Override 491 | public void run() { 492 | super.run(); 493 | weightedSize += weightDifference; 494 | } 495 | 496 | @Override 497 | public boolean isWrite() { 498 | return true; 499 | } 500 | } 501 | 502 | /* ---------------- Concurrent Map Support -------------- */ 503 | 504 | @Override 505 | public boolean isEmpty() { 506 | return data.isEmpty(); 507 | } 508 | 509 | @Override 510 | public int size() { 511 | return data.size(); 512 | } 513 | 514 | @Override 515 | public void clear() { 516 | // The alternative is to iterate through the keys and call #remove(), which 517 | // adds unnecessary contention on the eviction lock and buffers. 518 | evictionLock.lock(); 519 | try { 520 | Node node; 521 | while ((node = evictionDeque.poll()) != null) { 522 | data.remove(node.key, node); 523 | node.makeDead(); 524 | } 525 | 526 | // Drain the buffers and run only the write tasks 527 | for (int i = 0; i < buffers.length; i++) { 528 | Queue buffer = buffers[i]; 529 | int removed = 0; 530 | Task task; 531 | while ((task = buffer.poll()) != null) { 532 | if (task.isWrite()) { 533 | task.run(); 534 | } 535 | removed++; 536 | } 537 | bufferLengths.addAndGet(i, -removed); 538 | } 539 | } finally { 540 | evictionLock.unlock(); 541 | } 542 | } 543 | 544 | @Override 545 | public boolean containsKey(Object key) { 546 | if (key != null) { 547 | return data.containsKey(key); 548 | } 549 | return false; 550 | } 551 | 552 | @Override 553 | public boolean containsValue(Object value) { 554 | for (Node node : data.values()) { 555 | if (node.getValue().equals(value)) { 556 | return true; 557 | } 558 | } 559 | return false; 560 | } 561 | 562 | @Override 563 | public V get(Object key) { 564 | if (key != null && data.containsKey(key)) { 565 | final Node node = data.get(key); 566 | if (node == null) { 567 | return null; 568 | } 569 | afterCompletion(new ReadTask(node)); 570 | return node.getValue(); 571 | } 572 | return null; 573 | } 574 | 575 | @Override 576 | public V getOrDefault(Object key, V defaultValue) { 577 | return null; 578 | } 579 | 580 | @Override 581 | public void forEach(BiConsumer action) { 582 | 583 | } 584 | 585 | public V putIfAbsent(K key, V value) { 586 | return null; 587 | } 588 | 589 | @Override 590 | public boolean remove(Object key, Object value) { 591 | return false; 592 | } 593 | 594 | @Override 595 | public boolean replace(K key, V oldValue, V newValue) { 596 | return false; 597 | } 598 | 599 | @Override 600 | public V replace(K key, V value) { 601 | return null; 602 | } 603 | 604 | @Override 605 | public void replaceAll(BiFunction function) { 606 | 607 | } 608 | 609 | @Override 610 | public V computeIfAbsent(K key, Function mappingFunction) { 611 | return null; 612 | } 613 | 614 | @Override 615 | public V computeIfPresent(K key, BiFunction remappingFunction) { 616 | return null; 617 | } 618 | 619 | @Override 620 | public V compute(K key, BiFunction remappingFunction) { 621 | return null; 622 | } 623 | 624 | @Override 625 | public V merge(K key, V value, BiFunction remappingFunction) { 626 | return null; 627 | } 628 | 629 | /** 630 | * Adds a node to the list and the data store. If an existing node is found, then its 631 | * value is updated if allowed. 632 | * 633 | * @param key key with which the specified value is to be associated 634 | * @param value value to be associated with the specified key 635 | * @return the prior value in the data store or null if no mapping was found 636 | */ 637 | @Override 638 | public V put(K key, V value) { 639 | if (key != null) { 640 | final int weight = weigher.weightOf(value); 641 | final WeightedValue weightedValue = new WeightedValue<>(value, weight); 642 | final Node node = new Node(key, weightedValue); 643 | while (true) { 644 | final Node prior = data.putIfAbsent(node.key, node); 645 | if (prior == null) { 646 | afterCompletion(new AddTask(node, weight)); 647 | return null; 648 | } 649 | while (true) { 650 | final WeightedValue oldWeightedValue = prior.get(); 651 | if (!oldWeightedValue.isAlive()) { 652 | break; 653 | } 654 | if (prior.compareAndSet(oldWeightedValue, weightedValue)) { 655 | final int weightedDifference = weight - oldWeightedValue.weight; 656 | final Task task = (weightedDifference == 0) 657 | ? new ReadTask(prior) 658 | : new UpdateTask(prior, weightedDifference); 659 | afterCompletion(task); 660 | return oldWeightedValue.value; 661 | } 662 | } 663 | } 664 | } 665 | return null; 666 | } 667 | 668 | @Override 669 | public V remove(Object key) { 670 | if (key != null) { 671 | final Node node = data.remove(key); 672 | if (node == null) { 673 | return null; 674 | } 675 | 676 | node.makeRetired(); 677 | afterCompletion(new RemovalTask(node)); 678 | return node.getValue(); 679 | } 680 | return null; 681 | } 682 | 683 | @Override 684 | public void putAll(Map m) { 685 | 686 | } 687 | 688 | @Override 689 | public Set keySet() { 690 | Set ks = keySet; 691 | return (ks == null) ? (keySet = new KeySet()) : ks; 692 | } 693 | 694 | @Override 695 | public Collection values() { 696 | return null; 697 | } 698 | 699 | @Override 700 | public Set> entrySet() { 701 | Set> es = entrySet; 702 | return (es == null) ? (entrySet = new EntrySet()) : es; 703 | } 704 | 705 | /** 706 | * A value, its weight, and the entry's status. 707 | * 708 | * @param the type of the value 709 | */ 710 | private static final class WeightedValue { 711 | 712 | private final int weight; 713 | private final V value; 714 | 715 | WeightedValue(V value, int weight) { 716 | this.weight = weight; 717 | this.value = value; 718 | } 719 | 720 | boolean hasValue(Object o) { 721 | return (o == value) || value.equals(o); 722 | } 723 | 724 | /** 725 | * If the entry is available in the hash-table and page replacement policy. 726 | */ 727 | boolean isAlive() { 728 | return weight > 0; 729 | } 730 | } 731 | 732 | /** 733 | * A node contains the key, the weighted value, and the linkage pointers on the 734 | * page-replacement algorithm's data structures. 735 | */ 736 | @SuppressWarnings("serial") 737 | private class Node extends AtomicReference> implements Linked { 738 | private static final long serialVersionUID = 1; 739 | private K key; 740 | 741 | private Node prev; 742 | private Node next; 743 | /** Creates a new, unlinked node. */ 744 | Node(K key, WeightedValue weightedValue) { 745 | super(weightedValue); 746 | this.key = key; 747 | } 748 | 749 | public Node getPrevious() { 750 | return prev; 751 | } 752 | 753 | public void setPrevious(Node prev) { 754 | this.prev = prev; 755 | } 756 | 757 | public Node getNext() { 758 | return next; 759 | } 760 | 761 | public void setNext(Node next) { 762 | this.next = next; 763 | } 764 | 765 | /** Retrieves the value held by the current WeightedValue. */ 766 | V getValue() { 767 | return get().value; 768 | } 769 | 770 | /** 771 | * Atomically transitions the node from the alive state to the 772 | * retired state, if a valid transition. 773 | */ 774 | public void makeRetired() { 775 | for (;;) { 776 | WeightedValue current = get(); 777 | if (!current.isAlive()) { 778 | return; 779 | } 780 | WeightedValue retired = new WeightedValue<>( 781 | current.value, 782 | -current.weight); 783 | if (compareAndSet(current, retired)) { 784 | return; 785 | } 786 | } 787 | } 788 | 789 | /** 790 | * Atomically transitions the node to the dead state and decrements the 791 | * weightedSize. 792 | */ 793 | public void makeDead() { 794 | for (;;) { 795 | WeightedValue current = get(); 796 | WeightedValue dead = new WeightedValue<>(current.value, 0); 797 | if (compareAndSet(current, dead)) { 798 | weightedSize -= Math.abs(current.weight); 799 | return; 800 | } 801 | } 802 | } 803 | } 804 | 805 | /** An adapter to safely externalize the keys. */ 806 | private final class KeySet extends AbstractSet { 807 | 808 | final ConcurrentLinkedHashMap map = ConcurrentLinkedHashMap.this; 809 | 810 | @Override 811 | public Iterator iterator() { 812 | return null; 813 | } 814 | 815 | @Override 816 | public int size() { 817 | return 0; 818 | } 819 | 820 | @Override 821 | public T[] toArray(T[] array) { 822 | return map.data.keySet().toArray(array); 823 | } 824 | } 825 | 826 | /** An adapter to safely externalize the entries. */ 827 | private final class EntrySet extends AbstractSet> { 828 | 829 | @Override 830 | public Iterator> iterator() { 831 | return new EntryIterator(); 832 | } 833 | 834 | @Override 835 | public int size() { 836 | return 0; 837 | } 838 | } 839 | 840 | /** An adapter to safely externalize the entry iterator. */ 841 | private final class EntryIterator implements Iterator> { 842 | 843 | private final Iterator iterator = data.values().iterator(); 844 | Node current; 845 | 846 | public boolean hasNext() { 847 | return iterator.hasNext(); 848 | } 849 | 850 | @Override 851 | public Entry next() { 852 | current = iterator.next(); 853 | return new WriteThroughEntry(current); 854 | } 855 | 856 | public void remove() { 857 | if (current == null) { 858 | throw new IllegalStateException("Node can't be null"); 859 | } 860 | ConcurrentLinkedHashMap.this.remove(current.key); 861 | current = null; 862 | } 863 | } 864 | 865 | /** An entry that allows updates to write through to the map. */ 866 | private class WriteThroughEntry extends AbstractMap.SimpleEntry { 867 | 868 | private static final long serialVersionUID = 1; 869 | 870 | public WriteThroughEntry(Node node) { 871 | super(node.key, node.getValue()); 872 | } 873 | } 874 | 875 | /** An executor that is always terminated. */ 876 | private static final class DisabledExecutorService extends AbstractExecutorService { 877 | 878 | @Override 879 | public void shutdown() { 880 | 881 | } 882 | 883 | @Override 884 | public List shutdownNow() { 885 | return null; 886 | } 887 | 888 | public boolean isShutdown() { 889 | return true; 890 | } 891 | 892 | @Override 893 | public boolean isTerminated() { 894 | return false; 895 | } 896 | 897 | @Override 898 | public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { 899 | return false; 900 | } 901 | 902 | @Override 903 | public void execute(Runnable command) { 904 | 905 | } 906 | } 907 | 908 | /** An operation that can be lazily applied to the page replacement policy. */ 909 | private interface Task extends Runnable { 910 | 911 | /** The priority order. */ 912 | int getOrder(); 913 | 914 | /** If the task represents an add, modify, or remove operation. */ 915 | boolean isWrite(); 916 | 917 | /** Returns the next task on the link chain. */ 918 | Task getNext(); 919 | 920 | /** Sets the next task on the link chain. */ 921 | void setNext(Task task); 922 | } 923 | 924 | /** A skeletal implementation of the Task interface. */ 925 | private abstract class AbstractTask implements Task { 926 | 927 | private final int order; 928 | private Task task; 929 | 930 | public AbstractTask() { 931 | order = nextOrdering(); 932 | } 933 | 934 | public int getOrder() { 935 | return order; 936 | } 937 | 938 | public Task getNext() { 939 | return task; 940 | } 941 | 942 | public void setNext(Task task) { 943 | this.task = task; 944 | } 945 | } 946 | 947 | /* ---------------- Serialization Support -------------- */ 948 | 949 | private static final long serialVersionUID = 1; 950 | 951 | private void readObject(ObjectInputStream stream) throws InvalidObjectException { 952 | throw new InvalidObjectException("Proxy required"); 953 | } 954 | } 955 | --------------------------------------------------------------------------------