diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..3c4c2d0c76a3d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,10 @@ +# This is the GitHub CODEOWNERS file that defines ownership of dofferent source files. +# See: https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners +# +# We specifically use this to ensure that pull requests must be approved by a code owner +# + + +# This is a global pattern matching all files anw owned by members of the oracle/coherence-dev-team team + +* @oracle/coherence-dev-team diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000000000..518d1bb77a286 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Create a bug report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behaviour: + +**Expected behaviour** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Environment (please complete the following information):** + - Coherence CE version (or Git SHA) + - Java version and Java vendor + - OS: [e.g. iOS] + - OS Version [e.g. 22] + - Is this a container/cloud environment, e.g. Docker, CRI-O, Kubernetes, if so include additional information about the container environment, versions etc. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..e8c6ac39d3fe0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +## Enhancement Request + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/support-question--rfa-.md b/.github/ISSUE_TEMPLATE/support-question--rfa-.md new file mode 100644 index 0000000000000..32799396a47b5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support-question--rfa-.md @@ -0,0 +1,40 @@ +--- +name: Support Question (RFA) +about: Support questions and requests for advice +title: '' +labels: RFA +assignees: '' + +--- + + + +## Type of question + +**Are you asking how to use a specific feature, or about general context and help around Coherence?** + +## Question + +**What did you do?** +A clear and concise description of the steps you took (or insert a code snippet). + +**What did you expect to see?** +A clear and concise description of what you expected to happen (or insert a code snippet). + +**What did you see instead? Under which circumstances?** +A clear and concise description of what you expected to happen (or insert a code snippet). + + +**Environment** +* Coherence version: + + insert release or Git SHA here + +**Additional context** +Add any other context about the question here. diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000000000..5e8b5e1f47d9a --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,85 @@ +# Copyright 2020 Oracle Corporation and/or its affiliates. All rights reserved. +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. + +# --------------------------------------------------------------------------- +# Coherence CE GitHub Actions CI build. +# --------------------------------------------------------------------------- + +name: CI Build + +on: + push: + branches-ignore: + - master + - p4-integ* + pull_request: + branches: + - '*' + +jobs: +# First run a simple compile and unit tests + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: '1.8' + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2- + - name: Build + shell: bash + run: | + export DEV_ROOT=$(pwd) + rm -rf ~/.m2/repository/com/oracle/coherence-ce/ + mvn --file prj/pom.xml -U --batch-mode -e -P -javadoc verify -pl coherence -am + +# If the build job runs successfully then run the verify stages in parallel + stage: + runs-on: ubuntu-latest + needs: + - build + strategy: + matrix: + stage: + - stage1 + - stage2 + - stage3 + - stage4 + - stage5 + - stage6 + - stage7 + - stage8 + - stage9 + - stage10 + - stage11 + - stage12 + - stage13 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: '1.8' + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Verify + shell: bash + run: | + echo "Running verify ${{ matrix.stage }}" + export DEV_ROOT=$(pwd) + rm -rf ~/.m2/repository/com/oracle/coherence-ce/ + mvn --file prj/pom.xml -U --batch-mode -e -P ${{ matrix.stage }},-default,-shell,-javadoc -Doptional -Dcoherence.SkipLargeMemoryTests=true verify + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000..8394bb7684017 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# used by intellij +# ignore these types of java files +*.class +*.jar +*.war +*.rar +*.gar + +# ignore these IntelliJ files +.idea +*.iml +*.ipr +*.iws + +# ignore these hidden and build specific files +.* +~* + +# ignore these folders +target +dist +_package + +#ignore these specific files +dependency-reduced-pom.xml + +# however allow these files +!.ignore +!.github +!.gitignore diff --git a/.ignore b/.ignore new file mode 100644 index 0000000000000..76679760e5315 --- /dev/null +++ b/.ignore @@ -0,0 +1,30 @@ +# used by intellij +# ignore these types of java files +*.class +*.jar +*.war +*.rar +*.gar + +# ignore these IntelliJ files +.idea +*.iml +*.ipr +*.iws + +# ignore these hidden and build specific files +.* +~* + +# ignore these folders +target +dist +_package + +#ignore these specific files +dependency-reduced-pom.xml + +# however allow these files +!.ignore +!.github +!.gitignore diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000..43fed9d82e59e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,56 @@ + +# Contributing to Coherence + +Oracle welcomes contributions to this repository from anyone. + +If you want to submit a pull request to fix a bug or enhance an existing +feature, please first open an issue and link to that issue when you +submit your pull request. + +If you have any questions about a possible submission, feel free to open +an issue too. + +## Contributing to the Oracle Coherence Community Edition repository + +Pull requests can be made under +[The Oracle Contributor Agreement](https://www.oracle.com/technetwork/community/oca-486395.html) (OCA). + +For pull requests to be accepted, the bottom of your commit message must have +the following line using your name and e-mail address as it appears in the +OCA Signatories list. + +``` +Signed-off-by: Your Name +``` + +This can be automatically added to pull requests by committing with: + +``` + git commit --signoff +``` + +Only pull requests from committers that can be verified as having +signed the OCA can be accepted. + +### Pull request process + +1. Fork this repository +1. Create a branch in your fork to implement the changes. We recommend using +the issue number as part of your branch name, e.g. `1234-fixes` +1. Ensure that all changes comply to project coding conventions +1. Ensure that there is at least one test that would fail without the fix and +passes post fix +1. Ensure that a full verify build passes without **any** test failures +1. Submit the pull request. *Do not leave the pull request blank*. Explain exactly +what your changes are meant to do and provide simple steps on how to validate +your changes, ideally referencing the test. Ensure that you reference the issue +you created as well. We will assign the pull request to 2-3 people for review +before it is merged. + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000000000..6e0307d0e28a2 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,35 @@ +Copyright (c) 2000, 2020, Oracle and/or its affiliates. + +The Universal Permissive License (UPL), Version 1.0 + +Subject to the condition set forth below, permission is hereby granted to any +person obtaining a copy of this software, associated documentation and/or data +(collectively the "Software"), free of charge and under any and all copyright +rights in the Software, and any and all patent rights owned or freely +licensable by each licensor hereunder covering either (i) the unmodified +Software as contributed to or provided by such licensor, or (ii) the Larger +Works (as defined below), to deal in both + +(a) the Software, and +(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +one is included with the Software (each a "Larger Work" to which the Software +is contributed by such licensors), + +without restriction, including without limitation the rights to copy, create +derivative works of, display, perform, and distribute the Software and make, +use, sell, offer for sale, import, export, have made, and have sold the +Software and the Larger Work(s), and to sublicense the foregoing rights on +either these or other terms. + +This license is subject to the following condition: +The above copyright notice and either this complete permission notice or at +a minimum a reference to the UPL must be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000000000..5bd1e2f3a1bd8 --- /dev/null +++ b/README.md @@ -0,0 +1,271 @@ + + +----- + + +[![License](http://img.shields.io/badge/license-UPL%201.0-blue.svg)](https://oss.oracle.com/licenses/upl/) + +# Oracle Coherence Community Edition + +## Contents +1. [Introduction](#intro) +1. [How to Get Coherence Community Edition](#acquire) +1. [Coherence Overview](#overview) +1. [Hello Coherence](#started) +1. [Building](#build) +1. [Integrations](#integrations) +1. [Documentation](https://docs.oracle.com/en/middleware/fusion-middleware/coherence/12.2.1.4/index.html) +1. [Contributing](#contrib) + +## Introduction + +[Coherence](http://coherence.java.net/) is scalable, fault-tolerant, cloud-ready, +distributed platform for building grid-based applications and reliably storing data. +The product is used at scale, for both compute and raw storage, in a vast array of +industries including critical financial trading systems, high performance telecommunication +products and eCommerce applications to name but a few. Typically these deployments +do not tolerate any downtime and Coherence is chosen due its novel features in +death detection, application data evolvability, and the robust, battle-hardened +core of the product allowing it to be seamlessly deployed and adapt within any ecosystem. + +At a high level, Coherence provides an implementation of the all too familiar `Map` +interface but rather than storing the associated data in the local process it is partitioned +(or sharded if you prefer) across a number of designated remote nodes. This allows +applications to not only distribute (and therefore scale) their storage across multiple +processes, machines, racks, and data centers but also to perform grid-based processing +to truly harness the cpu resources of the machines. The Coherence interface `NamedCache` +(an extension of `MapHow to Get Coherence Community Edition + +As Coherence is generally embedded into an application with the application using +Coherence APIs, thus the natural place to start is downloading from maven: + +```xml + + + com.oracle.coherence.ce + coherence + 14.1.1-0-0 + + +``` + +Other forms of acquiring Coherence include the official [Docker image](https://hub.docker.com/_/oracle-coherence-12c), +other language clients ([C++](http://github.com/oracle/coherence-cpp-extend-client) and +[.NET](http://github.com/oracle/coherence-dotnet-extend-client)), and for non community +edition features of the product please take a look at the [Oracle Technology Network](https://www.oracle.com/middleware/technologies/coherence-downloads.html). + +## Coherence Overview + +First and foremost, Coherence provides a fundamental service that is responsible +for all facets of clustering and is a common denominator / building block for all +other Coherence services. This service, referred to as 'service 0' internally, +ensures the mesh of members is maintained and responsive, taking action to collaboratively +evict, shun, or in some cases voluntarily depart the cluster when deemed necessary. +As members join and leave the cluster, other Coherence services are notified thus +allows those services to react accordingly. + +> Note: This part of the Coherence product has been in production for 10+ years, +> being the subject of some extensive and imaginative testing. While it has +> been discussed here it certainly is not something that customers, generally, +> interact with directly but is valuable to be aware of. + +Coherence services build on top of the clustering service, with the key implementations +to be aware of are PartitionedService, InvocationService, and ProxyService. + +In the majority of cases customers will deal with caches; a cache is represented +by a an implementation of `NamedCache`. Cache is an unfortunate name, as +many Coherence customers use Coherence as a system-of-record rather than a lossy +store of data. A cache is hosted by a service, generally the PartitionedService, +and is the entry point to storing, retrieving, aggregating, querying, and streaming +data. There are a number of features that caches provide: + +* Fundamental **key-based access**: get/put getAll/putAll +* Client-side and storage-side events + * **MapListeners** to asynchronously notify clients of changes to data + * **EventInterceptors** (either sync or async) to be notified storage level events, including +mutations, partition transfer, failover, etc +* **NearCaches** - locally cached data based on previous requests with local content +invalidated upon changes in storage tier +* **ViewCaches** - locally stored view of remote data that can be a subset based on a +predicate and is kept in sync real time +* **Queries** - distributed, parallel query evaluation to return matching key, values +or entries with potential to optimize performance with indices +* **Aggregations** - a map/reduce style aggregation where data is aggregated in parallel +on all storage nodes and results streamed back to the client for aggregation of +those results to produce a final result +* **Data local processing** - an ability to send a function to the relevant storage node +to execute processing logic for the appropriate entries with exclusive access +* **Partition local transactions** - an ability to perform scalable transactions by +associating data (thus being on the same partition) and manipulating other entries +on the same partition potentially across caches +* **Non-blocking / async NamedCache API** +* **C++ and .NET clients** - access the same NamedCache API from either C++ or .NET +* **Portable Object Format** - optimized serialization format, with the ability to +navigate the serialized form for optimized queries, aggregations, or data processing +* **Integration with Databases** - Database & third party data integration with +CacheStores including both synchronous or asynchronous writes +* **CohQL** - ansi-style query language with a console for adhoc queries +* **Topics** - distributed topics implementation offering pub/sub messaging with +the storage capacity the cluster and parallelizable subscribers + +There are also a number of non-functional features that Coherence provides: + +* **Rock solid clustering** - highly tuned and robust clustering stack that allows +Coherence to scale to thousands of members in a cluster with thousands of partitions +and terabytes of data being accessed, mutated, queried and aggregated concurrently +* **Safety first** - resilient data management that ensures backup copies are +on distinct machines, racks, or sites and the ability to maintain multiple backups +* **24/7 Availability** - zero down time with rolling redeploy of cluster members +to upgrade application or product versions + * Backwards and forwards compatibility of product upgrades, including major versions +* **Persistent Caches** - with the ability to use local file system persistence (thus +avoid extra network hops) and leverage Coherence consensus protocols to perform +distributed disk recovery when appropriate +* **Distributed State Snapshot** - ability to perform distributed point-in-time +snapshot of cluster state, and recover snapshot in this or a different cluster +(leverages persistence feature) +* **Lossy redundancy** - ability to reduce the redundancy guarantee by making backups +and/or persistence asynchronous from a client perspective +* **Single Mangement View** - provides insight into the cluster with a single +JMX server that provides a view of all members of the cluster +* **Management over REST** - all JMX data and operations can be performed over REST, +including cluster wide thread dumps and heapdumps +* **Non-cluster Access** - access to the cluster from the outside via proxies, +for distant (high latency) clients and for non-java languages such as C++ and .NET +* **Kubernetes friendly** - seamlessly and safely deploy applications to k8s with +our own [operator](https://github.com/oracle/coherence-operator) + + +## Hello Coherence + +### Prerequisites + + 1. Java - jdk8 or higher + 2. Maven - 3.6.3 or higher + +### CLI Hello Coherence + +The following example illustrated starting a **storage enabled** Coherence Server, +followed by a **storage disabled** Coherence Console. Using the console data is +inserted, retrieved, the console is terminated and started and data once again +retrieved to illustrate the permanence of the data. + +> **Note:** this example uses the OOTB cache configuration and therefore explicitly +> specifying the console is storage disabled is unnecessary. + +```shell + +$> mvn -DgroupId=com.oracle.coherence.ce -DartifactId=coherence -Dversion=14.1.1-0-0 dependency:get + +$> export COH_JAR=~/.m2/repository/com/oracle/coherence-ce/coherence/14.1.1-0-0/coherence-14.1.1-0-0.jar + +$> java -jar $COH_JAR & + +$> java -Dcoherence.distributed.localstorage=false -cp $COH_JAR com.tangosol.net.CacheFactory + +$console> (?): cache welcomes + +$console> (welcomes): get english +null + +$console> (welcomes): put english Hello +null + +$console> (welcomes): put spanish Hola +null + +$console> (welcomes): put french Bonjour +null + +$console> (welcomes): get english +Hello + +$console> (welcomes): list +french = Bonjour +spanish = Hola +english = Hello + +$console> (welcomes): bye + +$> java -cp $COH_JAR com.tangosol.net.CacheFactory + +$console> (?): cache welcomes + +$console> (welcomes): list +french = Bonjour +spanish = Hola +english = Hello + +$console> (welcomes): bye + +$> kill %1 + +``` + +```shell + +$> mvn -DgroupId=com.oracle.coherence.ce -DartifactId=coherence -Dversion=14.1.1-0-0 dependency:get + +$> export COH_JAR=~/.m2/repository/com/oracle/coherence-ce/coherence/14.1.1-0-0/coherence-14.1.1-0-0.jar + +$> java -jar $COH_JAR & + +$> java -cp $COH_JAR com.tangosol.coherence.dslquery.QueryPlus + +CohQL> select * from welcomes + +CohQL> insert into welcomes key 'english' value 'Hello' + +CohQL> insert into welcomes key 'spanish' value 'Hola' + +CohQL> insert into welcomes key 'french' value 'Bonjour' + +CohQL> select key(), value() from welcomes +Results +["french", "Bonjour"] +["english", "Hello"] +["spanish", "Hola"] + +CohQL> bye + +$> java -cp $COH_JAR com.tangosol.coherence.dslquery.QueryPlus + +CohQL> select key(), value() from welcomes +Results +["french", "Bonjour"] +["english", "Hello"] +["spanish", "Hola"] + +CohQL> bye + +$> kill %1 + +``` + +## Building + +```shell + +$> git clone git@github.com:oracle/coherence.git +$> cd coherence/prj +$> mvn clean install + +``` + +## Integrations + +# Contribute + +Interested in contributing? Please see our contribution [guidelines](CONTRIBUTING.md) for details. diff --git a/THIRD_PARTY_LICENSES.txt b/THIRD_PARTY_LICENSES.txt new file mode 100644 index 0000000000000..24b97f8083f71 --- /dev/null +++ b/THIRD_PARTY_LICENSES.txt @@ -0,0 +1,3201 @@ +Third Party Attributions + +The following software (or certain identified files distributed with the +software) may be included in this product. Unless otherwise specified, +the software identified in this file is licensed under the licenses +described below. The disclaimers and copyright notices provided are +based on information made available to Oracle by the third party +licensors listed. + +The following applies to all products licensed under the Apache 2.0 License: + +You may not use the identified files except in compliance with the +Apache License, Version 2.0 (the "License.") + +You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. +A copy of the license is also reproduced below. + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and +limitations under the License. + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +================================== +MVFlex Expression Language (MVEL) +================================== + MVEL 2.0 + Copyright (C) 2007 The Codehaus + Mike Brock, Dhanji Prasanna, John Graham, Mark Proctor +Apache License, Version 2.0 + +================================== +jackson-annotations +================================== +Jackson Annotations +Copyright (c) 2019 Tatu Saloranta +Apache License, Version 2.0 + +================================= +jackson-jaxrs-base +================================== +jackson-jaxrs-base +COPYRIGHT: Copyright FasterXML.com +Apache License, Version 2.0 + +================================= +jackson-jaxrs-json-provider +================================= +Jackson JAXRS JSON +Copyright (c) 2019 Tatu Saloranta +Copyright (c) Fasterxml +Apache License, Version 2.0 + +================================= +jackson-module-jaxb-annotations +================================= +jackson-module-jaxb-annotations +COPYRIGHT: Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi +Apache License, Version 2.0 + +================================ +OpenTracing API for Java +================================ +opentracing-util: 0.33.0, Apache 2.0 +opentracing-mock: 0.33.0, Apache 2.0 +opentracing-api: 0.33.0, Apache 2.0 +opentracing-noop: 0.33.0, Apache 2.0 +Copyright 2016-2019 The OpenTracing Authors +Apache License, Version 2.0 + +================================ +Apache log4j 1.2.17 +================================ +Apache log4j +Copyright 2010 The Apache Software Foundation +Apache License, Version 2.0 + +================================ +Apache log4j 2.11.1 +================================ +Apache log4j +COPYRIGHT: Copyright 1999-2017 Apache Software Foundation +LICENSE: Apache 2.0 + +===================================== +Modules: +log4j-1.2-api +log4j-api-java9 +log4j-api +log4j-appserver +log4j-bom +log4j-cassandra +log4j-core-its +log4j-core-java9 +log4j-core +log4j-couchdb +log4j-distribution +log4j-flume-ng +log4j-iostreams +log4j-jcl +log4j-jdbc-dbcp2 +log4j-jmx-gui +log4j-jpa +log4j-jul +log4j-liquibase +log4j-mongodb2 +log4j-mongodb3 +log4j-osgi +log4j-perf +log4j-samples +log4j-slf4j-impl +log4j-slf4j18-impl +log4j-taglib +log4j-to-slf4j +log4j-web + + +===================================== +4P Dependencies + +--------------- +FROM NOTICE FILE: + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +ResolverUtil.java +Copyright 2005-2006 Tim Fennell +LICENSE: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 + +Dumbster SMTP test server +Copyright 2004 Jason Paul Kitchen +LICENSE: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 + +TypeUtil.java +Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams +LICENSE: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 + +picocli (http://picocli.info) +Copyright 2017 Remko Popma +LICENSE: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 + + +--------------- +FROM POM FILES (Compile Dependencies Only) + + +--- +com.lmax disruptor 3.4.2 +Copyright 2011 - 2018 LMAX Ltd. +LICENSE: Apache 2.0 https://github.com/LMAX-Exchange/disruptor/blob/3.4.2/LICENCE.txt + + + +--- +org.springframework spring-aop 3.2.18.RELEASE +COPYRIGHT: Copyright 2002-2018 the original author or authors. +LICENSE: Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 + + +--- +slf4j-api 1.7.25 +slf4j-ext 1.7.25 +COPYRIGHT and LICENSE: +Copyright (c) 2004-2017 QOS.ch +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================== +Maven +================================== +Apache Maven +Copyright 2001-2018 The Apache Software Foundation + +Apache License, Version 2.0 + +This product includes software developed at The Apache Software Foundation (http://www.apache.org/). + +//////////////////////////////////////////////////////////////////////////////////////// +DEPENDENCIES +//////////////////////////////////////////////////////////////////////////////////////// + +AOP alliance (http://aopalliance.sourceforge.net) aopalliance:aopalliance:jar:1.0 + License: Public Domain + +JSR-250 Common Annotations for the JavaTM Platform (http://jcp.org/aboutJava/communityprocess/final/jsr250/index.html) javax.annotation:jsr250-api:jar:1.0 + Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. + License: COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 https://glassfish.java.net/public/CDDLv1.0.html (lib/jsr250-api.license) + +CDI APIs (http://www.seamframework.org/Weld/cdi-api) javax.enterprise:cdi-api:jar:1.0 + Copyright 2008, Red Hat Middleware LLC, and individual contributors by the @authors tag. See the copyright.txt in the distribution for a full listing of individual contributors. + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 (lib/cdi-api.license) + +Maven Aether Provider (http://maven.apache.org/ref/3.2.5/maven-aether-provider) org.apache.maven:maven-aether-provider:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-aether-provider.license) + +Maven Artifact (http://maven.apache.org/ref/3.2.5/maven-artifact) org.apache.maven:maven-artifact:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-artifact.license) + +Maven Compat (http://maven.apache.org/ref/3.2.5/maven-compat) org.apache.maven:maven-compat:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-compat.license) + +Maven Core (http://maven.apache.org/ref/3.2.5/maven-core) org.apache.maven:maven-core:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-core.license) + +Maven Embedder (http://maven.apache.org/ref/3.2.5/maven-embedder) org.apache.maven:maven-embedder:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-embedder.license) + +Maven Model (http://maven.apache.org/ref/3.2.5/maven-model) org.apache.maven:maven-model:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-model.license) + +Maven Model Builder (http://maven.apache.org/ref/3.2.5/maven-model-builder) org.apache.maven:maven-model-builder:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-model-builder.license) + +Maven Plugin API (http://maven.apache.org/ref/3.2.5/maven-plugin-api) org.apache.maven:maven-plugin-api:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-plugin-api.license) + +Maven Repository Metadata Model (http://maven.apache.org/ref/3.2.5/maven-repository-metadata) org.apache.maven:maven-repository-metadata:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-repository-metadata.license) + +Maven Settings (http://maven.apache.org/ref/3.2.5/maven-settings) org.apache.maven:maven-settings:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-settings.license) + +Maven Settings Builder (http://maven.apache.org/ref/3.2.5/maven-settings-builder) org.apache.maven:maven-settings-builder:jar:3.2.5 + Copyright 2001-2014 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/maven-settings-builder.license) + +Apache Maven Wagon :: Providers :: File Provider (http://maven.apache.org/wagon/wagon-providers/wagon-file) org.apache.maven.wagon:wagon-file:jar:2.8 + Copyright 2003-2019 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/wagon-file.license) + +Apache Maven Wagon :: Providers :: HTTP Provider (http://maven.apache.org/wagon/wagon-providers/wagon-http) org.apache.maven.wagon:wagon-http:jar:2.8 + Copyright 2003-2019 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/wagon-http.license) + +Apache Maven Wagon :: Providers :: HTTP Shared Library (http://maven.apache.org/wagon/wagon-providers/wagon-http-shared) org.apache.maven.wagon:wagon-http-shared:jar:2.8 + Copyright 2003-2019 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/wagon-http-shared.license) + +Apache Maven Wagon :: API (http://maven.apache.org/wagon/wagon-provider-api) org.apache.maven.wagon:wagon-provider-api:jar:2.8 + Copyright 2003-2019 The Apache Software Foundation + License: Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt (lib/wagon-provider-api.license) + +Aether API (http://www.eclipse.org/aether/aether-api/) org.eclipse.aether:aether-api:jar:1.0.0.v20140518 + Copyright (c) 2010-2014 Sonatype, Inc. + License: Eclipse Public License, Version 1.0 http://www.eclipse.org/legal/epl-v10.html (lib/aether-api.license) + +Aether Connector Basic (http://www.eclipse.org/aether/aether-connector-basic/) org.eclipse.aether:aether-connector-basic:jar:1.0.0.v20140518 + Copyright (c) 2010-2014 Sonatype, Inc. + License: Eclipse Public License, Version 1.0 http://www.eclipse.org/legal/epl-v10.html (lib/aether-connector-basic.license) + +Aether Implementation (http://www.eclipse.org/aether/aether-impl/) org.eclipse.aether:aether-impl:jar:1.0.0.v20140518 + Copyright (c) 2010-2014 Sonatype, Inc. + License: Eclipse Public License, Version 1.0 http://www.eclipse.org/legal/epl-v10.html (lib/aether-impl.license) + +Aether SPI (http://www.eclipse.org/aether/aether-spi/) org.eclipse.aether:aether-spi:jar:1.0.0.v20140518 + Copyright (c) 2010-2014 Sonatype, Inc. + License: Eclipse Public License, Version 1.0 http://www.eclipse.org/legal/epl-v10.html (lib/aether-spi.license) + +Aether Transport Wagon (http://www.eclipse.org/aether/aether-transport-wagon/) org.eclipse.aether:aether-transport-wagon:jar:1.0.0.v20140518 + Copyright (c) 2010-2014 Sonatype, Inc. + License: Eclipse Public License, Version 1.0 http://www.eclipse.org/legal/epl-v10.html (lib/aether-transport-wagon.license) + +Aether Utilities (http://www.eclipse.org/aether/aether-util/) org.eclipse.aether:aether-util:jar:1.0.0.v20140518 + Copyright (c) 2010-2014 Sonatype, Inc. + License: Eclipse Public License, Version 1.0 http://www.eclipse.org/legal/epl-v10.html (lib/aether-util.license) + +org.eclipse.sisu.inject (http://www.eclipse.org/sisu/org.eclipse.sisu.inject/) org.eclipse.sisu:org.eclipse.sisu.inject:eclipse-plugin:0.3.0.M1 + Copyright (c) 2010, 2015 Sonatype, Inc. + License: Eclipse Public License, Version 1.0 http://www.eclipse.org/legal/epl-v10.html (lib/org.eclipse.sisu.inject.license) + +org.eclipse.sisu.plexus (http://www.eclipse.org/sisu/org.eclipse.sisu.plexus/) org.eclipse.sisu:org.eclipse.sisu.plexus:eclipse-plugin:0.3.0.M1 + Copyright (c) 2010, 2015 Sonatype, Inc. + License: Eclipse Public License, Version 1.0 http://www.eclipse.org/legal/epl-v10.html (lib/org.eclipse.sisu.plexus.license) + +jsoup (http://jsoup.org/) org.jsoup:jsoup:jar:1.7.2 + Copyright (c) 2009-2019 Jonathan Hedley + License: The MIT License http://jsoup.com/license (lib/jsoup.license) + +SLF4J API Module (http://www.slf4j.org) org.slf4j:slf4j-api:jar:1.7.5 + Copyright (c) 2004-2017 QOS.ch All rights reserved. + License: MIT License http://www.opensource.org/licenses/mit-license.php (lib/slf4j-api.license) + +SLF4J Simple Binding (http://www.slf4j.org) org.slf4j:slf4j-simple:jar:1.7.5 + Copyright (c) 2004-2017 QOS.ch All rights reserved. + License: MIT License http://www.opensource.org/licenses/mit-license.php (lib/slf4j-simple.license) + +Plexus Cipher: encryption/decryption Component (http://spice.sonatype.org/plexus-cipher) org.sonatype.plexus:plexus-cipher:jar:1.7 + Copyright (c) 2008 Sonatype, Inc. All rights reserved. + License: Apache Public License 2.0 http://www.apache.org/licenses/LICENSE-2.0 (lib/plexus-cipher.license) + +Plexus Security Dispatcher Component (http://spice.sonatype.org/plexus-sec-dispatcher) org.sonatype.plexus:plexus-sec-dispatcher:jar:1.3 + Copyright (c) 2008 Sonatype, Inc. All rights reserved. + License: Apache Public License 2.0 http://www.apache.org/licenses/LICENSE-2.0 (lib/plexus-sec-dispatcher.license) + +---------------------------------------------------------------------------------------------- + +COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1 + +1. Definitions. + +1.1. "Contributor" means each individual or entity that creates or +contributes to the creation of Modifications. + +1.2. "Contributor Version" means the combination of the Original +Software, prior Modifications used by a Contributor (if any), and +the Modifications made by that particular Contributor. + +1.3. "Covered Software" means (a) the Original Software, or (b) +Modifications, or (c) the combination of files containing Original +Software with files containing Modifications, in each case including +portions thereof. + +1.4. "Executable" means the Covered Software in any form other than +Source Code. + +1.5. "Initial Developer" means the individual or entity that first +makes Original Software available under this License. + +1.6. "Larger Work" means a work which combines Covered Software or +portions thereof with code not governed by the terms of this License. + +1.7. "License" means this document. + +1.8. "Licensable" means having the right to grant, to the maximum +extent possible, whether at the time of the initial grant or +subsequently acquired, any and all of the rights conveyed herein. + +1.9. "Modifications" means the Source Code and Executable form of +any of the following: + +A. Any file that results from an addition to, deletion from or +modification of the contents of a file containing Original Software +or previous Modifications; + +B. Any new file that contains any part of the Original Software or +previous Modification; or + +C. Any new file that is contributed or otherwise made available +under the terms of this License. + +1.10. "Original Software" means the Source Code and Executable form +of computer software code that is originally released under this +License. + +1.11. "Patent Claims" means any patent claim(s), now owned or +hereafter acquired, including without limitation, method, process, +and apparatus claims, in any patent Licensable by grantor. + +1.12. "Source Code" means (a) the common form of computer software +code in which modifications are made and (b) associated +documentation included in or with such code. + +1.13. "You" (or "Your") means an individual or a legal entity +exercising rights under, and complying with all of the terms of, +this License. For legal entities, "You" includes any entity which +controls, is controlled by, or is under common control with You. For +purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + +2. License Grants. + +2.1. The Initial Developer Grant. + +Conditioned upon Your compliance with Section 3.1 below and subject +to third party intellectual property claims, the Initial Developer +hereby grants You a world-wide, royalty-free, non-exclusive license: + +(a) under intellectual property rights (other than patent or +trademark) Licensable by Initial Developer, to use, reproduce, +modify, display, perform, sublicense and distribute the Original +Software (or portions thereof), with or without Modifications, +and/or as part of a Larger Work; and + +(b) under Patent Claims infringed by the making, using or selling of +Original Software, to make, have made, use, practice, sell, and +offer for sale, and/or otherwise dispose of the Original Software +(or portions thereof). + +(c) The licenses granted in Sections 2.1(a) and (b) are effective on +the date Initial Developer first distributes or otherwise makes the +Original Software available to a third party under the terms of this +License. + +(d) Notwithstanding Section 2.1(b) above, no patent license is +granted: (1) for code that You delete from the Original Software, or +(2) for infringements caused by: (i) the modification of the +Original Software, or (ii) the combination of the Original Software +with other software or devices. + +2.2. Contributor Grant. + +Conditioned upon Your compliance with Section 3.1 below and subject +to third party intellectual property claims, each Contributor hereby +grants You a world-wide, royalty-free, non-exclusive license: + +(a) under intellectual property rights (other than patent or +trademark) Licensable by Contributor to use, reproduce, modify, +display, perform, sublicense and distribute the Modifications +created by such Contributor (or portions thereof), either on an +unmodified basis, with other Modifications, as Covered Software +and/or as part of a Larger Work; and + +(b) under Patent Claims infringed by the making, using, or selling +of Modifications made by that Contributor either alone and/or in +combination with its Contributor Version (or portions of such +combination), to make, use, sell, offer for sale, have made, and/or +otherwise dispose of: (1) Modifications made by that Contributor (or +portions thereof); and (2) the combination of Modifications made by +that Contributor with its Contributor Version (or portions of such +combination). + +(c) The licenses granted in Sections 2.2(a) and 2.2(b) are effective +on the date Contributor first distributes or otherwise makes the +Modifications available to a third party. + +(d) Notwithstanding Section 2.2(b) above, no patent license is +granted: (1) for any code that Contributor has deleted from the +Contributor Version; (2) for infringements caused by: (i) third +party modifications of Contributor Version, or (ii) the combination +of Modifications made by that Contributor with other software +(except as part of the Contributor Version) or other devices; or (3) +under Patent Claims infringed by Covered Software in the absence of +Modifications made by that Contributor. + +3. Distribution Obligations. + +3.1. Availability of Source Code. + +Any Covered Software that You distribute or otherwise make available +in Executable form must also be made available in Source Code form +and that Source Code form must be distributed only under the terms +of this License. You must include a copy of this License with every +copy of the Source Code form of the Covered Software You distribute +or otherwise make available. You must inform recipients of any such +Covered Software in Executable form as to how they can obtain such +Covered Software in Source Code form in a reasonable manner on or +through a medium customarily used for software exchange. + +3.2. Modifications. + +The Modifications that You create or to which You contribute are +governed by the terms of this License. You represent that You +believe Your Modifications are Your original creation(s) and/or You +have sufficient rights to grant the rights conveyed by this License. + +3.3. Required Notices. + +You must include a notice in each of Your Modifications that +identifies You as the Contributor of the Modification. You may not +remove or alter any copyright, patent or trademark notices contained +within the Covered Software, or any notices of licensing or any +descriptive text giving attribution to any Contributor or the +Initial Developer. + +3.4. Application of Additional Terms. + +You may not offer or impose any terms on any Covered Software in +Source Code form that alters or restricts the applicable version of +this License or the recipients' rights hereunder. You may choose to +offer, and to charge a fee for, warranty, support, indemnity or +liability obligations to one or more recipients of Covered Software. +However, you may do so only on Your own behalf, and not on behalf of +the Initial Developer or any Contributor. You must make it +absolutely clear that any such warranty, support, indemnity or +liability obligation is offered by You alone, and You hereby agree +to indemnify the Initial Developer and every Contributor for any +liability incurred by the Initial Developer or such Contributor as a +result of warranty, support, indemnity or liability terms You offer. + +3.5. Distribution of Executable Versions. + +You may distribute the Executable form of the Covered Software under +the terms of this License or under the terms of a license of Your +choice, which may contain terms different from this License, +provided that You are in compliance with the terms of this License +and that the license for the Executable form does not attempt to +limit or alter the recipient's rights in the Source Code form from +the rights set forth in this License. If You distribute the Covered +Software in Executable form under a different license, You must make +it absolutely clear that any terms which differ from this License +are offered by You alone, not by the Initial Developer or +Contributor. You hereby agree to indemnify the Initial Developer and +every Contributor for any liability incurred by the Initial +Developer or such Contributor as a result of any such terms You offer. + +3.6. Larger Works. + +You may create a Larger Work by combining Covered Software with +other code not governed by the terms of this License and distribute +the Larger Work as a single product. In such a case, You must make +sure the requirements of this License are fulfilled for the Covered +Software. + +4. Versions of the License. + +4.1. New Versions. + +Oracle is the initial license steward and may publish revised and/or +new versions of this License from time to time. Each version will be +given a distinguishing version number. Except as provided in Section +4.3, no one other than the license steward has the right to modify +this License. + +4.2. Effect of New Versions. + +You may always continue to use, distribute or otherwise make the +Covered Software available under the terms of the version of the +License under which You originally received the Covered Software. If +the Initial Developer includes a notice in the Original Software +prohibiting it from being distributed or otherwise made available +under any subsequent version of the License, You must distribute and +make the Covered Software available under the terms of the version +of the License under which You originally received the Covered +Software. Otherwise, You may also choose to use, distribute or +otherwise make the Covered Software available under the terms of any +subsequent version of the License published by the license steward. + +4.3. Modified Versions. + +When You are an Initial Developer and You want to create a new +license for Your Original Software, You may create and use a +modified version of this License if You: (a) rename the license and +remove any references to the name of the license steward (except to +note that the license differs from this License); and (b) otherwise +make it clear that the license contains terms which differ from this +License. + +5. DISCLAIMER OF WARRANTY. + +COVERED SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, +WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, WITHOUT LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE +IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR +NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF +THE COVERED SOFTWARE IS WITH YOU. SHOULD ANY COVERED SOFTWARE PROVE +DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY +OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, +REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN +ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED SOFTWARE IS +AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +6. TERMINATION. + +6.1. This License and the rights granted hereunder will terminate +automatically if You fail to comply with terms herein and fail to +cure such breach within 30 days of becoming aware of the breach. +Provisions which, by their nature, must remain in effect beyond the +termination of this License shall survive. + +6.2. If You assert a patent infringement claim (excluding +declaratory judgment actions) against Initial Developer or a +Contributor (the Initial Developer or Contributor against whom You +assert such claim is referred to as "Participant") alleging that the +Participant Software (meaning the Contributor Version where the +Participant is a Contributor or the Original Software where the +Participant is the Initial Developer) directly or indirectly +infringes any patent, then any and all rights granted directly or +indirectly to You by such Participant, the Initial Developer (if the +Initial Developer is not the Participant) and all Contributors under +Sections 2.1 and/or 2.2 of this License shall, upon 60 days notice +from Participant terminate prospectively and automatically at the +expiration of such 60 day notice period, unless if within such 60 +day period You withdraw Your claim with respect to the Participant +Software against such Participant either unilaterally or pursuant to +a written agreement with Participant. + +6.3. If You assert a patent infringement claim against Participant +alleging that the Participant Software directly or indirectly +infringes any patent where such claim is resolved (such as by +license or settlement) prior to the initiation of patent +infringement litigation, then the reasonable value of the licenses +granted by such Participant under Sections 2.1 or 2.2 shall be taken +into account in determining the amount or value of any payment or +license. + +6.4. In the event of termination under Sections 6.1 or 6.2 above, +all end user licenses that have been validly granted by You or any +distributor hereunder prior to termination (excluding licenses +granted to You by any distributor) shall survive termination. + +7. LIMITATION OF LIABILITY. + +UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT +(INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE +INITIAL DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF +COVERED SOFTWARE, OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE +TO ANY PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR +CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT +LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER +FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR +LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE +POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT +APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY RESULTING FROM SUCH +PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW PROHIBITS SUCH +LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR +LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION +AND LIMITATION MAY NOT APPLY TO YOU. + +8. U.S. GOVERNMENT END USERS. + +The Covered Software is a "commercial item," as that term is defined +in 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer +software" (as that term is defined at 48 C.F.R. ยง +252.227-7014(a)(1)) and "commercial computer software documentation" +as such terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent +with 48 C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 +(June 1995), all U.S. Government End Users acquire Covered Software +with only those rights set forth herein. This U.S. Government Rights +clause is in lieu of, and supersedes, any other FAR, DFAR, or other +clause or provision that addresses Government rights in computer +software under this License. + +9. MISCELLANEOUS. + +This License represents the complete agreement concerning subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. This License shall be governed by +the law of the jurisdiction specified in a notice contained within +the Original Software (except to the extent applicable law, if any, +provides otherwise), excluding such jurisdiction's conflict-of-law +provisions. Any litigation relating to this License shall be subject +to the jurisdiction of the courts located in the jurisdiction and +venue specified in a notice contained within the Original Software, +with the losing party responsible for costs, including, without +limitation, court costs and reasonable attorneys' fees and expenses. +The application of the United Nations Convention on Contracts for +the International Sale of Goods is expressly excluded. Any law or +regulation which provides that the language of a contract shall be +construed against the drafter shall not apply to this License. You +agree that You alone are responsible for compliance with the United +States export administration regulations (and the export control +laws and regulation of any other countries) when You use, distribute +or otherwise make available any Covered Software. + +10. RESPONSIBILITY FOR CLAIMS. + +As between Initial Developer and the Contributors, each party is +responsible for claims and damages arising, directly or indirectly, +out of its utilization of rights under this License and You agree to +work with Initial Developer and Contributors to distribute such +responsibility on an equitable basis. Nothing herein is intended or +shall be deemed to constitute any admission of liability. + +NOTICE PURSUANT TO SECTION 9 OF THE COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) + +The code released under the CDDL shall be governed by the laws of the +State of California (excluding conflict-of-law provisions). Any +litigation relating to this License shall be subject to the jurisdiction +of the Federal Courts of the Northern District of California and the +state courts of the State of California, with venue lying in Santa Clara +County, California. + +---------------------------------------------------------------------------------------------- + +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and +documentation distributed under this Agreement, and +b) in the case of each subsequent Contributor: + +i) changes to the Program, and + +ii) additions to the Program; + +where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) are +not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + +a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and +such derivative works, in source code and object code form. + +b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under +Licensed Patents to make, use, sell, offer to sell, import and otherwise +transfer the Contribution of such Contributor, if any, in source code and +object code form. This patent license shall apply to the combination of the +Contribution and the Program if, at the time the Contribution is added by the +Contributor, such addition of the Contribution causes such combination to be +covered by the Licensed Patents. The patent license shall not apply to any +other combinations which include the Contribution. No hardware per se is +licensed hereunder. + +c) Recipient understands that although each Contributor grants the +licenses to its Contributions set forth herein, no assurances are provided by +any Contributor that the Program does not infringe the patent or other +intellectual property rights of any other entity. Each Contributor disclaims +any liability to Recipient for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a condition to +exercising the rights and licenses granted hereunder, each Recipient hereby +assumes sole responsibility to secure any other intellectual property rights +needed, if any. For example, if a third party patent license is required to +allow Recipient to distribute the Program, it is Recipient's responsibility to +acquire that license before distributing the Program. + +d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright license +set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + +a) it complies with the terms and conditions of this Agreement; and + +b) its license agreement: + +i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose; + +ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + +iii) states that any provisions which differ from this Agreement are +offered by that Contributor alone and not by any other party; and + +iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner on +or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + +a) it must be made available under this Agreement; and + +b) a copy of this Agreement must be included with each copy of the +Program. + +Contributors may not remove or alter any copyright notices contained within the +Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor +to control, and cooperate with the Commercial Contributor in, the defense and +any related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its exercise +of rights under this Agreement, including but not limited to the risks and +costs of program errors, compliance with applicable laws, damage to or loss of +data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software or +hardware) infringes such Recipient's patent(s), then such Recipient's rights +granted under Section 2(b) shall terminate as of the date such litigation is +filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation +may assign the responsibility to +serve as the Agreement Steward to a suitable separate entity. Each new version +of the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version of +the Agreement under which it was received. In addition, after a new version of +the Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to +the intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial +in any resulting litigation. + +---------------------------------------------------------------------------------------------- + +The MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Apache License, Version 2.0 + +================================== +ASM +================================== +Copyright INRIA, France Telecom +https://gitlab.ow2.org/asm/asm/blob/master/LICENSE.txt + +ASM: a very small and fast Java bytecode manipulation framework + Copyright (c) 2000-2011 INRIA, France Telecom + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + THE POSSIBILITY OF SUCH DAMAGE. + + +================================== +JLine +================================== +Copyright Marc Prud'hommeaux +Copyright (c) 2002-2016, the original author or authors. +All rights reserved. + +http://www.opensource.org/licenses/bsd-license.php + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the following +conditions are met: + +Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with +the distribution. + +Neither the name of JLine nor the names of its contributors +may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. +============= +jansi 1.12 Apache 2.0 +hawtjni 2.4.1 Apache 2.0 +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +================================== +Jakarta Restful Web Services +================================== +jakarta.ws.rs:jakarta.ws.rs-api + +Copyright (c) 2019 Eclipse Foundation. +Copyright (c) 2010,2019 Oracle and/or its affiliates. All rights reserved. + +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS + ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR + DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE + OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program + by such Contributor itself or anyone acting on such Contributor's + behalf. Contributions do not include changes or additions to the + Program that are not Modified Works. + +"Contributor" means any person or entity that Distributes the +Program. + +"Licensed Patents" mean patent claims licensable by a Contributor +which are necessarily infringed by the use or sale of its Contribution +alone or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with +this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or +other form, that is based on (or derived from) the Program and for +which the editorial revisions, annotations, elaborations, or other +modifications represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form +that results from an addition to, deletion from, or modification +of the contents of the Program, including, for purposes of clarity +any new file in Source Code form that contains any contents of the +Program. Modified Works shall not include works that contain only +declarations, interfaces, types, classes, structures, or files of +the Program solely in each case in order to link to, bind by name, +or subclass the Program or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly + display, publicly perform, Distribute and sublicense the Contribution + of such Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, + at the time the Contribution is added by the Contributor, such + addition of the Contribution causes such combination to be covered + by the Licensed Patents. The patent license shall not apply to + any other combinations which include the Contribution. No hardware + per se is licensed hereunder. + + c) Recipient understands that although each Contributor grants + the licenses to its Contributions set forth herein, no assurances + are provided by any Contributor that the Program does not infringe + the patent or other intellectual property rights of any other + entity. Each Contributor disclaims any liability to Recipient + for claims brought by any other entity based on infringement of + intellectual property rights or otherwise. As a condition to + exercising the rights and licenses granted hereunder, each Recipient + hereby assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no Contributor + makes additional grants to any Recipient (other than those set + forth in this Agreement) as a result of such Recipient's receipt + of the Program under the terms of a Secondary License (if permitted + under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors + all warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors + all liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file + or files made available under a Secondary License, and (ii) the + initial Contributor attached to the Source Code the notice described + in Exhibit A of this Agreement, then the Program may be made + available under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy +of the Program which they Distribute, provided that Contributors +may add their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While +this license is intended to facilitate the commercial use of the +Program, the Contributor who includes the Program in a commercial +product offering should do so in a manner which does not create +potential liability for other Contributors. Therefore, if a Contributor +includes the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any +losses, damages and costs (collectively "Losses") arising from +claims, lawsuits and other legal actions brought by a third party +against the Indemnified Contributor to the extent caused by the +acts or omissions of such Commercial Contributor in connection with +its distribution of the Program in a commercial product offering. +The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. +In order to qualify, an Indemnified Contributor must: a) promptly +notify the Commercial Contributor in writing of such claim, and b) +allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any +such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have +to defend claims against the other Contributors related to those +performance claims and warranties, and if a court requires any other +Contributor to pay any damages as a result, the Commercial Contributor +must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS +OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS +OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A +PARTICULAR PURPOSE. Each Recipient is solely responsible for +determining the appropriateness of using and distributing the Program +and assumes all risks associated with its exercise of rights under +this Agreement, including but not limited to the risks and costs +of program errors, compliance with applicable laws, damage to or +loss of data, programs or equipment, and unavailability or interruption +of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT +LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR +DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED +HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability +of the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to +the minimum extent necessary to make such provision valid and +enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging +that the Program itself (excluding combinations of the Program with +other software or hardware) infringes such Recipient's patent(s), +then such Recipient's rights granted under Section 2(b) shall +terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of +this Agreement and does not cure such failure in a reasonable period +of time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease +use and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any +licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted +and may only be modified in the following manner. The Agreement +Steward reserves the right to publish new versions (including +revisions) of this Agreement from time to time. No one other than +the Agreement Steward has the right to modify this Agreement. The +Eclipse Foundation is the initial Agreement Steward. The Eclipse +Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the +Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be Distributed subject to the +version of the Agreement under which it was received. In addition, +after a new version of the Agreement is published, Contributor may +elect to Distribute the Program (including its Contributions) under +the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly +granted under this Agreement are reserved. Nothing in this Agreement +is intended to be enforceable by any entity that is not a Contributor +or Recipient. No third-party beneficiary rights are created under +this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set +forth in the Eclipse Public License, v. 2.0 are satisfied: {name +license(s), version(s), and exceptions or additional permissions +here}." + + Simply including a copy of this Agreement, including this Exhibit + A is not sufficient to license the Source Code under Secondary + Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a + LICENSE file in a relevant directory) where a recipient would be + likely to look for such a notice. + + You may add additional accurate notices of copyright ownership. + + +************************** NOTICE FILE: ************************** + +Notices for the Jakarta RESTful Web Services Project + +This content is produced and maintained by the Jakarta RESTful Web +Services project. + + Project home: https://projects.eclipse.org/projects/ee4j.jaxrs + +Trademarks + +Jakarta RESTful Web Services is a trademark of the Eclipse Foundation. +Copyright + +All content is the property of the respective authors or their +employers. For more information regarding authorship of content, +please consult the listed source code repository logs. Declared +Project Licenses + +This program and the accompanying materials are made available under +the terms of the Eclipse Public License v. 2.0 which is available +at http://www.eclipse.org/legal/epl-2.0. This Source Code may also +be made available under the following Secondary Licenses when the +conditions for such availability set forth in the Eclipse Public +License v. 2.0 are satisfied: GNU General Public License, version +2 with the GNU Classpath Exception which is available at +https://www.gnu.org/software/classpath/license.html. + +SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +Source Code + +The project maintains the following source code repositories: + + https://github.com/eclipse-ee4j/jaxrs-api + + +Third-party Content + +This project leverages the following third party content. + + + +--- javaee-api (7.0) + + License: Apache-2.0 AND W3C + +--- The W3C SOFTWARE NOTICE AND LICENSE (W3C) + +View Summary of W3C Software Notice and License (W3C) on TLDRLegal +» (Disclaimer) W3C® SOFTWARE NOTICE AND LICENSE + +Copyright © 1994-2001 World Wide Web Consortium, (Massachusetts +Institute of Technology, Institut National de Recherche en Informatique +et en Automatique, Keio University). All Rights Reserved. +http://www.w3.org/Consortium/Legal/ + +This W3C work (including software, documents, or other related +items) is being provided by the copyright holders under the following +license. By obtaining, using and/or copying this work, you (the +licensee) agree that you have read, understood, and will comply +with the following terms and conditions: + +Permission to use, copy, modify, and distribute this software and +its documentation, with or without modification, for any purpose +and without fee or royalty is hereby granted, provided that you +include the following on ALL copies of the software and documentation +or portions thereof, including modifications, that you make: + + 1. The full text of this NOTICE in a location viewable to users + of the redistributed or derivative work. + + 2. Any pre-existing intellectual property disclaimers, notices, + or terms and conditions. If none exist, a short notice of the + following form (hypertext is preferred, text is permitted) + should be used within the body of any redistributed or derivative + code: "Copyright © [$date-of-software] World Wide Web Consortium, + (Massachusetts Institute of Technology, Institut National de + Recherche en Informatique et en Automatique, Keio University). + All Rights Reserved. http://www.w3.org/Consortium/Legal/" + + 3. Notice of any changes or modifications to the W3C files, + including the date changes were made. (We recommend you provide + URIs to the location from which the code is derived.) + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT +HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR +FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE +OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, +TRADEMARKS OR OTHER RIGHTS. + +COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, +SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE +SOFTWARE OR DOCUMENTATION. + +The name and trademarks of copyright holders may NOT be used in +advertising or publicity pertaining to the software without specific, +written prior permission. Title to copyright in this software and +any associated documentation will at all times remain with copyright +holders. + + +================================== +Netty +================================== + The Netty Project + ================= + +Please visit the Netty web site for more information: + + * http://netty.io/ + +Copyright 2014 The Netty Project +Apache License, Version 2.0 + +================================== +jackson-core +================================== +Jackson Core +Copyright © 2008–2019 FasterXML. All rights reserved. + +This copy of Jackson JSON processor streaming parser/generator is licensed under the +Apache (Software) License, version 2.0 ("the License"). +See the License for details about distribution rights, and the +specific rights regarding derivate works. + +You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +NOTICE FILE: +=============== +# Jackson JSON processor + +Jackson is a high-performance, Free/Open Source JSON processing library. +It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has +been in development since 2007. +It is currently developed by a community of developers, as well as supported +commercially by FasterXML.com. + +## Licensing + +Jackson core and extension components may licensed under different licenses. +To find the details that apply to this artifact see the accompanying LICENSE file. +For more information, including possible other licensing options, contact +FasterXML.com (http://fasterxml.com). + +## Credits + +A list of contributors may be found from CREDITS file, which is included +in some artifacts (usually source distributions); but is always available +from the source code management (SCM) system project uses. +=============== +Apache License, Version 2.0 + +================================== +jackson-databind +================================== +Jackson Databind +Copyright (c) 2019 Tatu Saloranta +Apache License, Version 2.0 + + +================================ +SLF4J +================================ +Simple Logging Facade for Java (SLF4J ) 1.7.26 + +Copyright (c) 2004-2017 QOS.ch +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +================================ +Apache Ant +================================ +Apache Ant +Copyright 1999-2019 The Apache Software Foundation +Apache License, Version 2.0 + +The task is based on code Copyright (c) 2002, Landmark +Graphics Corp that has been kindly donated to the Apache Software +Foundation. + +W3C® SOFTWARE NOTICE AND LICENSE +http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 + +This work (and included software, documentation such as READMEs, or other +related items) is being provided by the copyright holders under the following +license. By obtaining, using and/or copying this work, you (the licensee) agree +that you have read, understood, and will comply with the following terms and +conditions. + +Permission to copy, modify, and distribute this software and its documentation, +with or without modification, for any purpose and without fee or royalty is +hereby granted, provided that you include the following on ALL copies of the +software and documentation or portions thereof, including modifications: + + 1. The full text of this NOTICE in a location viewable to users of the + redistributed or derivative work. + 2. Any pre-existing intellectual property disclaimers, notices, or terms + and conditions. If none exist, the W3C Software Short Notice should be + included (hypertext is preferred, text is permitted) within the body + of any redistributed or derivative code. + 3. Notice of any changes or modifications to the files, including the date + changes were made. (We recommend you provide URIs to the location from + which the code is derived.) + +THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE +NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT +THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY +PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. + +COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. + +The name and trademarks of copyright holders may NOT be used in advertising or +publicity pertaining to the software without specific, written prior permission. +Title to copyright in this software and any associated documentation will at +all times remain with copyright holders. + +____________________________________ + +This formulation of W3C's notice and license became active on December 31 2002. +This version removes the copyright ownership notice such that this license can +be used with materials other than those owned by the W3C, reflects that ERCIM +is now a host of the W3C, includes references to this specific dated version of +the license, and removes the ambiguous grant of "use". Otherwise, this version +is the same as the previous version and is written so as to preserve the Free +Software Foundation's assessment of GPL compatibility and OSI's certification +under the Open Source Definition. Please see our Copyright FAQ for common +questions about using materials from our site, including specific terms and +conditions for packages like libwww, Amaya, and Jigsaw. Other questions about +this notice can be directed to site-policy@w3.org. + +Joseph Reagle + +This license came from: http://www.megginson.com/SAX/copying.html + However please note future versions of SAX may be covered + under http://saxproject.org/?selected=pd + +SAX2 is Free! + +I hereby abandon any property rights to SAX 2.0 (the Simple API for +XML), and release all of the SAX 2.0 source code, compiled code, and +documentation contained in this distribution into the Public Domain. +SAX comes with NO WARRANTY or guarantee of fitness for any +purpose. + +David Megginson, david@megginson.com +2000-05-05 + +================================ +AQute Bndlib +================================ +AQute Bndlib +Copyright: 2006-2010, OSGi Alliance +Apache License, Version 2.0 + +================================ +Genson +================================ +Genson +Copyright 2011-2014 Genson - Cepoi Eugen +Apache License, Version 2.0 + +================================ +Jaeger Tracing Client +================================ +Jaeger Tracing Client +Copyright (c) 2018, The Jaeger Authors +Apache License, Version 2.0 + +-------------------------------------------- +Fourth Party Dependencies +-------------------------------------------- +"SLF4J API Module" (org.slf4j:slf4j-api) + Copyright (c) 2004-2011 QOS.ch + +The MIT License SPDX short identifier: MIT + +Further resources on the MIT License Copyright + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------- +"Apache Thrift" (org.apache.thrift:libthrift) +Apache Thrift +Copyright (C) 2006 - 2019, The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). +Apache License Version 2.0 +-------------------------------------------- +"okhttp" (com.squareup.okhttp3:okhttp) + Copyright (C) 2018 Square, Inc. + Apache License Version 2.0 +-------------------------------------------- +"Okio" (com.squareup.okio:okio) + Copyright (C) 2019 Square, Inc. + Apache License Version 2.0 +-------------------------------------------- +"org.jetbrains.kotlin:kotlin-stdlib" (org.jetbrains.kotlin:kotlin-stdlib) + Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + Apache License Version 2.0 +-------------------------------------------- +"org.jetbrains.kotlin:kotlin-stdlib-common" (org.jetbrains.kotlin:kotlin-stdlib-common) + Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. + Apache License Version 2.0 +-------------------------------------------- +"OpenTracing API" (io.opentracing:opentracing-api) + Copyright 2016-2019 The OpenTracing Authors + Apache License Version 2.0 +-------------------------------------------- +"OpenTracing-util" (io.opentracing:opentracing-util) + Copyright 2016-2019 The OpenTracing Authors + Apache License Version 2.0 +-------------------------------------------- +"OpenTracing-noop" (io.opentracing:opentracing-noop) + Copyright 2016-2019 The OpenTracing Authors + Apache License Version 2.0 +-------------------------------------------- +"Gson" (com.google.code.gson:gson) + Copyright (C) 2017,2018 The Gson authors + Copyright (C) 2008,2014 Google Inc. + Copyright (C) 2010 The Android Open Source Project + Apache License Version 2.0 +-------------------------------------------- +"Tracer resolver" (io.opentracing.contrib:opentracing-tracerresolver) + Copyright 2017-2019 The OpenTracing Authors + Apache License Version 2.0 +-------------------------------------------- + + +================================ +JUnit +================================ +JUnit +Copyright JUnit + +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial code and + documentation distributed under this Agreement, and + b) in the case of each subsequent Contributor: + + i) changes to the Program, and + + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) are +not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and +such derivative works, in source code and object code form. + + b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under +Licensed Patents to make, use, sell, offer to sell, import and otherwise +transfer the Contribution of such Contributor, if any, in source code and +object code form. This patent license shall apply to the combination of the +Contribution and the Program if, at the time the Contribution is added by the +Contributor, such addition of the Contribution causes such combination to be +covered by the Licensed Patents. The patent license shall not apply to any +other combinations which include the Contribution. No hardware per se is +licensed hereunder. + + c) Recipient understands that although each Contributor grants the +licenses to its Contributions set forth herein, no assurances are provided by +any Contributor that the Program does not infringe the patent or other +intellectual property rights of any other entity. Each Contributor disclaims +any liability to Recipient for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a condition to +exercising the rights and licenses granted hereunder, each Recipient hereby +assumes sole responsibility to secure any other intellectual property rights +needed, if any. For example, if a third party patent license is required to +allow Recipient to distribute the Program, it is Recipient's responsibility to +acquire that license before distributing the Program. + + d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright license +set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + + b) its license agreement: + + i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose; + + ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + + iii) states that any provisions which differ from this Agreement are +offered by that Contributor alone and not by any other party; and + + iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner on +or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + + b) a copy of this Agreement must be included with each copy of the +Program. + +Contributors may not remove or alter any copyright notices contained within the +Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor +to control, and cooperate with the Commercial Contributor in, the defense and +any related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its exercise +of rights under this Agreement, including but not limited to the risks and +costs of program errors, compliance with applicable laws, damage to or loss of +data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software or +hardware) infringes such Recipient's patent(s), then such Recipient's rights +granted under Section 2(b) shall terminate as of the date such litigation is +filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to +serve as the Agreement Steward to a suitable separate entity. Each new version +of the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version of +the Agreement under which it was received. In addition, after a new version of +the Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to +the intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial +in any resulting litigation. + + +------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------- +====================================================================================== +4th party: hamcrest 2.2 + +BSD License + +Copyright (c) 2000-2015 www.hamcrest.org +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. Redistributions in binary form must reproduce +the above copyright notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. + +Neither the name of Hamcrest nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + + +================================ +spymemcached +================================ +spymemcached +couchbase/spymemcached is licensed under the MIT License. + +Copyright (c) 2006-2009 Dustin Sallings +Copyright (c) 2009-2011 Couchbase, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +================================ +Apache Felix Framework +================================ +Apache Felix Framework +Copyright 2019 Apache Software Foundation +Apache License, Version 2.0 + +------------------------------------------------------------------------------------------------------------------------- +NOTICE file contents: + +Apache Felix Framework +Copyright 2006-2017 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + + +This product includes software developed at +The OSGi Alliance (http://www.osgi.org/). +Copyright (c) OSGi Alliance (2000, 2015). +Licensed under the Apache License 2.0. +------------------------------------------------------------------------------------------------------------------------- +Fourth party dependencies + +With Apache license: + +org.apache.felix:org.apache.felix.resolver 1.14.0 +org.osgi:org.osgi.core 5.0.0 +org.osgi:org.osgi.annotation 6.0.0 +------------------------------------------------------------------------------------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +------------------------------------------------------------------------------------------------------------------------- +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +------------------------------------------------------------------------------------------------------------------------- +Copyright 2015 The Apache Software Foundation + +This software was developed at the Apache Software Foundation +(http://www.apache.org) and may have dependencies on other +Apache software licensed under Apache License 2.0. + +I. Included Third-Party Software + +This product includes software developed at +The OSGi Alliance (http://www.osgi.org/). +Copyright (c) OSGi Alliance (2000, 2014). +Licensed under the Apache License 2.0. + +II. Used Third-Party Software + +This product uses software developed at +The OSGi Alliance (http://www.osgi.org/). +Copyright (c) OSGi Alliance (2000, 2014). +Licensed under the Apache License 2.0. + +III. License Summary +- Apache License 2.0 +------------------------------------------------------------------------------------------------------------------------- +License for Animal Sniffer Annotations: + + The MIT License + + Copyright (c) 2009 codehaus.org. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +------------------------------------------------------------------------------------------------------------------------- +Copyright (c) OSGi Alliance (2008, 2018). All Rights Reserved. + + + +================================ +Jettison +================================ +Jettison +Copyright 2006 Envoi Solutions LLC +Apache License, Version 2.0 + +================================ +Hamcrest +================================ +Hamcrest +BSD License + +Copyright (c) 2000-2015 www.hamcrest.org +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. Redistributions in binary form must reproduce +the above copyright notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the distribution. + +Neither the name of Hamcrest nor the names of its contributors may be used to endorse +or promote products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + + +================================ +JaCoCo +================================ +JaCoCo +Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors + +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial code and + documentation distributed under this Agreement, and + b) in the case of each subsequent Contributor: + + i) changes to the Program, and + + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in +conjunction with the Program under their own license agreement, and (ii) are +not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents " mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free copyright license to +reproduce, prepare derivative works of, publicly display, publicly perform, +distribute and sublicense the Contribution of such Contributor, if any, and +such derivative works, in source code and object code form. + + b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under +Licensed Patents to make, use, sell, offer to sell, import and otherwise +transfer the Contribution of such Contributor, if any, in source code and +object code form. This patent license shall apply to the combination of the +Contribution and the Program if, at the time the Contribution is added by the +Contributor, such addition of the Contribution causes such combination to be +covered by the Licensed Patents. The patent license shall not apply to any +other combinations which include the Contribution. No hardware per se is +licensed hereunder. + + c) Recipient understands that although each Contributor grants the +licenses to its Contributions set forth herein, no assurances are provided by +any Contributor that the Program does not infringe the patent or other +intellectual property rights of any other entity. Each Contributor disclaims +any liability to Recipient for claims brought by any other entity based on +infringement of intellectual property rights or otherwise. As a condition to +exercising the rights and licenses granted hereunder, each Recipient hereby +assumes sole responsibility to secure any other intellectual property rights +needed, if any. For example, if a third party patent license is required to +allow Recipient to distribute the Program, it is Recipient's responsibility to +acquire that license before distributing the Program. + + d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright license +set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + + b) its license agreement: + + i) effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose; + + ii) effectively excludes on behalf of all Contributors all liability for +damages, including direct, indirect, special, incidental and consequential +damages, such as lost profits; + + iii) states that any provisions which differ from this Agreement are +offered by that Contributor alone and not by any other party; and + + iv) states that source code for the Program is available from such +Contributor, and informs licensees how to obtain it in a reasonable manner on +or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + + b) a copy of this Agreement must be included with each copy of the +Program. + +Contributors may not remove or alter any copyright notices contained within the +Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor +to control, and cooperate with the Commercial Contributor in, the defense and +any related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its exercise +of rights under this Agreement, including but not limited to the risks and +costs of program errors, compliance with applicable laws, damage to or loss of +data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software or +hardware) infringes such Recipient's patent(s), then such Recipient's rights +granted under Section 2(b) shall terminate as of the date such litigation is +filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to +serve as the Agreement Steward to a suitable separate entity. Each new version +of the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version of +the Agreement under which it was received. In addition, after a new version of +the Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to +the intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial +in any resulting litigation. + +================================================================= + +Trademarks +Java and all Java-based trademarks are trademarks of Oracle Corporation in the +United States, other countries, or both. Eclipse and all Eclipse related trademarks +and logos are trademarks of the Eclipse Foundation, Inc. OSGi is a trademark, +registered trademark, or service mark of The OSGi Alliance in the US and other +countries. Apache Ant and Apache Maven are trademarks of the Apache Software +Foundation. Android and Dalvik are trademarks of Google Inc. All other trademarks + are the property of their respective owners. + +Third Party Content +The Content includes items that have been sourced from third parties as set out below. +-- + +Apache Ant 1.7.1 +Licensed under the Apache License 2.0. + +-- + +ASM 6.2.1 +ASM is subject to the terms and conditions of the following license: +Copyright (c) 2012 France Télécom +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + +-- + +args4j 2.0.28 +Copyright (c) 2013 Kohsuke Kawaguchi and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-- + +Google Code Prettify 2010/07/21 +Google Code Prettify is subject to the terms and conditions of the Apache License 2.0. + +================================================================= + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +================================ +Mockito +================================ +Mockito +The MIT License + +Copyright (c) 2007 Mockito contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +====================================================================== +FOURTH PARTY LIBRARIES + +Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.9.10 - http://bytebuddy.net/byte-buddy) +Byte Buddy Java agent (net.bytebuddy:byte-buddy-agent:1.9.10 - http://bytebuddy.net/byte-buddy-agent) +Objenesis (org.objenesis:objenesis:2.6 - http://objenesis.org) +Apache License, Version 2.0 + diff --git a/bin/cfgcommon.sh b/bin/cfgcommon.sh new file mode 100644 index 0000000000000..7b898c5f069fb --- /dev/null +++ b/bin/cfgcommon.sh @@ -0,0 +1,188 @@ +#!/bin/bash + +# +# Copyright (c) 2000, 2020, Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# + +# +# This script sets all environment variables necessary to build Coherence, +# however should be sourced by other scripts. These scripts should define +# _JAVA_HOME_CMD as a platform specific means of locating the correct +# JAVA_HOME. +# +# This script is responsible for the following environment variables: +# +# DEV_ROOT e.g. /dev +# JAVA_HOME e.g. /usr/java/jdk1.6 +# MAVEN_HOME e.g. /dev/tools/maven +# TDE_HOME e.g. /dev/tools/tde +# CLASSPATH e.g. +# +# _TDE_HOME saved TDE_HOME +# _CLASSPATH saved CLASSPATH +# _PATH saved PATH +# + +# +# Reset the build environment if the "-reset" flag was passed. +# +function reset + { + if [ -z $DEV_ROOT ]; then + echo Build environment already reset. + return 0 + fi + + if [ -z $_JAVA_HOME ]; then + unset JAVA_HOME + else + export JAVA_HOME=$_JAVA_HOME + fi + + if [ -z $_MAVEN_HOME ]; then + unset MAVEN_HOME + else + export MAVEN_HOME=$_MAVEN_HOME + fi + + if [ -z $_TDE_HOME ]; then + unset TDE_HOME + else + export TDE_HOME=$_TDE_HOME + fi + + if [ -z $_CLASSPATH ]; then + unset CLASSPATH + else + export CLASSPATH=$_CLASSPATH + fi + + if [ -z $_PATH ]; then + unset PATH + else + export PATH=$_PATH + fi + + unset DEV_ROOT + unset _JAVA_HOME + unset _MAVEN_HOME + unset _TDE_HOME + unset _CLASSPATH + unset _PATH + + echo Build environment reset. + } + +# +# Setup the shell for Coherence builds. +# +function setup + { + # + # Determine the root of the dev tree + # + cd $SCRIPTS_DIR/.. + DEV_ROOT=`pwd` + cd - > /dev/null + + # + # Ensure proper Java version, attempt selection if necessary + # + _JAVA_HOME=$JAVA_HOME + _VERSION_REQUIRED=1.8 + + if [ -z $JAVA_HOME ] || [ "$($JAVA_HOME/bin/java -version 2>&1 | sed 's/java version "\(.*\)\.\(.*\)\..*"/\1.\2/; 1q')" != "$_VERSION_REQUIRED" ]; then + # Try to find the correct version + JAVA_HOME=`eval $_JAVA_HOME_CMD` + + if [ ! -d "$JAVA_HOME" ]; then + _ERROR="Set JAVA_HOME as Java version $_VERSION_REQUIRED could not be located using command: $_JAVA_HOME_CMD" + else + # Ensure that it has been found + _VERSION=$($JAVA_HOME/bin/java -version 2>&1 | sed 's/java version "\(.*\)\.\(.*\)\..*"/\1.\2/; 1q') + + if [ "$_VERSION" != "$_VERSION_REQUIRED" ]; then + _ERROR="Incorrect JDK version $_VERSION at $JAVA_HOME; $_VERSION_REQUIRED is required, please export JAVA_HOME appropriately." + fi + fi + + if [ ! -z "$_ERROR" ]; then + + if [ -z $_JAVA_HOME ]; then + unset JAVA_HOME + else + JAVA_HOME=$_JAVA_HOME + fi + unset _JAVA_HOME + unset _VERSION + unset _VERSION_REQUIRED + unset DEV_ROOT + + echo "$_ERROR" + return 1 + + fi + unset _VERSION + fi + + unset _VERSION_REQUIRED + export DEV_ROOT + echo DEV_ROOT = $DEV_ROOT + export JAVA_HOME + echo JAVA_HOME = $JAVA_HOME + $JAVA_HOME/bin/java -version + + # + # Save the PATH environment variable + # + _PATH=$PATH + + # + # Set the MAVEN_HOME environment variable if tools/maven exists + # + _MAVEN_HOME=$MAVEN_HOME + if [ -d $DEV_ROOT/tools/maven ]; then + MAVEN_HOME=$DEV_ROOT/tools/maven + PATH=$MAVEN_HOME/bin:$PATH + export MAVEN_HOME + echo MAVEN_HOME = $MAVEN_HOME + fi + + # + # Set the TDE_HOME environment variable + # + _TDE_HOME=$TDE_HOME + TDE_HOME=$DEV_ROOT/tools/tde + PATH=$TDE_HOME/bin:$PATH + export TDE_HOME + echo TDE_HOME = $TDE_HOME + + # + # Add the RQ executables to the PATH environment variable + # + PATH=$PATH:$DEV_ROOT/tools/wls/infra + + # + # Set the CLASSPATH environment variable + # + _CLASSPATH=$CLASSPATH + CLASSPATH="" + export CLASSPATH + echo CLASSPATH = $CLASSPATH + + export PATH + echo PATH = $PATH + echo Build environment set. + } + +# main +if [ "$1" = "-reset" ]; then + reset +elif [ -z $DEV_ROOT ]; then + setup +else + echo Build environment already set. +fi diff --git a/bin/cfglinux.sh b/bin/cfglinux.sh new file mode 100644 index 0000000000000..3ec444a030b37 --- /dev/null +++ b/bin/cfglinux.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# +# Copyright (c) 2000, 2020, Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# + +# +# This script sets all environment variables necessary to build Coherence. +# +# Command line: +# . ./cfglinux.sh [-reset] +# +# see cfgcommon.sh + +# +# Global Variables +# + +# The platform specific command used to locate the correct version of java +# Note: this command is evaluated when required +_JAVA_HOME_CMD="get_java_home" + +function get_java_home + { + if [ -d /usr/java/jdk$_VERSION_REQUIRED ]; then + echo "/usr/java/jdk$_VERSION_REQUIRED" + elif [ -d /usr/local/packages/jdk8 ]; then + echo "/usr/local/packages/jdk8" + fi + } + +# determine the scripts directory, assuming all scripts are in the same directory +SCRIPT_PATH="${BASH_SOURCE[0]}" +while [ -h "${SCRIPT_PATH}" ]; do + LS=`ls -ld "${SCRIPT_PATH}"` + LINK=`expr "${LS}" : '.*-> \(.*\)$'` + if [ `expr "${LINK}" : '/.*'` > /dev/null ]; then + SCRIPT_PATH="${LINK}" + else + SCRIPT_PATH="`dirname "${SCRIPT_PATH}"`/${LINK}" + fi +done + +cd `dirname $SCRIPT_PATH` +SCRIPTS_DIR=`pwd` +cd - &>/dev/null + +source $SCRIPTS_DIR/cfgcommon.sh + +unset SCRIPT_PATH +unset SCRIPTS_DIR diff --git a/bin/cfglocal.sh b/bin/cfglocal.sh new file mode 100644 index 0000000000000..5079c5795c86e --- /dev/null +++ b/bin/cfglocal.sh @@ -0,0 +1,38 @@ +# determine the scripts directory, assuming all scripts are in the same directory + +# +# Copyright (c) 2000, 2020, Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# + +SCRIPT_PATH="${BASH_SOURCE[0]}" +while [ -h "${SCRIPT_PATH}" ]; do + LS=`ls -ld "${SCRIPT_PATH}"` + LINK=`expr "${LS}" : '.*-> \(.*\)$'` + if [ `expr "${LINK}" : '/.*'` > /dev/null ]; then + SCRIPT_PATH="${LINK}" + else + SCRIPT_PATH="`dirname "${SCRIPT_PATH}"`/${LINK}" + fi +done + +cd `dirname $SCRIPT_PATH` +SCRIPTS_DIR=`pwd` +cd - &>/dev/null + +case $(uname) in + Darwin*) + . $SCRIPTS_DIR/cfgosx.sh + ;; + SunOS*) + . $SCRIPTS_DIR/cfgsolaris.sh + ;; + *) + . $SCRIPTS_DIR/cfglinux.sh + ;; +esac + +unset SCRIPT_PATH +unset SCRIPTS_DIR diff --git a/bin/cfgosx.sh b/bin/cfgosx.sh new file mode 100644 index 0000000000000..422a33c937932 --- /dev/null +++ b/bin/cfgosx.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# +# Copyright (c) 2000, 2020, Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# + +# +# This script sets all environment variables necessary to build Coherence. +# +# Command line: +# . ./cfgosx.sh [-reset] +# +# see cfgcommon.sh + +# +# Global Variables +# + +# The platform specific command used to locate the correct version of java +# Note: this command is evaluated when required +_JAVA_HOME_CMD="/usr/libexec/java_home -v \$_VERSION_REQUIRED 2>&1" + +# determine the scripts directory, assuming all scripts are in the same directory +SCRIPT_PATH="${BASH_SOURCE[0]}" +while [ -h "${SCRIPT_PATH}" ]; do + LS=`ls -ld "${SCRIPT_PATH}"` + LINK=`expr "${LS}" : '.*-> \(.*\)$'` + if [ `expr "${LINK}" : '/.*'` > /dev/null ]; then + SCRIPT_PATH="${LINK}" + else + SCRIPT_PATH="`dirname "${SCRIPT_PATH}"`/${LINK}" + fi +done + +cd `dirname $SCRIPT_PATH` +SCRIPTS_DIR=`pwd` +cd - &>/dev/null + +source $SCRIPTS_DIR/cfgcommon.sh + +unset SCRIPT_PATH +unset SCRIPTS_DIR diff --git a/bin/cfgsolaris.sh b/bin/cfgsolaris.sh new file mode 100644 index 0000000000000..4a33c52b6a943 --- /dev/null +++ b/bin/cfgsolaris.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# +# Copyright (c) 2000, 2020, Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# + +# +# This script sets all environment variables necessary to build Coherence. +# +# Command line: +# . ./cfgsolaris.sh [-reset] +# +# see cfgcommon.sh + +# +# Global Variables +# + +# The platform specific command used to locate the correct version of java +# Note: this command is evaluated when required +_JAVA_HOME_CMD="ls -td /usr/java/jdk$_VERSION_REQUIRED* | head -1" + +# determine the scripts directory, assuming all scripts are in the same directory +SCRIPT_PATH="${BASH_SOURCE[0]}" +while [ -h "${SCRIPT_PATH}" ]; do + LS=`ls -ld "${SCRIPT_PATH}"` + LINK=`expr "${LS}" : '.*-> \(.*\)$'` + if [ `expr "${LINK}" : '/.*'` > /dev/null ]; then + SCRIPT_PATH="${LINK}" + else + SCRIPT_PATH="`dirname "${SCRIPT_PATH}"`/${LINK}" + fi +done + +cd `dirname $SCRIPT_PATH` +SCRIPTS_DIR=`pwd` +cd - &>/dev/null + +source $SCRIPTS_DIR/cfgcommon.sh + +unset SCRIPT_PATH +unset SCRIPTS_DIR \ No newline at end of file diff --git a/bin/cfgwindows.cmd b/bin/cfgwindows.cmd new file mode 100755 index 0000000000000..29d31a5f8963a --- /dev/null +++ b/bin/cfgwindows.cmd @@ -0,0 +1,105 @@ +@echo off +rem Copyright (c) 2000, 2020, Oracle and/or its affiliates. + +rem Licensed under the Universal Permissive License v 1.0 as shown at +rem http://oss.oracle.com/licenses/upl. + +rem +rem This script sets all environment variables necessary to build Coherence. +rem +rem Command line: +rem cfgwindows [-reset] +rem +rem This script is responsible for the following environment variables: +rem +rem DEV_ROOT e.g. c:\dev +rem MAVEN_HOME e.g. c:\dev\tools\maven +rem TDE_HOME e.g. c:\dev\tools\tde +rem CLASSPATH e.g. +rem PATH e.g. %ANT_HOME%\bin;%MAVEN_HOME%\bin;%PATH% +rem +rem _TDE_HOME saved TDE_HOME +rem _CLASSPATH saved CLASSPATH +rem _PATH saved PATH +rem + +rem +rem Reset the build environment if the "-reset" flag was passed +rem +if "%1"=="-reset" ( + if "%DEV_ROOT%"=="" ( + echo Build environment already reset. + goto exit + ) + + set MAVEN_HOME=%_MAVEN_HOME% + set TDE_HOME=%_TDE_HOME% + set CLASSPATH=%_CLASSPATH% + set PATH=%_PATH% + + set DEV_ROOT= + set _MAVEN_HOME= + set _TDE_HOME= + set _CLASSPATH= + set _PATH= + + echo Build environment reset. + goto exit +) + +rem +rem Ensure that the JAVA_HOME envirionment variable has been set +rem +if defined JAVA_HOME ( + :: Strip quotes from JAVA_HOME environment variable if present + set JAVA_HOME=%JAVA_HOME:"=% +) else ( + echo Please set JAVA_HOME appropriately. + goto exit +) +echo JAVA_HOME = %JAVA_HOME% +"%JAVA_HOME%\bin\java" -version + +rem +rem Determine the root of the dev tree +rem +if not "%DEV_ROOT%"=="" ( + echo Build environment already set. + goto exit +) +for %%i in ("%~dp0..") do @set DEV_ROOT=%%~fni +echo DEV_ROOT = %DEV_ROOT% + +rem +rem Set the MAVEN_HOME environment variable if %DEV_ROOT%\tools\maven exists +rem +set _MAVEN_HOME=%MAVEN_HOME% +if exist %DEV_ROOT%\tools\maven ( + set MAVEN_HOME=%DEV_ROOT%\tools\maven + echo MAVEN_HOME = %MAVEN_HOME% +) + +rem +rem Set the TDE_HOME environment variable +rem +set _TDE_HOME=%TDE_HOME% +set TDE_HOME=%DEV_ROOT%\tools\tde +echo TDE_HOME = %TDE_HOME% + +rem +rem Set the CLASSPATH environment variable +rem +set _CLASSPATH=%CLASSPATH% +set CLASSPATH= +echo CLASSPATH = %CLASSPATH% + +rem +rem Set the PATH environment variable +rem +set _PATH=%PATH% +set PATH=%TDE_HOME%\bin;%PATH% +echo PATH = %PATH% + +echo Build environment set. + +:exit diff --git a/ext/license/coherence-client.xml b/ext/license/coherence-client.xml new file mode 100644 index 0000000000000..d5ccd9de1820f --- /dev/null +++ b/ext/license/coherence-client.xml @@ -0,0 +1,16 @@ + + + + + Oracle Coherence: Data Client + n/a + production + 0x00A800CC0000011295888F24BC6C730B + 302C02147737DBFAC8F3931DC9A50EFB78A9DF824414054B0214277CACA4CA2B3D01292FBEB080A18DDCC79DBF0F + + diff --git a/ext/license/coherence-community.xml b/ext/license/coherence-community.xml new file mode 100644 index 0000000000000..0efbe4cd9f1ad --- /dev/null +++ b/ext/license/coherence-community.xml @@ -0,0 +1,30 @@ + + + + + Oracle Coherence: Community Edition + n/a + production + 0xFD3D5B880000016EF9B508011D8D78FA + 302D02140690239D179A12FFE2DCFE6F1D9B469D670DA7D60215008610169730C7C702175DF79885737D8B954593F6 + + + Oracle Coherence: Data Client + n/a + production + 0x00A800CC0000011295888F24BC6C730B + 302C0214178CD5EB5734B2625A2E7111E25241714E9DEE2A0214503B0E28C377E9EC54FF60CCEFF556917F6BF961 + + + Oracle Coherence: Real-Time Client + n/a + production + 0x10A800CC0000011290D2E11674855B90 + 302E02150086864EA114581420CC15328ECFC813C1969062030215008A5C5E4FC9A3EE1E97734B9B499EE58CEEA5EE45 + + diff --git a/ext/license/coherence-enterprise.xml b/ext/license/coherence-enterprise.xml new file mode 100644 index 0000000000000..eb5abb69db141 --- /dev/null +++ b/ext/license/coherence-enterprise.xml @@ -0,0 +1,23 @@ + + + + + Oracle Coherence: Enterprise Edition + n/a + production + 0x50A800CC0000011290D1AFCA71730128 + 302D0215008C52A3A92ADB0BE627CDC57C691891F74C2A9E7502141B63BAE0145A1D00567ABA18F422FD900D529CEB + + + Oracle Coherence: Data Client + n/a + production + 0x00A800CC0000011295888F24BC6C730B + 302C02141F139BD16E8E934027834486F1A4AB575BC3044C02143E548405DF1D352BBFAB81BEF0565314EEE881EE + + diff --git a/ext/license/coherence-grid.xml b/ext/license/coherence-grid.xml new file mode 100644 index 0000000000000..ec5740a702cb7 --- /dev/null +++ b/ext/license/coherence-grid.xml @@ -0,0 +1,30 @@ + + + + + Oracle Coherence: Grid Edition + n/a + production + 0xF0A800CC0000011290D2E0B874855B8F + 302C0214400FDBBF6C025252C82637DDEA6C30FAE2892E8F0214712211DC5CFB2B3CD2B36831A73DCE2927EBD0F0 + + + Oracle Coherence: Real-Time Client + n/a + production + 0x10A800CC0000011290D2E11674855B90 + 302C021441D29A180EBD0B1C564A70CFDD89E1B1786E1E85021464A2089578C2BDC5E994DABD01467D42EEA8BC87 + + + Oracle Coherence: Data Client + n/a + production + 0x00A800CC0000011295888F24BC6C730B + 302C02147D6367E681700189CAC66F92C6A77EB9E1255D2F021425026409450EEDFFCC926D13FF58546930321113 + + diff --git a/ext/license/coherence-rtc.xml b/ext/license/coherence-rtc.xml new file mode 100644 index 0000000000000..5bb1e2cae8a44 --- /dev/null +++ b/ext/license/coherence-rtc.xml @@ -0,0 +1,23 @@ + + + + + Oracle Coherence: Compute Client + n/a + production + 0x2A95BC15000001142C0D832575DCFF94 + 302D0215009578C7A925164DD27F9096EEA1C93391710EC9B3021460C02CA42CB0B12DC058B1AEC9E4F1D3AE61F5A9 + + + Oracle Coherence: Real-Time Client + n/a + production + 0x10A800CC0000011290D2E11674855B90 + 302D021500853B423AACBCD520764A32E052397E5F897673A1021448631F7ED29F6EB09A4DD9EBC1AF6FFF2D26EDA3 + + diff --git a/ext/license/coherence-standard.xml b/ext/license/coherence-standard.xml new file mode 100644 index 0000000000000..3b69441be9e2c --- /dev/null +++ b/ext/license/coherence-standard.xml @@ -0,0 +1,23 @@ + + + + + Oracle Coherence: Standard Edition + n/a + production + 0xF0A800CC0000011290D1A22ED170B42C + 302C02143994C40B38455EB2CC8CA100298C247A6113CC1E02146D2C4C48266F95BB6F2D5DE36690EE54F21D33AD + + + Oracle Coherence: Data Client + n/a + production + 0x00A800CC0000011295888F24BC6C730B + 302C02145DADFAD73EB8A1042E425D9E349FFBBED973663A02147B34DA5EE1CB52E0262A7D7FFD47AA061AC6D7C7 + + diff --git a/ext/license/keys/tangosol.cer b/ext/license/keys/tangosol.cer new file mode 100644 index 0000000000000..c2c1bd343f811 --- /dev/null +++ b/ext/license/keys/tangosol.cer @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAtsCBEURqEEwCwYHKoZIzjgEAwUAMHMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJNQTET +MBEGA1UEBxMKU29tZXJ2aWxsZTEXMBUGA1UEChMOVGFuZ29zb2wsIEluYy4xEDAOBgNVBAsTB1Vu +a25vd24xFzAVBgNVBAMTDlRhbmdvc29sLCBJbmMuMCAXDTA2MDkyMDIwNDQ0OVoYDzIyODAwNzA1 +MjA0NDQ5WjBzMQswCQYDVQQGEwJVUzELMAkGA1UECBMCTUExEzARBgNVBAcTClNvbWVydmlsbGUx +FzAVBgNVBAoTDlRhbmdvc29sLCBJbmMuMRAwDgYDVQQLEwdVbmtub3duMRcwFQYDVQQDEw5UYW5n +b3NvbCwgSW5jLjCCAbgwggEsBgcqhkjOOAQBMIIBHwKBgQD9f1OBHXUSKVLfSpwu7OTn9hG3Ujzv +RADDHj+AtlEmaUVdQCJR+1k9jVj6v8X1ujD2y5tVbNeBO4AdNG/yZmC3a5lQpaSfn+gEexAiwk+7 +qdf+t8Yb+DtX58aophUPBPuD9tPFHsMCNVQTWhaRMvZ1864rYdcq7/IiAxmd0UgBxwIVAJdgUI8V +IwvMspK5gqLrhAvwWBz1AoGBAPfhoIXWmz3ey7yrXDa4V7l5lK+7+jrqgvlXTAs9B4JnUVlXjrrU +WU/mcQcQgYC0SRZxI+hMKBYTt88JMozIpuE8FnqLVHyNKOCjrh4rs6Z1kW6jfwv6ITVi8ftiegEk +O8yk8b6oUZCJqIPf4VrlnwaSi2ZegHtVJWQBTDv+z0kqA4GFAAKBgQDclms4MK7gp9Ybx7lznEKG +Z0VrExC3xFlSJ8luNVTIebcqQ1Tq9TQG2BSi4SbiIMzFcpPy6vvA79dpK1mMx8HUPc9fksbnwKr5 +9MJt53K0UvlsZ+w/x//j1mX/DlZvKxBQRInOp1lrCquYJWETYBPknuKecjiqJuMVU6vmHsqJ+TAL +BgcqhkjOOAQDBQADLwAwLAIUIhsTUe1Ht9Vd4dC4n3TE9oMJeeUCFCElz4I8DH2Qc+lDH8hPfNgD +97aA +-----END CERTIFICATE----- diff --git a/ext/license/keys/tangosol.dat b/ext/license/keys/tangosol.dat new file mode 100644 index 0000000000000..8d9e80a030cbb Binary files /dev/null and b/ext/license/keys/tangosol.dat differ diff --git a/ext/license/processor-dictionary.xml b/ext/license/processor-dictionary.xml new file mode 100644 index 0000000000000..953c9effd9baf --- /dev/null +++ b/ext/license/processor-dictionary.xml @@ -0,0 +1,96 @@ + + + + + GenuineIntel + 6 + 15 + 2 + 302C021442AA21149EAC05420EB6207BB3A2B2C5D2C66FB6021436B592259BC8F273D890DE11E77C0BD7861FA39F + + + Windows + GenuineIntel + 6 + 14 + 2 + 302D0214094697CE235B3AC1E0EC257F484057D18E41593E0215008EBBB81A6E51C77D4C4085A9A01928D4846A93A7 + + + Windows + GenuineIntel + 15 + [2-9]|\d{2,3} + 2 + 302C0214716DB06182D3BA592319206A9776E34F8E8516660214644ABB986E0481A94649F76DE427365083372868 + + + GenuineIntel + .* ht .* + 2 + 302C021437D70C22A871E6342093E1D82CF789D50705DEF30214104F1000B9E650F71BD53D54905FCB886B5B3F9D + + + GenuineIntel + 6 + 15 + 7 + 4 + 302C021445F10E2B2B882A08103B3607B0A62BAD633BF78202145907187D23BE4EFD558B1E22A68524FD80DF8DFC + + + GenuineIntel + 15 + 4|6 + 8 + 4 + 302C02141F7BBB359B4F56F059461D378F163BEED9C7FC83021410CEA440AB90B0CC5B656877C56A79B1E9394AC0 + + + AuthenticAMD + 15 + 5 + 4 + 302C02141241A92B49548FC59153B55032A4AE32C3F53CA8021474970FE7DC902C12332810DE1A592EC4AF0192F2 + + + AuthenticAMD + 15 + 33 + 2 + 302C021468EC7F0E2174FAD4644B9F990DD955A3DD233B2A02144C2EBAE8D2D8A1D1060B62120B700D44E655DA47 + + + AuthenticAMD + 15 + 35 + 2 + 302C02142E6E6E8ECA844F5146C3C2CEC97719D49755D0840214747440501C29680FD6FEB48CB95EB8A150E81446 + + + AuthenticAMD + 15 + 43 + 2 + 302C02147A9C2A87F18949ADB9FB173735D319B0F171A47802145D3A4F47B47AD69CAC9D12D3767162F6B468D404 + + + AuthenticAMD + 15 + 35 + 2 + 302C021455274389617438BBD0F3AA769E8568BC7F860549021410F831823D690419E7DF759194DDDF8DC5109DD6 + + + ppc64 + POWER5 (gr) + 2\.\d* + 4 + 302C021419B61653EA35519BE2DFB757CC77AE58C9C8E61A02141320B3B7FB5B1481A5943CC3313F150D3D3B87C4 + + diff --git a/prj/build-import.xml b/prj/build-import.xml new file mode 100644 index 0000000000000..7137efdbc11d1 --- /dev/null +++ b/prj/build-import.xml @@ -0,0 +1,770 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ****************************************************************************** + | + | ${banner.message} + | + ****************************************************************************** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/prj/coherence-cdi-server/pom.xml b/prj/coherence-cdi-server/pom.xml new file mode 100644 index 0000000000000..21b00b9eb4ea8 --- /dev/null +++ b/prj/coherence-cdi-server/pom.xml @@ -0,0 +1,80 @@ + + + + 4.0.0 + + + com.oracle.coherence.ce + main + 14.1.2-0-0-SNAPSHOT + ../pom.xml + + + coherence-cdi-server + Coherence CDI Server + + CDI-enabled Coherence Server + + + + + ${project.groupId} + coherence-cdi + ${project.version} + + + + javax.enterprise + cdi-api + + + javax.annotation + javax.annotation-api + + + org.jboss.weld.se + weld-se-core + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven.deploy.plugin.version} + + false + + + + + + org.codehaus.mojo + flatten-maven-plugin + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + diff --git a/prj/coherence-cdi-server/src/main/java/com/oracle/coherence/cdi/server/CdiCacheServer.java b/prj/coherence-cdi-server/src/main/java/com/oracle/coherence/cdi/server/CdiCacheServer.java new file mode 100644 index 0000000000000..ca448cd875e15 --- /dev/null +++ b/prj/coherence-cdi-server/src/main/java/com/oracle/coherence/cdi/server/CdiCacheServer.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.server; + +import com.oracle.coherence.common.base.Blocking; + +import com.tangosol.net.DefaultCacheServer; + +import javax.enterprise.inject.se.SeContainerInitializer; + +/** + * This class bootstraps the CDI container, which will in turn start Coherence + * server via CDI extension. + *

+ * This class should only be used when Coherence CDI is used in a standalone + * mode. When used with Helidon 2.0, Helidon's {@code io.helidon.microprofile.cdi.Main} + * should be used instead and will ensure that both Helidon and Coherence + * services are started correctly. + * + * @author Aleks Seovic 2020.03.24 + */ +public class CdiCacheServer + { + /** + * Main method that creates and starts {@code CdiCacheServer}. + * + * @param args program arguments + */ + public static void main(String[] args) + { + new CdiCacheServer().start(); + } + + /** + * Starts CDI, which will indirectly start {@link DefaultCacheServer}. + */ + public void start() + { + // configure logging + LogConfig.configureLogging(); + + // bootstrap Weld + SeContainerInitializer initializer = SeContainerInitializer.newInstance(); + initializer.initialize(); + + monitorServices(5000L); + } + + /** + * Blocks in a loop for {@code cWaitMillis} until the Coherence monitor is + * stopped. + * + * @param cWaitMillis the number of milliseconds to block during each + * iteration + */ + private void monitorServices(long cWaitMillis) + { + DefaultCacheServer dcs = DefaultCacheServer.getInstance(); + do + { + try + { + Blocking.sleep(cWaitMillis); + } + catch (InterruptedException ignore) + { + } + } + while (!dcs.isMonitorStopped()); + + dcs.shutdownServer(); + } + } diff --git a/prj/coherence-cdi-server/src/main/java/com/oracle/coherence/cdi/server/LogConfig.java b/prj/coherence-cdi-server/src/main/java/com/oracle/coherence/cdi/server/LogConfig.java new file mode 100644 index 0000000000000..4697f0e77bfa1 --- /dev/null +++ b/prj/coherence-cdi-server/src/main/java/com/oracle/coherence/cdi/server/LogConfig.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.server; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import java.util.logging.LogManager; +import java.util.logging.Logger; + +/** + * Logging configuration utility. + *

+ * Eliminates the need to explicitly configure Java Util logging as long as a + * file {@code logging.properties} is on the classpath or in the current + * directory, or you configure logging explicitly using System properties. + *

+ * Both {@value #SYS_PROP_LOGGING_CLASS} and {@value #SYS_PROP_LOGGING_FILE} are + * honored. If you wish to configure the logging system differently, just do not + * include the file and/or system properties. + */ +final class LogConfig + { + /** + * Construct {@code LogConfig} instance. + */ + private LogConfig() + { + } + + /** + * Configure logging. + */ + static void configureLogging() + { + try + { + doConfigureLogging(); + } + catch (IOException e) + { + System.err.println("Failed to configure logging"); + e.printStackTrace(); + } + } + + /** + * Internal implementation of logging subsystem configuration. + * + * @throws IOException if an error occurs + */ + private static void doConfigureLogging() throws IOException + { + String sConfigClass = System.getProperty(SYS_PROP_LOGGING_CLASS); + String sConfigPath = System.getProperty(SYS_PROP_LOGGING_FILE); + String sSource; + + if (sConfigClass != null) + { + sSource = "class: " + sConfigClass; + } + else if (sConfigPath != null) + { + Path path = Paths.get(sConfigPath); + sSource = path.toAbsolutePath().toString(); + } + else + { + // we want to configure logging ourselves + sSource = findAndConfigureLogging(); + } + + Logger.getLogger(LogConfig.class.getName()).info("Logging configured using " + sSource); + } + + /** + * Find the default configuration file on file system or in the class path + * and use it to configure logging subsystem. + * + * @return the name of the configuration source used + * + * @throws IOException if an error occurs + */ + private static String findAndConfigureLogging() throws IOException + { + String sSource = "defaults"; + + // Let's try to find a logging.properties + // first as a file in the current working directory + InputStream logConfigStream = null; + + Path path = Paths.get("").resolve(LOGGING_FILE); + + if (Files.exists(path)) + { + logConfigStream = new BufferedInputStream(Files.newInputStream(path)); + sSource = "file: " + path.toAbsolutePath(); + } + else + { + // second look for classpath (only the first one) + InputStream resourceStream = LogConfig.class.getResourceAsStream("/" + LOGGING_FILE); + if (null != resourceStream) + { + logConfigStream = new BufferedInputStream(resourceStream); + sSource = "classpath: /" + LOGGING_FILE; + } + } + if (null != logConfigStream) + { + try + { + LogManager.getLogManager().readConfiguration(logConfigStream); + } + finally + { + logConfigStream.close(); + } + } + + return sSource; + } + + // ---- constants ------------------------------------------------------- + + /** + * The default name of the logging configuration file. + */ + private static final String LOGGING_FILE = "logging.properties"; + + /** + * The name of the system property that can be used to define + * logging configuration class. + */ + private static final String SYS_PROP_LOGGING_CLASS = "java.util.logging.config.class"; + + /** + * The name of the system property that can be used to define + * logging configuration file. + */ + private static final String SYS_PROP_LOGGING_FILE = "java.util.logging.config.file"; + } diff --git a/prj/coherence-cdi-server/src/main/java/com/oracle/coherence/cdi/server/package-info.java b/prj/coherence-cdi-server/src/main/java/com/oracle/coherence/cdi/server/package-info.java new file mode 100644 index 0000000000000..802d0c6f66627 --- /dev/null +++ b/prj/coherence-cdi-server/src/main/java/com/oracle/coherence/cdi/server/package-info.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +/** + * Simple CDI-based Cache Server implementation. + * + * @author Aleks Seovic 2020.03.25 + */ +package com.oracle.coherence.cdi.server; diff --git a/prj/coherence-cdi/pom.xml b/prj/coherence-cdi/pom.xml new file mode 100644 index 0000000000000..937a5ea698a92 --- /dev/null +++ b/prj/coherence-cdi/pom.xml @@ -0,0 +1,116 @@ + + + + 4.0.0 + + + com.oracle.coherence.ce + main + 14.1.2-0-0-SNAPSHOT + ../pom.xml + + + coherence-cdi + Coherence CDI + + Coherence CDI Extension and Producers + + + + + ${coherence.group.id} + coherence + ${project.version} + + + + javax.enterprise + cdi-api + provided + + + javax.annotation + javax.annotation-api + provided + + + + org.jboss.weld.se + weld-se-core + provided + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + test + + + org.hamcrest + hamcrest + test + + + org.jboss.weld + weld-junit5 + test + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + ${maven.deploy.plugin.version} + + false + + + + + + org.codehaus.mojo + flatten-maven-plugin + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + + + + + diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/AlwaysFilter.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/AlwaysFilter.java new file mode 100644 index 0000000000000..bc534a72c875a --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/AlwaysFilter.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +/** + * A {@link com.oracle.coherence.cdi.FilterBinding} annotation representing an + * {@link com.tangosol.util.filter.AlwaysFilter}. + * + * @author Jonathan Knight 2019.10.24 + */ +@FilterBinding +@Inherited +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface AlwaysFilter + { + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.AlwaysFilter} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements AlwaysFilter + { + /** + * Construct {@code Literal} instance. + */ + private Literal() + { + } + + // ---- constants --------------------------------------------------- + + /** + * A {@link com.oracle.coherence.cdi.AlwaysFilter.Literal} instance. + */ + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/AnnotationInstance.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/AnnotationInstance.java new file mode 100644 index 0000000000000..bea25d5f1838f --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/AnnotationInstance.java @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import java.util.Arrays; +import java.util.Objects; + +import javax.enterprise.util.Nonbinding; + +/** + * A representation of an {@link Annotation} that can be used for equality tests + * where methods in the {@link Annotation} annotated with {@link + * javax.enterprise.util.Nonbinding} are ignored. + * + * @author Jonathan Knight 2019.10.24 + */ +class AnnotationInstance + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code AnnotationInstance} instance. + * + * @param clzAnnotationType the annotation class + * @param aMembers an array of annotation members (properties) + * @param aoValues an array of annotation member/property values + */ + private AnnotationInstance(Class clzAnnotationType, Method[] aMembers, Object[] aoValues) + { + f_clzAnnotationType = clzAnnotationType; + f_aMembers = aMembers; + f_aoValues = aoValues; + if (aMembers.length == 0) + { + m_nCachedHashCode = 0; + } + else + { + m_nCachedHashCode = null; + } + } + + /** + * Create an {@link AnnotationInstance} from an {@link + * java.lang.annotation.Annotation}. + * + * @param instance the {@link java.lang.annotation.Annotation} to create the + * {@link AnnotationInstance} from + * + * @return an {@link AnnotationInstance} from an {@link + * java.lang.annotation.Annotation} + */ + static AnnotationInstance create(Annotation instance) + { + if (instance == null) + { + return new AnnotationInstance(Annotation.class, new Method[0], new Object[0]); + } + + Method[] members = instance.annotationType().getDeclaredMethods(); + Object[] values = new Object[members.length]; + + for (int i = 0; i < members.length; i++) + { + if (members[i].isAnnotationPresent(Nonbinding.class)) + { + values[i] = null; + } + else + { + values[i] = invoke(members[i], instance); + } + } + return new AnnotationInstance(instance.annotationType(), members, values); + } + + // ---- Object methods -------------------------------------------------- + + @Override + public String toString() + { + StringBuilder string = new StringBuilder(); + string.append('@').append(f_clzAnnotationType.getName()).append('('); + for (int i = 0; i < f_aMembers.length; i++) + { + string.append(f_aMembers[i].getName()).append('='); + Object value = f_aoValues[i]; + if (value instanceof boolean[]) + { + appendInBraces(string, Arrays.toString((boolean[]) value)); + } + else if (value instanceof byte[]) + { + appendInBraces(string, Arrays.toString((byte[]) value)); + } + else if (value instanceof short[]) + { + appendInBraces(string, Arrays.toString((short[]) value)); + } + else if (value instanceof int[]) + { + appendInBraces(string, Arrays.toString((int[]) value)); + } + else if (value instanceof long[]) + { + appendInBraces(string, Arrays.toString((long[]) value)); + } + else if (value instanceof float[]) + { + appendInBraces(string, Arrays.toString((float[]) value)); + } + else if (value instanceof double[]) + { + appendInBraces(string, Arrays.toString((double[]) value)); + } + else if (value instanceof char[]) + { + appendInBraces(string, Arrays.toString((char[]) value)); + } + else if (value instanceof String[]) + { + String[] strings = (String[]) value; + String[] quoted = new String[strings.length]; + for (int j = 0; j < strings.length; j++) + { + quoted[j] = "\"" + strings[j] + "\""; + } + appendInBraces(string, Arrays.toString(quoted)); + } + else if (value instanceof Class[]) + { + Class[] classes = (Class[]) value; + String[] names = new String[classes.length]; + for (int j = 0; j < classes.length; j++) + { + names[j] = classes[j].getName() + ".class"; + } + appendInBraces(string, Arrays.toString(names)); + } + else if (value instanceof Object[]) + { + appendInBraces(string, Arrays.toString((Object[]) value)); + } + else if (value instanceof String) + { + string.append('"').append(value).append('"'); + } + else if (value instanceof Class) + { + string.append(((Class) value).getName()).append(".class"); + } + else + { + string.append(value); + } + if (i < f_aMembers.length - 1) + { + string.append(", "); + } + } + return string.append(')').toString(); + } + + @Override + public boolean equals(Object other) + { + if (this == other) + { + return true; + } + if (other == null || getClass() != other.getClass()) + { + return false; + } + + AnnotationInstance that = (AnnotationInstance) other; + if (!Objects.equals(f_clzAnnotationType, that.f_clzAnnotationType)) + { + return false; + } + if (f_aMembers.length != that.f_aMembers.length) + { + return false; + } + for (int i = 0; i < f_aMembers.length; i++) + { + Object thisValue = f_aoValues[i]; + Object thatValue = that.f_aoValues[i]; + + if (thisValue instanceof byte[] && thatValue instanceof byte[]) + { + if (!Arrays.equals((byte[]) thisValue, (byte[]) thatValue)) + { + return false; + } + } + else if (thisValue instanceof short[] && thatValue instanceof short[]) + { + if (!Arrays.equals((short[]) thisValue, (short[]) thatValue)) + { + return false; + } + } + else if (thisValue instanceof int[] && thatValue instanceof int[]) + { + if (!Arrays.equals((int[]) thisValue, (int[]) thatValue)) + { + return false; + } + } + else if (thisValue instanceof long[] && thatValue instanceof long[]) + { + if (!Arrays.equals((long[]) thisValue, (long[]) thatValue)) + { + return false; + } + } + else if (thisValue instanceof float[] && thatValue instanceof float[]) + { + if (!Arrays.equals((float[]) thisValue, (float[]) thatValue)) + { + return false; + } + } + else if (thisValue instanceof double[] && thatValue instanceof double[]) + { + if (!Arrays.equals((double[]) thisValue, (double[]) thatValue)) + { + return false; + } + } + else if (thisValue instanceof char[] && thatValue instanceof char[]) + { + if (!Arrays.equals((char[]) thisValue, (char[]) thatValue)) + { + return false; + } + } + else if (thisValue instanceof boolean[] && thatValue instanceof boolean[]) + { + if (!Arrays.equals((boolean[]) thisValue, (boolean[]) thatValue)) + { + return false; + } + } + else if (thisValue instanceof Object[] && thatValue instanceof Object[]) + { + if (!Arrays.equals((Object[]) thisValue, (Object[]) thatValue)) + { + return false; + } + } + else if (!Objects.equals(thisValue, thatValue)) + { + return false; + } + } + return true; + } + + @Override + public int hashCode() + { + if (m_nCachedHashCode == null) + { + int hashCode = 0; + for (int i = 0; i < f_aMembers.length; i++) + { + int memberNameHashCode = 127 * f_aMembers[i].getName().hashCode(); + Object value = f_aoValues[i]; + int memberValueHashCode; + if (value instanceof boolean[]) + { + memberValueHashCode = Arrays.hashCode((boolean[]) value); + } + else if (value instanceof short[]) + { + memberValueHashCode = Arrays.hashCode((short[]) value); + } + else if (value instanceof int[]) + { + memberValueHashCode = Arrays.hashCode((int[]) value); + } + else if (value instanceof long[]) + { + memberValueHashCode = Arrays.hashCode((long[]) value); + } + else if (value instanceof float[]) + { + memberValueHashCode = Arrays.hashCode((float[]) value); + } + else if (value instanceof double[]) + { + memberValueHashCode = Arrays.hashCode((double[]) value); + } + else if (value instanceof byte[]) + { + memberValueHashCode = Arrays.hashCode((byte[]) value); + } + else if (value instanceof char[]) + { + memberValueHashCode = Arrays.hashCode((char[]) value); + } + else if (value instanceof Object[]) + { + memberValueHashCode = Arrays.hashCode((Object[]) value); + } + else + { + memberValueHashCode = Objects.hashCode(value); + } + hashCode += memberNameHashCode ^ memberValueHashCode; + } + m_nCachedHashCode = hashCode; + } + return m_nCachedHashCode; + } + + // ---- helpers --------------------------------------------------------- + + /** + * Invoke specified method on the specified target instance. + * + * @param method method to invoke + * @param oTarget target instance to invoke method on + * + * @return the result of method invocation + */ + private static Object invoke(Method method, Object oTarget) + { + try + { + if (!method.isAccessible()) + { + method.setAccessible(true); + } + return method.invoke(oTarget); + } + catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) + { + throw new RuntimeException("Error checking value of member method " + method.getName() + " on " + + method.getDeclaringClass(), e); + } + } + + /** + * Wrap specified string with curly braces and append it to the specified + * string builder. + * + * @param sb a {@code StringBuilder} to append wrapped string to + * @param s a string to wrap + */ + private void appendInBraces(StringBuilder sb, String s) + { + sb.append('{').append(s, 1, s.length() - 1).append('}'); + } + + // ---- data members ---------------------------------------------------- + + /** + * The annotation class. + */ + private final Class f_clzAnnotationType; + + /** + * An array of annotation members (properties). + */ + private final Method[] f_aMembers; + + /** + * An array of annotation member/property values. Each element in this array + * corresponds to an element in the members array. + */ + private final Object[] f_aoValues; + + /** + * A cached hash code of this instance. + */ + private Integer m_nCachedHashCode; + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/BeanBuilder.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/BeanBuilder.java new file mode 100644 index 0000000000000..d38edf519bd20 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/BeanBuilder.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.ParameterResolver; + +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.literal.NamedLiteral; +import javax.enterprise.inject.spi.CDI; + +/** + * Element processor for {@code } XML element. + * + * @author Aleks Seovic 2019.10.02 + */ +public class BeanBuilder + implements ParameterizedBuilder, + ParameterizedBuilder.ReflectionSupport + { + private Expression m_exprBeanName; + + /** + * Construct {@code BeanBuilder} instance. + * + * @param exprBeanName the name of the CDI bean + */ + public BeanBuilder(String exprBeanName) + { + m_exprBeanName = new LiteralExpression<>(exprBeanName); + } + + // ---- ParameterizedBuilder interface ---------------------------------- + + @Override + public Object realize(ParameterResolver resolver, ClassLoader loader, ParameterList parameterList) + { + String beanName = m_exprBeanName.evaluate(resolver); + Instance instance = CDI.current().select(NamedLiteral.of(beanName)); + if (instance.isResolvable()) + { + return instance.get(); + } + else + { + throw new ConfigurationException(String.format("CDI bean [%s] cannot be resolved", beanName), + "Please ensure that a bean with that name exists and can be discovered by CDI."); + } + } + + // ---- ParameterizedBuilder.ReflectionSupport interface ---------------- + + @Override + public boolean realizes(Class aClass, ParameterResolver resolver, ClassLoader loader) + { + String beanName = m_exprBeanName.evaluate(resolver); + Instance instance = CDI.current().select(aClass, NamedLiteral.of(beanName)); + + return instance.isResolvable(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/BeanProcessor.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/BeanProcessor.java new file mode 100644 index 0000000000000..7826682c30a51 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/BeanProcessor.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +/** + * Element processor for {@code } XML element. + * + * @author Aleks Seovic 2019.10.02 + */ +@XmlSimpleName("bean") +public class BeanProcessor + implements ElementProcessor + { + @Override + public BeanBuilder process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + return context.inject(new BeanBuilder(element.getString()), element); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/Cache.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/Cache.java new file mode 100644 index 0000000000000..b9840cdea4011 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/Cache.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used when injecting Coherence resource to indicate a + * specific cache name. + * + * @author Jonathan Knight 2019.10.20 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Cache + { + /** + * The name used to identify a specific cache. + * + * @return the name used to identify a specific cache + */ + @Nonbinding String value(); + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link Cache} annotation. + */ + class Literal + extends AnnotationLiteral + implements Cache + { + /** + * Construct {@code Cache.Literal} instance. + * + * @param sName the cache name + */ + private Literal(String sName) + { + m_sName = sName; + } + + /** + * Create a {@link Cache.Literal}. + * + * @param sName the cache name + * + * @return a {@link Cache.Literal} with the specified value + */ + public static Literal of(String sName) + { + return new Literal(sName); + } + + /** + * The name used to identify a specific cache. + * + * @return the name used to identify a specific cache + */ + public String value() + { + return m_sName; + } + + // ---- data members ------------------------------------------------ + + /** + * The cache name. + */ + private final String m_sName; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CacheFactory.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CacheFactory.java new file mode 100644 index 0000000000000..61fb24091e9b7 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CacheFactory.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; +import javax.inject.Qualifier; + +/** + * A qualifier annotation used when injecting Coherence resource to indicate + * that those resource should be obtained from a specific {@link + * com.tangosol.net.ConfigurableCacheFactory}. + * + * @author Jonathan Knight 2019.10.20 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface CacheFactory + { + /** + * The URI used to identify a specific {@link + * com.tangosol.net.ConfigurableCacheFactory}. + * + * @return the URI used to identify a specific + * {@link com.tangosol.net.ConfigurableCacheFactory} + */ + @Nonbinding String value(); + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link CacheFactory} annotation. + */ + class Literal + extends AnnotationLiteral + implements CacheFactory + { + /** + * Construct {@code Literal} instacne. + * + * @param sUri the URI used to identify a specific + * {@link com.tangosol.net.ConfigurableCacheFactory} + */ + private Literal(String sUri) + { + m_sValue = sUri; + } + + /** + * Create a {@link CacheFactory.Literal}. + * + * @param sUri the URI used to identify a specific + * {@link com.tangosol.net.ConfigurableCacheFactory} + * + * @return a {@link CacheFactory.Literal} with the specified URI + */ + public static Literal of(String sUri) + { + return new Literal(sUri); + } + + /** + * Obtain the name value. + * + * @return the name value + */ + public String value() + { + return m_sValue; + } + + // ---- data members ------------------------------------------------ + + /** + * The name value for this literal. + */ + private final String m_sValue; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CacheFactoryUriResolver.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CacheFactoryUriResolver.java new file mode 100644 index 0000000000000..bbdc9a542871d --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CacheFactoryUriResolver.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import javax.annotation.Priority; + +import javax.enterprise.context.ApplicationScoped; + +import javax.enterprise.inject.Alternative; + +import com.tangosol.net.options.WithConfiguration; + +/** + * A class that can resolve the URI of a Coherence cache configuration file from + * a {@code String} value. + * + * @author Jonathan Knight 2019.10.21 + */ +public interface CacheFactoryUriResolver + { + /** + * The priority for the default {@link CacheFactoryUriResolver} bean. + */ + public int PRIORITY = 0; + + /** + * Resolve a String value into a Coherence cache configuration URI. + * + * @param sValue the value to resolve + * + * @return the cache configuration URI + */ + public String resolve(String sValue); + + // ---- inner class: Default -------------------------------------------- + + /** + * The default implementation of a {@link CacheFactoryUriResolver}. + *

+ * This implementation returns the passed in {@code value} unchanged. + */ + @ApplicationScoped + @Alternative + @Priority(PRIORITY) + class Default + implements CacheFactoryUriResolver + { + @Override + public String resolve(String sValue) + { + if (sValue == null || sValue.trim().isEmpty()) + { + return WithConfiguration.autoDetect().getLocation(); + } + return sValue; + } + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CacheView.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CacheView.java new file mode 100644 index 0000000000000..05122324f6e2b --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CacheView.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used when injecting a cache view. + * + * @author Jonathan Knight 2019.10.24 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface CacheView + { + /** + * A flag that is {@code true} to cache both the keys and values of the + * materialized view locally, or {@code false} to only cache the keys (the + * default value is {@code true}). + * + * @return {@code true} to indicate that values should be cached or + * {@code false} to indicate that only keys should be cached + */ + @Nonbinding boolean cacheValues() default true; + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link CacheView} annotation. + */ + class Literal + extends AnnotationLiteral + implements CacheView + { + /** + * Construct {@code Literal} instance. + * + * @param fCacheValues a flag that is {@code true} to cache both the keys + * and values of the materialized view locally, or + * {@code false} to only cache the keys + */ + private Literal(boolean fCacheValues) + { + this.f_fCacheValues = fCacheValues; + } + + /** + * Create a {@link CacheView.Literal}. + * + * @param fCacheValues a flag that is {@code true} to cache both the keys + * and values of the materialized view locally, or + * {@code false} to only cache the keys + * + * @return a {@link CacheView.Literal} with the specified value + */ + public static Literal of(boolean fCacheValues) + { + return new Literal(fCacheValues); + } + + /** + * Obtain the flag that is {@code true} to cache both the keys and + * values of the materialized view locally, or {@code false} to only + * cache the keys (the default value is {@code true}). + * + * @return {@code true} to indicate that values should be cache or + * {@code false} to indicate that only keys should be cached. + */ + @Override + public boolean cacheValues() + { + return f_fCacheValues; + } + + // ---- constants ------------------------------------------------------- + + /** + * A singleton instance of {@link com.oracle.coherence.cdi.CacheView.Literal} + * with the cache values flag set to true. + */ + public static final Literal INSTANCE = Literal.of(true); + + // ---- data members ---------------------------------------------------- + + /** + * A flag that is {@code true} to cache both the keys and values of the + * materialized view locally, or {@code false} to only cache the keys. + */ + private final boolean f_fCacheValues; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CdiInterceptorMetadataResolver.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CdiInterceptorMetadataResolver.java new file mode 100644 index 0000000000000..96a0f0291e776 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CdiInterceptorMetadataResolver.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.net.events.EventInterceptor; +import com.tangosol.net.events.InterceptorMetadataResolver; + +import org.jboss.weld.proxy.WeldClientProxy; + +/** + * An implementation of {@link com.tangosol.net.events.InterceptorMetadataResolver} + * that knows how to extract interceptor metadata from a Weld proxy. + * + * @author Aleks Seovic 2020.04.03 + */ +@SuppressWarnings("unchecked") +public class CdiInterceptorMetadataResolver + implements InterceptorMetadataResolver + { + @Override + public Class getInterceptorClass(EventInterceptor eventInterceptor) + { + Class clazz = eventInterceptor.getClass(); + if (eventInterceptor instanceof WeldClientProxy) + { + clazz = (Class) clazz.getSuperclass(); + } + return clazz; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CdiNamespaceHandler.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CdiNamespaceHandler.java new file mode 100644 index 0000000000000..9c71b16a1c0fe --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CdiNamespaceHandler.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.config.xml.AbstractNamespaceHandler; + +/** + * Custom namespace handler for {@code cdi} namespace. + *

+ * This namespace handler supports only one XML element: + *

    + *
  • {@code <cdi:bean>beanName</cdi:bean>}, where {@code beanName} + * is the unique name of a CDI bean defined by the {@code @Named} annotation. + * This element can only be used as a child of the standard {@code + * <instance>} element.
  • + *
+ * + * @author Aleks Seovic 2019.10.02 + */ +public class CdiNamespaceHandler + extends AbstractNamespaceHandler + { + /** + * Construct {@code CdiNamespaceHandler} instance. + */ + public CdiNamespaceHandler() + { + registerProcessor(BeanProcessor.class); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ChainedExtractor.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ChainedExtractor.java new file mode 100644 index 0000000000000..56ad53279544d --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ChainedExtractor.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; + +/** + * A {@link ValueExtractorBinding} annotation representing a + * {@link com.tangosol.util.extractor.ChainedExtractor}. + * + * @author Jonathan Knight 2019.10.25 + */ +@Inherited +@ValueExtractorBinding +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(ChainedExtractor.Extractors.class) +public @interface ChainedExtractor + { + /** + * Returns the a method or property name to use when creating a {@link + * com.tangosol.util.extractor.ChainedExtractor}. + *

+ * If the value does not end in {@code "()"} the value is assumed to be a + * property name. If the value is prefixed with one of the accessor prefixes + * {@code "get"} or {@code "is"} and ends in {@code "()"} this extractor is + * a property extractor. Otherwise, if the value just ends in {@code "()"} + * this value is considered a method name. + * + * @return the value used for the where clause when creating a {@link + * com.tangosol.util.extractor.ChainedExtractor} + */ + @Nonbinding String[] value(); + + // ---- inner interface: Extractors ------------------------------------- + + /** + * A holder for the repeatable {@link ChainedExtractor} annotation. + */ + @Inherited + @ValueExtractorBinding + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface Extractors + { + /** + * An array of {@link ChainedExtractor}s. + * + * @return an array of {@link ChainedExtractor}s + */ + @Nonbinding ChainedExtractor[] value(); + + // ---- inner class: Literal ---------------------------------------- + + /** + * An annotation literal for the {@link Extractors} annotation. + */ + class Literal + extends AnnotationLiteral + implements Extractors + { + private Literal(ChainedExtractor... aExtractors) + { + f_aExtractors = aExtractors; + } + + /** + * Create an {@link Extractors.Literal}. + * + * @param extractors the extractors + * + * @return an {@link Extractors.Literal} containing the specified + * extractors + */ + public static Literal of(ChainedExtractor... extractors) + { + return new Literal(extractors); + } + + /** + * The extractor annotations contained in this annotation. + * + * @return the extractor annotations contained in this annotation + */ + public ChainedExtractor[] value() + { + return f_aExtractors; + } + + // ---- data members -------------------------------------------- + + /** + * The extractors value for this literal. + */ + private final ChainedExtractor[] f_aExtractors; + } + } + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link ChainedExtractor} annotation. + */ + class Literal + extends AnnotationLiteral + implements ChainedExtractor + { + /** + * Construct {@code Literal} instance. + * + * @param asFields the value used to create the extractor + */ + private Literal(String[] asFields) + { + this.f_asFields = asFields; + } + + /** + * Create a {@link ChainedExtractor.Literal}. + * + * @param asFields the value used to create the extractor + * + * @return a {@link ChainedExtractor.Literal} with the specified value + */ + public static Literal of(String... asFields) + { + return new Literal(asFields); + } + + /** + * The value used for the where clause when creating a {@link + * com.tangosol.util.extractor.ChainedExtractor}. + * + * @return the value used for the where clause when creating a {@link + * com.tangosol.util.extractor.ChainedExtractor} + */ + public String[] value() + { + return f_asFields; + } + + // ---- data members ------------------------------------------------ + + /** + * The extractor value for this literal. + */ + private final String[] f_asFields; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ClusterProducer.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ClusterProducer.java new file mode 100644 index 0000000000000..0df3e7ccc4f60 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ClusterProducer.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.coherence.component.util.SafeCluster; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.Cluster; +import com.tangosol.net.OperationalContext; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +/** + * A CDI producer that produces instances of the singleton Coherence {@link + * Cluster}. + * + * @author Jonathan Knight 2019.11.20 + */ +@ApplicationScoped +public class ClusterProducer + { + /** + * Produces the singleton Coherence {@link Cluster} instance. + * + * @return the singleton Coherence {@link Cluster} instance + */ + @Produces + public Cluster getCluster() + { + return CacheFactory.ensureCluster(); + } + + /** + * Produces the singleton Coherence {@link OperationalContext} instance. + * + * @return the singleton Coherence {@link OperationalContext} instance + */ + @Produces + public OperationalContext getOperationalContext() + { + return (SafeCluster) CacheFactory.getCluster(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CoherenceExtension.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CoherenceExtension.java new file mode 100644 index 0000000000000..1a876f917336a --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/CoherenceExtension.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.oracle.coherence.cdi.events.CacheServerStarted; +import com.tangosol.net.DefaultCacheServer; + +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +import javax.enterprise.event.Event; +import javax.enterprise.event.Observes; + +import javax.enterprise.inject.Default; + +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.ProcessAnnotatedType; +import javax.enterprise.inject.spi.WithAnnotations; + +import org.jboss.weld.environment.se.events.ContainerInitialized; + +/** + * A Coherence CDI {@link Extension}. + * + * @author Jonathan Knight 2019.10.24 + * @author Aleks Seovic 2020.03.25 + */ +public class CoherenceExtension + implements Extension + { + /** + * Process {@link FilterFactory} beans annotated with {@link FilterBinding}. + * + * @param event the event to process + * @param the declared type of the injection point + */ + private > void processFilterInjectionPoint( + @Observes @WithAnnotations(FilterBinding.class) ProcessAnnotatedType event) + { + AnnotatedType type = event.getAnnotatedType(); + type.getAnnotations() + .stream() + .filter(a -> a.annotationType().isAnnotationPresent(FilterBinding.class)) + .map(AnnotationInstance::create) + .forEach(a -> m_mapFilterSupplier.put(a, type.getJavaClass())); + } + + /** + * Process {@link ValueExtractorFactory} beans annotated with + * {@link ValueExtractorBinding}. + * + * @param event the event to process + * @param the declared type of the injection point + */ + private > void processValueExtractorInjectionPoint( + @Observes @WithAnnotations(ValueExtractorBinding.class) ProcessAnnotatedType event) + { + AnnotatedType type = event.getAnnotatedType(); + type.getAnnotations() + .stream() + .filter(a -> a.annotationType().isAnnotationPresent(ValueExtractorBinding.class)) + .map(AnnotationInstance::create) + .forEach(a -> m_mapExtractorSupplier.put(a, type.getJavaClass())); + } + + /** + * Register dynamic beans for this extension. + * + * @param event the event to use for dynamic bean registration + */ + private void addBeans(@Observes final AfterBeanDiscovery event) + { + // Register the filter producer bean that knows about all of the FilterFactory beans + FilterProducer.FilterFactoryResolver filterResolver = new FilterProducer.FilterFactoryResolver(m_mapFilterSupplier); + event.addBean() + .produceWith(i -> filterResolver) + .types(FilterProducer.FilterFactoryResolver.class) + .qualifiers(Default.Literal.INSTANCE) + .scope(ApplicationScoped.class) + .beanClass(FilterProducer.FilterFactoryResolver.class); + + // Register the value extractor producer bean that knows about all of the ValueExtractorFactory beans + ValueExtractorProducer.ValueExtractorFactoryResolver extractorResolver + = new ValueExtractorProducer.ValueExtractorFactoryResolver(m_mapExtractorSupplier); + event.addBean() + .produceWith(i -> extractorResolver) + .types(ValueExtractorProducer.ValueExtractorFactoryResolver.class) + .qualifiers(Default.Literal.INSTANCE) + .scope(ApplicationScoped.class) + .beanClass(ValueExtractorProducer.ValueExtractorFactoryResolver.class); + } + + /** + * Start {@link com.tangosol.net.DefaultCacheServer} as a daemon and wait + * for all services to start. + * + * @param event the event fired once the CDI container is initialized + */ + private void startCacheServer(@Observes ContainerInitialized event, Event servicesStartedEvent) + { + DefaultCacheServer.startServerDaemon().waitForServiceStart(); + servicesStartedEvent.fire(new CacheServerStarted()); + } + + // ---- data members ---------------------------------------------------- + + /** + * A map of {@link FilterBinding} annotation to {@link FilterFactory} bean + * class. + */ + private Map>> m_mapFilterSupplier = new HashMap<>(); + + /** + * A map of {@link ValueExtractorBinding} annotation to {@link + * ValueExtractorFactory} bean class. + */ + private Map>> m_mapExtractorSupplier = new HashMap<>(); + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ConfigurableCacheFactoryProducer.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ConfigurableCacheFactoryProducer.java new file mode 100644 index 0000000000000..8fae04ff80fa7 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ConfigurableCacheFactoryProducer.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.net.CacheFactoryBuilder; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.options.WithConfiguration; + +import com.tangosol.util.Base; + +import java.lang.reflect.Member; + +import javax.enterprise.context.ApplicationScoped; + +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; + +import javax.inject.Inject; + +/** + * A CDI producer for {@link com.tangosol.net.ConfigurableCacheFactory} and + * {@link com.tangosol.net.CacheFactoryBuilder} instances. + * + * @author Jonathan Knight 2019.10.19 + */ +@ApplicationScoped +class ConfigurableCacheFactoryProducer + { + // ---- constructors ---------------------------------------------------- + + /** + * Default constructor used by CDI. + * + * @param uriResolver the URI resolver to use + */ + @Inject + ConfigurableCacheFactoryProducer(CacheFactoryUriResolver uriResolver) + { + this(uriResolver, com.tangosol.net.CacheFactory.getCacheFactoryBuilder()); + } + + /** + * Create a ConfigurableCacheFactoryProducer. + * + * @param uriResolver the URI resolver to use + * @param cacheFactoryBuilder the {@link CacheFactoryBuilder} to use to + * obtain {@link ConfigurableCacheFactory} instances + */ + ConfigurableCacheFactoryProducer(CacheFactoryUriResolver uriResolver, CacheFactoryBuilder cacheFactoryBuilder) + { + f_cacheFactoryBuilder = cacheFactoryBuilder; + m_uriResolver = uriResolver; + } + + // ---- producer methods ------------------------------------------------ + + /** + * Produces the default {@link ConfigurableCacheFactory}. + * + * @return the default {@link ConfigurableCacheFactory} + */ + @Produces + public ConfigurableCacheFactory getDefaultConfigurableCacheFactory() + { + return f_cacheFactoryBuilder.getConfigurableCacheFactory(Base.getContextClassLoader()); + } + + /** + * Produces a named {@link ConfigurableCacheFactory}. + *

+ * If the value of the name qualifier is blank or empty String the default + * {@link com.tangosol.net.ConfigurableCacheFactory} will be returned. + *

+ * The name parameter will be resolved to a cache configuration URI using + * the {@link CacheFactoryUriResolver} bean. + * + * @param injectionPoint the {@link InjectionPoint} that the cache factory + * it to be injected into + * + * @return the named {@link ConfigurableCacheFactory} + */ + @Produces + @CacheFactory("") + public ConfigurableCacheFactory getNamedConfigurableCacheFactory(InjectionPoint injectionPoint) + { + String sName = injectionPoint.getQualifiers() + .stream() + .filter(q -> q.annotationType().isAssignableFrom(CacheFactory.class)) + .map(q -> ((CacheFactory) q).value()) + .findFirst() + .orElse(null); + + String sUri = m_uriResolver.resolve(sName); + if (sUri == null || sUri.trim().isEmpty()) + { + sUri = WithConfiguration.autoDetect().getLocation(); + } + + Member member = injectionPoint.getMember(); + ClassLoader loader = member == null + ? Base.getContextClassLoader() + : member.getDeclaringClass().getClassLoader(); + + return f_cacheFactoryBuilder.getConfigurableCacheFactory(sUri, loader); + } + + /** + * Dispose of a {@link ConfigurableCacheFactory} bean. + *

+ * Disposing of a {@link ConfigurableCacheFactory} will call {@link + * ConfigurableCacheFactory#dispose()}. + * + * @param ccf the {@link ConfigurableCacheFactory} to dispose + */ + void disposeConfigurableCacheFactory(@Disposes ConfigurableCacheFactory ccf) + { + ccf.dispose(); + } + + /** + * Dispose of a {@link ConfigurableCacheFactory} bean. + *

+ * Disposing of a {@link ConfigurableCacheFactory} will call {@link + * ConfigurableCacheFactory#dispose()}. + * + * @param ccf the {@link ConfigurableCacheFactory} to dispose + */ + void disposeQualifiedConfigurableCacheFactory(@Disposes @CacheFactory("") ConfigurableCacheFactory ccf) + { + ccf.dispose(); + } + + /** + * Produces the default {@link ConfigurableCacheFactory}. + * + * @return the default {@link ConfigurableCacheFactory} + */ + @Produces + public CacheFactoryBuilder getCacheFactoryBuilder() + { + return f_cacheFactoryBuilder; + } + + // ---- data members ---------------------------------------------------- + + /** + * Cache factory builder. + */ + private final CacheFactoryBuilder f_cacheFactoryBuilder; + + /** + * Cache factory URI resolver. + */ + private CacheFactoryUriResolver m_uriResolver; + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/EventDispatcher.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/EventDispatcher.java new file mode 100644 index 0000000000000..ce083d56032d1 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/EventDispatcher.java @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.oracle.coherence.cdi.events.Activated; +import com.oracle.coherence.cdi.events.Activating; +import com.oracle.coherence.cdi.events.Arrived; +import com.oracle.coherence.cdi.events.Assigned; +import com.oracle.coherence.cdi.events.CacheName; +import com.oracle.coherence.cdi.events.Committed; +import com.oracle.coherence.cdi.events.Committing; +import com.oracle.coherence.cdi.events.Created; +import com.oracle.coherence.cdi.events.Departed; +import com.oracle.coherence.cdi.events.Departing; +import com.oracle.coherence.cdi.events.Destroyed; +import com.oracle.coherence.cdi.events.Disposing; +import com.oracle.coherence.cdi.events.Executed; +import com.oracle.coherence.cdi.events.Executing; +import com.oracle.coherence.cdi.events.Inserted; +import com.oracle.coherence.cdi.events.Inserting; +import com.oracle.coherence.cdi.events.Lost; +import com.oracle.coherence.cdi.events.Processor; +import com.oracle.coherence.cdi.events.Recovered; +import com.oracle.coherence.cdi.events.Removed; +import com.oracle.coherence.cdi.events.Removing; +import com.oracle.coherence.cdi.events.Rollback; +import com.oracle.coherence.cdi.events.ServiceName; +import com.oracle.coherence.cdi.events.Truncated; +import com.oracle.coherence.cdi.events.Updated; +import com.oracle.coherence.cdi.events.Updating; + +import com.tangosol.net.events.EventDispatcherAwareInterceptor; +import com.tangosol.net.events.EventInterceptor; + +import com.tangosol.net.events.application.LifecycleEvent; + +import com.tangosol.net.events.partition.TransactionEvent; +import com.tangosol.net.events.partition.TransferEvent; +import com.tangosol.net.events.partition.UnsolicitedCommitEvent; + +import com.tangosol.net.events.partition.cache.CacheLifecycleEvent; +import com.tangosol.net.events.partition.cache.EntryEvent; +import com.tangosol.net.events.partition.cache.EntryProcessorEvent; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Event; +import javax.enterprise.inject.Instance; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * EventDispatcher is an {@link com.tangosol.net.events.EventInterceptor} + * implementation that listens to all Coherence server-side events and publishes + * them to registered CDI observers. + * + * @author Aleks Seovic 2020.04.03 + */ +@ApplicationScoped +@Named("com.oracle.coherence.cdi.EventDispatcher") +public class EventDispatcher + implements EventDispatcherAwareInterceptor> + { + // ---- EventDispatcherAwareInterceptor interface ----------------------- + + @Override + public void onEvent(com.tangosol.net.events.Event event) + { + // no-op, as EventDispatcher never actually registers itself as an + // interceptor. Its only purpose is to discover and register all + // available EventHandlers with standard Coherence event dispatchers. + } + + @Override + public void introduceEventDispatcher(String id, com.tangosol.net.events.EventDispatcher eventDispatcher) + { + m_eventHandlers.stream() + .map(EventHandler::getEventInterceptor) + .forEach(eventDispatcher::addEventInterceptor); + } + + // ---- inner interface: EventHandler ----------------------------------- + + /** + * Marker interface that must be implemented by event interceptors + * responsible for adapting Coherence events to CDI events. + *

+ * This interface is used for runtime discovery of available event + * interceptors, so each concrete class that implements this interface + * must also implement {@link EventInterceptor} interface. + */ + interface EventHandler + { + @SuppressWarnings("rawtypes") + default EventInterceptor getEventInterceptor() + { + return (EventInterceptor) this; + } + } + + // ---- inner class: LifecycleEventHandler ------------------------------ + + /** + * Handler for {@link LifecycleEvent}s. + */ + @ApplicationScoped + static class LifecycleEventHandler + implements EventHandler, EventInterceptor + { + @Override + public void onEvent(LifecycleEvent event) + { + Event e = m_lifecycleEvent; + + switch (event.getType()) + { + case ACTIVATING: + e = e.select(Activating.Literal.INSTANCE); + break; + case ACTIVATED: + e = e.select(Activated.Literal.INSTANCE); + break; + case DISPOSING: + e = e.select(Disposing.Literal.INSTANCE); + break; + } + + e.fireAsync(event); + e.fire(event); + } + + // ---- data members ---------------------------------------------------- + + @Inject + private Event m_lifecycleEvent; + } + + // ---- inner class: CacheLifecycleEventHandler ------------------------- + + /** + * Handler for {@link CacheLifecycleEvent}s. + */ + @ApplicationScoped + static class CacheLifecycleEventHandler + implements EventHandler, EventInterceptor + { + @Override + public void onEvent(CacheLifecycleEvent event) + { + CacheName cache = CacheName.Literal.of(event.getCacheName()); + ServiceName service = ServiceName.Literal.of(event.getService().getInfo().getServiceName()); + + Event e = m_cacheLifecycleEvent.select(cache, service); + + switch (event.getType()) + { + case CREATED: + e = e.select(Created.Literal.INSTANCE); + break; + case DESTROYED: + e = e.select(Destroyed.Literal.INSTANCE); + break; + case TRUNCATED: + e = e.select(Truncated.Literal.INSTANCE); + break; + } + + e.fireAsync(event); + e.fire(event); + } + + // ---- data members ---------------------------------------------------- + + @Inject + private Event m_cacheLifecycleEvent; + } + + // ---- inner class: EntryEventHandler ---------------------------------- + + /** + * Handler for {@link EntryEvent}s. + */ + @ApplicationScoped + static class EntryEventHandler + implements EventHandler, EventInterceptor> + { + @Override + public void onEvent(EntryEvent event) + { + CacheName cache = CacheName.Literal.of(event.getCacheName()); + ServiceName service = ServiceName.Literal.of(event.getService().getInfo().getServiceName()); + + Event> e = m_entryEvent.select(cache, service); + + switch (event.getType()) + { + case INSERTING: + e = e.select(Inserting.Literal.INSTANCE); + break; + case INSERTED: + e = e.select(Inserted.Literal.INSTANCE); + break; + case UPDATING: + e = e.select(Updating.Literal.INSTANCE); + break; + case UPDATED: + e = e.select(Updated.Literal.INSTANCE); + break; + case REMOVING: + e = e.select(Removing.Literal.INSTANCE); + break; + case REMOVED: + e = e.select(Removed.Literal.INSTANCE); + break; + } + + e.fireAsync(event); + e.fire(event); + } + + // ---- data members ---------------------------------------------------- + + @Inject + private Event> m_entryEvent; + } + + // ---- inner class: EntryProcessorEventHandler ------------------------- + + /** + * Handler for {@link EntryProcessorEvent}s. + */ + @ApplicationScoped + static class EntryProcessorEventHandler + implements EventHandler, EventInterceptor + { + @Override + public void onEvent(EntryProcessorEvent event) + { + CacheName cache = CacheName.Literal.of(event.getCacheName()); + ServiceName service = ServiceName.Literal.of(event.getService().getInfo().getServiceName()); + Processor processor = Processor.Literal.of(event.getProcessor().getClass()); + + Event e = m_entryProcessorEvent.select(cache, service, processor); + + switch (event.getType()) + { + case EXECUTING: + e = e.select(Executing.Literal.INSTANCE); + break; + case EXECUTED: + e = e.select(Executed.Literal.INSTANCE); + break; + } + + e.fireAsync(event); + e.fire(event); + } + + // ---- data members ---------------------------------------------------- + + @Inject + private Event m_entryProcessorEvent; + } + + // ---- inner class: TransactionEventHandler ---------------------------- + + /** + * Handler for {@link TransactionEvent}s. + */ + @ApplicationScoped + static class TransactionEventHandler + implements EventHandler, EventInterceptor + { + @Override + public void onEvent(TransactionEvent event) + { + ServiceName service = ServiceName.Literal.of(event.getService().getInfo().getServiceName()); + + Event e = m_transactionEvent.select(service); + + switch (event.getType()) + { + case COMMITTING: + e = e.select(Committing.Literal.INSTANCE); + break; + case COMMITTED: + e = e.select(Committed.Literal.INSTANCE); + break; + } + + e.fireAsync(event); + e.fire(event); + } + + // ---- data members ---------------------------------------------------- + + @Inject + private Event m_transactionEvent; + } + + // ---- inner class: TransferEventHandler ------------------------------- + + /** + * Handler for {@link com.tangosol.net.events.partition.TransferEvent}s. + */ + @ApplicationScoped + static class TransferEventHandler + implements EventHandler, EventInterceptor + { + @Override + public void onEvent(TransferEvent event) + { + ServiceName service = ServiceName.Literal.of(event.getService().getInfo().getServiceName()); + + Event e = m_transferEvent.select(service); + + switch (event.getType()) + { + case ASSIGNED: + e = e.select(Assigned.Literal.INSTANCE); + break; + case ARRIVED: + e = e.select(Arrived.Literal.INSTANCE); + break; + case DEPARTING: + e = e.select(Departing.Literal.INSTANCE); + break; + case DEPARTED: + e = e.select(Departed.Literal.INSTANCE); + break; + case LOST: + e = e.select(Lost.Literal.INSTANCE); + break; + case RECOVERED: + e = e.select(Recovered.Literal.INSTANCE); + break; + case ROLLBACK: + e = e.select(Rollback.Literal.INSTANCE); + break; + } + + e.fireAsync(event); + e.fire(event); + } + + // ---- data members ---------------------------------------------------- + + @Inject + private Event m_transferEvent; + } + + // ---- inner class: UnsolicitedCommitEventHandler ---------------------- + + /** + * Handler for {@link com.tangosol.net.events.partition.UnsolicitedCommitEvent}s. + */ + @ApplicationScoped + static class UnsolicitedCommitEventHandler + implements EventHandler, EventInterceptor + { + @Override + public void onEvent(UnsolicitedCommitEvent event) + { + ServiceName service = ServiceName.Literal.of(event.getService().getInfo().getServiceName()); + + Event e = m_unsolicitedCommitEvent.select(service); + + if (event.getType() == UnsolicitedCommitEvent.Type.COMMITTED) + { + e = e.select(Committed.Literal.INSTANCE); + } + + e.fireAsync(event); + e.fire(event); + } + + // ---- data members ---------------------------------------------------- + + @Inject + private Event m_unsolicitedCommitEvent; + } + + // ---- data members ---------------------------------------------------- + + /** + * Discovered {@link EventHandler}s. + */ + @Inject + private Instance m_eventHandlers; + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/FilterBinding.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/FilterBinding.java new file mode 100644 index 0000000000000..b9502568872fd --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/FilterBinding.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Specifies that an annotation type is a {@link com.tangosol.util.Filter} + * binding type. + * + *

+ * @Inherited
+ * @FilterBinding
+ * @Target({TYPE, METHOD, CONSTRUCTOR})
+ * @Retention(RUNTIME)
+ * public @interface CustomerNameFilter {}
+ * 
+ * + *

+ * Filter bindings are intermediate annotations that may be used to associate + * {@link com.tangosol.util.Filter}s with target beans. + *

+ * Filter bindings are used by annotating a {@link FilterFactory} bean with the + * binding type annotations. Wherever the same annotation is used at an + * injection point that requires a {@link com.tangosol.util.Filter} the + * corresponding factory's {@link FilterFactory#create(java.lang.annotation.Annotation)} + * method is called to produce a {@link com.tangosol.util.Filter} instance. + * + * @author Jonathan Knight 2019.10.24 + */ +@Target(ANNOTATION_TYPE) +@Retention(RUNTIME) +@Documented +public @interface FilterBinding + { + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/FilterFactory.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/FilterFactory.java new file mode 100644 index 0000000000000..dc2965b1a0afd --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/FilterFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.util.Filter; + +import java.lang.annotation.Annotation; + +/** + * A factory that produces instances of {@link com.tangosol.util.Filter} for a + * given {@link java.lang.annotation.Annotation}. + *

+ * A {@link FilterFactory} is normally a CDI bean that is also annotated with a + * {@link FilterBinding} annotation. Whenever an injection point annotated with + * the corresponding {@link FilterBinding} annotation is encountered the {@link + * FilterFactory} bean's {@link FilterFactory#create(java.lang.annotation.Annotation)} + * method is called to create an instance of a {@link com.tangosol.util.Filter}. + * + * @param the annotation type that the factory supports + * @param the type of value being filtered + * + * @author Jonathan Knight 2019.10.24 + */ +public interface FilterFactory + { + /** + * Create a {@link Filter} instance. + * + * @param annotation the {@link Annotation} that defines the filter + * + * @return a {@link Filter} instance + */ + public Filter create(A annotation); + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/FilterProducer.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/FilterProducer.java new file mode 100644 index 0000000000000..84a68abecfb2d --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/FilterProducer.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.util.Filter; +import com.tangosol.util.Filters; +import com.tangosol.util.QueryHelper; + +import java.lang.annotation.Annotation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.Annotated; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.DefinitionException; +import javax.enterprise.inject.spi.InjectionPoint; + +import javax.inject.Inject; + +/** + * A CDI bean that produces {@link com.tangosol.util.Filter} instances using + * {@link FilterFactory} beans annotated with {@link FilterBinding} + * annotations. + * + * @author Jonathan Knight 2019.10.24 + */ +@ApplicationScoped +class FilterProducer + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code FilterProducer} instance. + * + * @param beanManager a {@code BeanManager} to use + * @param filterFactoryResolver a {@code FilterFactoryResolver} to use + */ + @Inject + FilterProducer(BeanManager beanManager, FilterFactoryResolver filterFactoryResolver) + { + f_beanManager = beanManager; + f_filterFactoryResolver = filterFactoryResolver; + } + + // ---- producer methods ------------------------------------------------ + + /** + * Produces {@link Filter} based on injection point metadata. + * + * @param injectionPoint an injection point + * @param the type of objects to filter + * + * @return a {@link Filter} instance + */ + @Produces + @SuppressWarnings("unchecked") + com.tangosol.util.Filter getFilter(InjectionPoint injectionPoint) + { + List> list = new ArrayList<>(); + Annotated annotated = injectionPoint.getAnnotated(); + + if (annotated != null) + { + for (Annotation annotation : annotated.getAnnotations()) + { + if (annotation.annotationType().isAnnotationPresent(FilterBinding.class)) + { + Class clazz = f_filterFactoryResolver.resolve(annotation); + if (clazz != null) + { + FilterFactory supplier = f_beanManager.createInstance().select(clazz).get(); + if (supplier != null) + { + list.add(supplier.create(annotation)); + } + } + else + { + throw new DefinitionException("Unsatisfied dependency - no FilterFactory bean found annotated with " + annotation); + } + } + } + } + + Filter[] aFilters = list.toArray(new Filter[0]); + + if (aFilters.length == 0) + { + return Filters.always(); + } + else if (aFilters.length == 1) + { + return aFilters[0]; + } + else + { + return Filters.all(aFilters); + } + } + + // ---- inner class: AlwaysFilterSupplier ------------------------------- + + /** + * A {@link FilterFactory} that produces {@link com.tangosol.util.filter.AlwaysFilter} + * instances. + */ + @AlwaysFilter + @ApplicationScoped + static class AlwaysFilterSupplier + implements FilterFactory + { + @Override + public Filter create(AlwaysFilter annotation) + { + return Filters.always(); + } + } + + // ---- inner class: WhereFilterSupplier -------------------------------- + + /** + * A {@link FilterFactory} that produces {@link com.tangosol.util.Filter} + * instances from a CohQL where clause. + */ + @WhereFilter("") + @ApplicationScoped + static class WhereFilterSupplier + implements FilterFactory + { + @Override + @SuppressWarnings("unchecked") + public Filter create(WhereFilter annotation) + { + String where = annotation.value(); + if (where.trim().isEmpty()) + { + return Filters.always(); + } + return QueryHelper.createFilter(where); + } + } + + // ---- inner class: FilterFactoryResolver ------------------------------ + + /** + * A resolver of {@link FilterFactory} bean classes for a given {@link + * FilterBinding} annotation. + */ + static class FilterFactoryResolver + { + /** + * Construct {@code FilterFactoryResolver} instance. + * + * @param mapFilterFactory the map of filter bindings to filter factories + */ + FilterFactoryResolver(Map>> mapFilterFactory) + { + m_mapFilterFactory = mapFilterFactory; + } + + /** + * Obtain the {@link FilterFactory} class for a given {@link + * FilterBinding} annotation. + * + * @param annotation the filter binding to obtain the {@link FilterFactory} + * class for + * @param the type of the {@link FilterBinding} annotation + * + * @return the {@link FilterFactory} class for a given {@link + * FilterBinding} annotation + */ + @SuppressWarnings("unchecked") + Class> resolve(A annotation) + { + AnnotationInstance instance = AnnotationInstance.create(annotation); + return (Class>) m_mapFilterFactory.get(instance); + } + + // ---- data members ------------------------------------------------ + + /** + * The map of filter bindings to filter factories. + */ + private Map>> m_mapFilterFactory; + } + + // ---- data members ---------------------------------------------------- + + /** + * The current Bean Manager. + */ + private final BeanManager f_beanManager; + + /** + * The resolver that can resolve a {@link FilterFactory} bean from a {@link + * FilterBinding} annotation. + */ + private final FilterFactoryResolver f_filterFactoryResolver; + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/Injectable.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/Injectable.java new file mode 100644 index 0000000000000..77f05391bdda5 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/Injectable.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.io.SerializationSupport; + +import java.io.ObjectStreamException; + +import javax.enterprise.context.spi.CreationalContext; + +import javax.enterprise.inject.spi.AnnotatedType; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.CDI; +import javax.enterprise.inject.spi.InjectionTarget; + +/** + * An interface that should be implemented by classes that require CDI injection + * upon deserialization. + * + * @author Aleks Seovic 2019.10.02 + */ +public interface Injectable + extends SerializationSupport + { + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + default Object readResolve() throws ObjectStreamException + { + BeanManager bm = CDI.current().getBeanManager(); + Class clz = this.getClass(); + AnnotatedType annotatedType = bm.createAnnotatedType(clz); + InjectionTarget injectionTarget = bm.createInjectionTarget(annotatedType); + CreationalContext context = bm.createCreationalContext(null); + + injectionTarget.inject(this, context); + injectionTarget.postConstruct(this); + + return this; + } + + @Override + default Object writeReplace() throws ObjectStreamException + { + return this; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/NamedCacheProducer.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/NamedCacheProducer.java new file mode 100644 index 0000000000000..b3531738fa7b5 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/NamedCacheProducer.java @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.net.AsyncNamedCache; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.NamedCache; +import com.tangosol.net.cache.ContinuousQueryCache; + +import com.tangosol.util.Base; +import com.tangosol.util.Filter; +import com.tangosol.util.ValueExtractor; + +import java.lang.annotation.Annotation; + +import java.lang.reflect.Member; + +import javax.enterprise.context.ApplicationScoped; + +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.Typed; +import javax.enterprise.inject.spi.DefinitionException; +import javax.enterprise.inject.spi.InjectionPoint; + +import javax.inject.Inject; + +/** + * A CDI producer for {@link NamedCache}, {@link AsyncNamedCache} and {@link + * ContinuousQueryCache} instances. + * + * @author Jonathan Knight 2019.10.22 + */ +@ApplicationScoped +@SuppressWarnings("unchecked") +class NamedCacheProducer + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@link NamedCacheProducer} instance. + * + * @param cacheFactoryProducer the producer to use to obtain {@link + * ConfigurableCacheFactory} instances + * @param filterProducer the producer to use to obtain {@link + * com.tangosol.util.Filter} instances + * @param extractorProducer the producer to use to obtain {@link + * com.tangosol.util.ValueExtractor} instances + */ + @Inject + // For some reason IntelliJ marks the parameter as an error but builds and tests pass + NamedCacheProducer(ConfigurableCacheFactoryProducer cacheFactoryProducer, + FilterProducer filterProducer, + ValueExtractorProducer extractorProducer) + { + m_cacheFactoryProducer = cacheFactoryProducer; + m_filterProducer = filterProducer; + m_extractorProducer = extractorProducer; + } + + // ---- producer methods ------------------------------------------------ + + /** + * Produce an {@link AsyncNamedCache} using the injection point member name + * for the cache name and the default {@link ConfigurableCacheFactory} as + * the source. + * + * @param injectionPoint the injection point to inject the {@link AsyncNamedCache} into + * @param the type of the cache keys + * @param the type of the cache values + * + * @return an {@link AsyncNamedCache} using the injection point member name + * for the cache name and the default {@link ConfigurableCacheFactory} + * as the source + */ + @Produces + AsyncNamedCache getNonQualifiedAsyncNamedCache(InjectionPoint injectionPoint) + { + return getAsyncNamedCache(injectionPoint); + } + + /** + * Produce an {@link AsyncNamedCache} using the name from the {@link Cache} + * qualifier as the cache name and the name from the optional {@link + * CacheFactory} qualifier to identify the source {@link + * ConfigurableCacheFactory}. + *

+ * If no {@link CacheFactory} qualifier is present the default {@link + * ConfigurableCacheFactory} will be used as the source. + * + * @param injectionPoint the injection point to inject the {@link AsyncNamedCache} into + * @param the type of the cache keys + * @param the type of the cache values + * + * @return an {@link AsyncNamedCache} using the name from the {@link Cache} + * qualifier as the cache name and the name from the optional + * {@link CacheFactory} qualifier + */ + @Produces + @Cache("") + @CacheFactory("") + AsyncNamedCache getAsyncNamedCache(InjectionPoint injectionPoint) + { + NamedCache cache = getCache(injectionPoint); + return cache.async(); + } + + /** + * Produce an {@link NamedCache} using the injection point member name for + * the cache name and the default {@link ConfigurableCacheFactory} as the + * source. + * + * @param injectionPoint the injection point to inject the {@link NamedCache} into + * @param the type of the cache keys + * @param the type of the cache values + * + * @return a {@link NamedCache} using the injection point member name for + * the cache name and the default {@link ConfigurableCacheFactory} + * as the source + */ + @Produces + NamedCache getNonQualifiedNamedCache(InjectionPoint injectionPoint) + { + return getCache(injectionPoint); + } + + /** + * Produce an {@link NamedCache} using the name from the {@link Cache} + * qualifier as the cache name and the name from the optional {@link + * CacheFactory} qualifier to identify the source {@link + * ConfigurableCacheFactory}. + *

+ * If no {@link CacheFactory} qualifier is present the default {@link + * ConfigurableCacheFactory} will be used as the source. + * + * @param injectionPoint the injection point to inject the {@link NamedCache} into + * @param the type of the cache keys + * @param the type of the cache values + * + * @return an {@link NamedCache} using the name from the {@link Cache} + * qualifier as the cache name and the name from the optional + * {@link CacheFactory} qualifier + */ + @Produces + @Cache("") + @CacheView + @CacheFactory("") + NamedCache getCache(InjectionPoint injectionPoint) + { + return getCacheInternal(injectionPoint, false); + } + + /** + * Produce an {@link com.tangosol.net.cache.ContinuousQueryCache} using the + * injection point member name for the underlying cache name and the default + * {@link com.tangosol.net.ConfigurableCacheFactory} as the source for the + * underlying cache. + *

+ * This producer method is annotated with {@link Typed} to restrict the + * types produced to only the class {@link com.tangosol.net.cache.ContinuousQueryCache}. + * This is so that injection points that are of a super class of CQC, for + * example {@link com.tangosol.net.NamedCache} but are not annotated with + * any other qualifier do not see an ambiguous producer. + * + * @param injectionPoint the injection point to inject the {@link + * com.tangosol.net.cache.ContinuousQueryCache} into + * @param the type of the cache entry keys + * @param the type of the entry values in the back cache that + * is used as the source for this {@link + * com.tangosol.net.cache.ContinuousQueryCache} + * @param the type of the entry values in this {@link + * com.tangosol.net.cache.ContinuousQueryCache}, which + * will be the same as {@code V_BACK}, unless a {@code + * transformer} is specified when creating this {@link + * com.tangosol.net.cache.ContinuousQueryCache} + * + * @return a {@link com.tangosol.net.cache.ContinuousQueryCache} using the + * injection point member name for the underlying cache name and + * the default {@link com.tangosol.net.ConfigurableCacheFactory} as + * the source for the underlying cache + */ + @Produces + @Typed(ContinuousQueryCache.class) + ContinuousQueryCache getNonQualifiedCQC(InjectionPoint injectionPoint) + { + return getCQC(injectionPoint); + } + + /** + * Produce an {@link com.tangosol.net.cache.ContinuousQueryCache} using the + * name from the {@link com.oracle.coherence.cdi.Cache} qualifier as the + * underlying cache name and the name from the optional {@link + * com.oracle.coherence.cdi.CacheFactory} qualifier to identify the source + * {@link com.tangosol.net.ConfigurableCacheFactory}. + *

+ * If no {@link com.oracle.coherence.cdi.CacheFactory} qualifier is present + * the default {@link com.tangosol.net.ConfigurableCacheFactory} will be + * used as the source. + * + * @param injectionPoint the injection point to inject the {@link + * com.tangosol.net.cache.ContinuousQueryCache} into + * @param the type of the cache entry keys + * @param the type of the entry values in the back cache that + * is used as the source for this {@link ContinuousQueryCache} + * @param the type of the entry values in this {@link + * ContinuousQueryCache}, which will be the same as + * {@code V_BACK}, unless a {@code transformer} is + * specified when creating this {@link ContinuousQueryCache} + * + * @return an {@link com.tangosol.net.cache.ContinuousQueryCache} using the + * name from the {@link com.oracle.coherence.cdi.Cache} qualifier + * as the name of the underlying cache and the name from the optional + * {@link com.oracle.coherence.cdi.CacheFactory} qualifier to identify + * the source {@link com.tangosol.net.ConfigurableCacheFactory} + */ + @Produces + @Cache("") + @CacheView + @CacheFactory("") + @Typed(ContinuousQueryCache.class) + ContinuousQueryCache getCQC(InjectionPoint injectionPoint) + { + return (ContinuousQueryCache) getCacheInternal(injectionPoint, true); + } + + /** + * Dispose of a {@link ContinuousQueryCache} bean. + *

+ * Disposing of a {@link ContinuousQueryCache} will call {@link + * ContinuousQueryCache#destroy()} which will destroy the {@link + * ContinuousQueryCache} but not the underlying {@link NamedCache}. + * + * @param cqc the {@link ContinuousQueryCache} to dispose + * @param the type of the cache entry keys + * @param the type of the entry values in the back cache that is + * used as the source for this {@link ContinuousQueryCache} + * @param the type of the entry values in this {@link ContinuousQueryCache}, + * which will be the same as {@code V_BACK}, unless a + * {@code transformer} is specified when creating this + * {@link ContinuousQueryCache} + */ + void destroyCQC(@Disposes ContinuousQueryCache cqc) + { + destroyQualifiedCQC(cqc); + } + + /** + * Dispose of a {@link ContinuousQueryCache} bean. + *

+ * Disposing of a {@link ContinuousQueryCache} will call {@link + * ContinuousQueryCache#destroy()} which will destroy the {@link + * ContinuousQueryCache} but not the underlying {@link NamedCache}. + * + * @param cqc the {@link ContinuousQueryCache} to dispose + * @param the type of the cache entry keys + * @param the type of the entry values in the back cache that is + * used as the source for this {@link ContinuousQueryCache} + * @param the type of the entry values in this {@link + * ContinuousQueryCache}, which will be the same as {@code + * V_BACK}, unless a {@code transformer} is specified when + * creating this {@link ContinuousQueryCache} + */ + void destroyQualifiedCQC( + @Disposes @Cache("") @CacheView @CacheFactory("") ContinuousQueryCache cqc) + { + cqc.destroy(); + } + + // ---- helpers --------------------------------------------------------- + + /** + * Create a {@link NamedCache} instance. + *

+ * If the injection point has a type of {@link ContinuousQueryCache} or is + * qualified with the {@link CacheView} annotation then a {@link + * ContinuousQueryCache} instance will be returned otherwise a {@link + * NamedCache} will be returned. + * + * @param injectionPoint the {@link InjectionPoint} that the {@link NamedCache} will be injected into + * @param fView a flag specifying whether to return a {@link ContinuousQueryCache} + * @param the type of the cache keys + * @param the type of the cache values + * @param the type of the {@link NamedCache} to return + * + * @return a {@link NamedCache} instance to inject into the injection point + */ + @SuppressWarnings("unchecked") + private > C getCacheInternal(InjectionPoint injectionPoint, boolean fView) + { + String sName = null; + boolean fCacheValues = true; + + for (Annotation annotation : injectionPoint.getQualifiers()) + { + if (Cache.class.equals(annotation.annotationType())) + { + sName = ((Cache) annotation).value(); + } + else if (CacheView.class.equals(annotation.annotationType())) + { + fView = true; + fCacheValues = ((CacheView) annotation).cacheValues(); + } + } + + Member member = injectionPoint.getMember(); + if (sName == null || sName.trim().isEmpty()) + { + if (member == null) + { + throw new DefinitionException( + "Cannot determine cache name. No @Cache qualifier and injection point member is null"); + } + sName = member.getName(); + } + + ConfigurableCacheFactory ccf = m_cacheFactoryProducer.getNamedConfigurableCacheFactory(injectionPoint); + ClassLoader loader = member == null + ? Base.getContextClassLoader() + : member.getDeclaringClass().getClassLoader(); + C cache = (C) ccf.ensureCache(sName, loader); + + if (fView) + { + Filter filter = m_filterProducer.getFilter(injectionPoint); + ValueExtractor extractor = m_extractorProducer.getValueExtractor(injectionPoint); + + return (C) new ContinuousQueryCache<>(cache, filter, fCacheValues, null, extractor); + } + else + { + return cache; + } + } + + // ---- data members ---------------------------------------------------- + + /** + * The producer to use to obtain {@link ConfigurableCacheFactory} + * instances. + */ + private ConfigurableCacheFactoryProducer m_cacheFactoryProducer; + + /** + * The producer of {@link com.tangosol.util.Filter} instances. + */ + private FilterProducer m_filterProducer; + + /** + * The producer of {@link com.tangosol.util.ValueExtractor} instances. + */ + private ValueExtractorProducer m_extractorProducer; + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/NamedTopicProducer.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/NamedTopicProducer.java new file mode 100644 index 0000000000000..72f1efcd9bb7a --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/NamedTopicProducer.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.net.ConfigurableCacheFactory; + +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.Subscriber; + +import javax.enterprise.context.ApplicationScoped; + +import javax.enterprise.inject.Disposes; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; + +import javax.inject.Inject; + +/** + * A CDI producer for {@link com.tangosol.net.topic.NamedTopic}, {@link + * com.tangosol.net.topic.Publisher} and {@link com.tangosol.net.topic.Subscriber} + * instances. + * + * @author Jonathan Knight 2019.10.23 + */ +@ApplicationScoped +class NamedTopicProducer + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@link NamedCacheProducer} instance. + * + * @param cacheFactoryProducer the producer to use to obtain {@link + * ConfigurableCacheFactory} instances + */ + @Inject + // For some reason IntelliJ marks the parameter as an error but builds and tests pass + NamedTopicProducer(ConfigurableCacheFactoryProducer cacheFactoryProducer) + { + m_cacheFactoryProducer = cacheFactoryProducer; + } + + // ---- producer methods ------------------------------------------------ + + /** + * Produce a {@link NamedTopic} using the injection point member name for + * the topic name and the default {@link ConfigurableCacheFactory} as the + * source. + * + * @param injectionPoint the injection point to inject the {@link NamedTopic} into + * @param the type of the topic's elements + * + * @return an {@link NamedTopic} using the injection point member name for + * the topic name and the default {@link ConfigurableCacheFactory} + * as the source + */ + @Produces + NamedTopic getNonQualifiedNamedTopic(InjectionPoint injectionPoint) + { + return getNamedTopic(injectionPoint); + } + + /** + * Produce a {@link NamedTopic} using the name from the {@link Topic} + * qualifier as the topic name and the name from the optional {@link + * CacheFactory} qualifier to identify the source {@link + * ConfigurableCacheFactory}. + *

+ * If no {@link CacheFactory} qualifier is present the default {@link + * ConfigurableCacheFactory} will be used as the source. + * + * @param injectionPoint the injection point to inject the {@link NamedTopic} into + * @param the type of the topic's elements + * + * @return a {@link NamedTopic} using the name from the {@link Topic} + * qualifier as the topic name and the name from the optional + * {@link CacheFactory} qualifier + */ + @Produces + @Topic("") + @CacheFactory("") + NamedTopic getNamedTopic(InjectionPoint injectionPoint) + { + String sName = injectionPoint.getQualifiers() + .stream() + .filter(q -> Topic.class.isAssignableFrom(q.getClass())) + .map(q -> ((Topic) q).value()) + .findFirst() + .orElse(null); + + if (sName == null) + { + sName = injectionPoint.getMember().getName(); + } + + ConfigurableCacheFactory ccf = m_cacheFactoryProducer.getNamedConfigurableCacheFactory(injectionPoint); + return ccf.ensureTopic(sName); + } + + /** + * Produce a {@link Publisher} for a {@link NamedTopic} the injection point + * member name for the topic name and the default {@link + * ConfigurableCacheFactory} as the source. + * + * @param injectionPoint the injection point to inject the {@link NamedTopic} into + * @param the type of the topic's elements + * + * @return a {@link Publisher} for a {@link NamedTopic} using the injection + * point member name for the topic name and the default + * {@link ConfigurableCacheFactory} as the source + */ + @Produces + Publisher getNonQualifiedNamedTopicPublisher(InjectionPoint injectionPoint) + { + return getNamedTopicPublisher(injectionPoint); + } + + /** + * Produce a {@link Publisher} for a {@link NamedTopic} using the name from + * the {@link Topic} qualifier as the topic name and the name from the + * optional {@link CacheFactory} qualifier to identify the source {@link + * ConfigurableCacheFactory}. + *

+ * If no {@link CacheFactory} qualifier is present the default {@link + * ConfigurableCacheFactory} will be used as the source. + * + * @param injectionPoint the injection point to inject the {@link NamedTopic} into + * @param the type of the topic's elements + * + * @return a {@link NamedTopic} using the name from the {@link Topic} + * qualifier as the topic name and the name from the optional + * {@link CacheFactory} qualifier + */ + @Produces + @Topic("") + @CacheFactory("") + Publisher getNamedTopicPublisher(InjectionPoint injectionPoint) + { + NamedTopic topic = getNamedTopic(injectionPoint); + return topic.createPublisher(); + } + + /** + * Dispose of a {@link Publisher} bean. + *

+ * Disposing of a {@link Publisher} will call {@link Publisher#close()}. + * + * @param publisher the {@link Publisher} to dispose + * @param the type of the topic elements + */ + void closePublisher(@Disposes Publisher publisher) + { + publisher.close(); + } + + /** + * Dispose of a {@link Publisher} bean. + *

+ * Disposing of a {@link Publisher} will call {@link Publisher#close()}. + * + * @param publisher the {@link Publisher} to dispose + * @param the type of the topic elements + */ + void closeQualifiedPublisher(@Disposes @Topic("") @CacheFactory("") Publisher publisher) + { + publisher.close(); + } + + /** + * Produce a {@link Subscriber} for a {@link NamedTopic} the injection point + * member name for the topic name and the default {@link + * ConfigurableCacheFactory} as the source. + *

+ * The {@link Subscriber} produced will not be part of any subscriber + * group. + * + * @param injectionPoint the injection point to inject the {@link NamedTopic} into + * @param the type of the topic's elements + * + * @return a {@link Subscriber} for a {@link NamedTopic} using the injection + * point member name for the topic name and the default + * {@link ConfigurableCacheFactory} as the source + */ + @Produces + Subscriber getNonQualifiedNamedTopicSubscriber(InjectionPoint injectionPoint) + { + return getNamedTopicSubscriber(injectionPoint); + } + + /** + * Produce a {@link Subscriber} to a {@link NamedTopic}. + *

+ * The value from the {@link Topic} qualifier, if present, will be used as + * the topic name. If no {@link Topic} qualifier is present then the + * injection point member name will be used as the topic name. + *

+ * The name from the optional {@link CacheFactory} qualifier will be used to + * determine the source {@link ConfigurableCacheFactory}. If no {@link + * CacheFactory} qualifier is present the default {@link + * ConfigurableCacheFactory} will be used as the source. + *

+ * The optional {@link SubscriberGroup} qualifier can be used to specify a + * subscriber group that the {@link Subscriber} should belong to. + * + * @param injectionPoint the injection point to inject the {@link NamedTopic} into + * @param the type of the topic's elements + * + * @return a {@link NamedTopic} using the name from the {@link Topic} + * qualifier as the topic name and the name from the optional + * {@link CacheFactory} qualifier + */ + @Produces + @Topic("") + @CacheFactory("") + @SuppressWarnings("unchecked") + Subscriber getNamedTopicSubscriber(InjectionPoint injectionPoint) + { + String sGroup = injectionPoint.getQualifiers() + .stream() + .filter(q -> SubscriberGroup.class.isAssignableFrom(q.getClass())) + .map(q -> ((SubscriberGroup) q).value()) + .findFirst() + .orElse(null); + + NamedTopic topic = getNamedTopic(injectionPoint); + return sGroup == null + ? topic.createSubscriber() + : topic.createSubscriber(Subscriber.Name.of(sGroup)); + } + + /** + * Dispose of a {@link Subscriber} bean. + *

+ * Disposing of a {@link Subscriber} will call {@link Subscriber#close()}. + * + * @param subscriber the {@link Subscriber} to dispose + * @param the type of the topic elements + */ + void closeSubscriber(@Disposes Subscriber subscriber) + { + subscriber.close(); + } + + /** + * Dispose of a {@link Subscriber} bean. + *

+ * Disposing of a {@link Subscriber} will call {@link Subscriber#close()}. + * + * @param subscriber the {@link Subscriber} to dispose + * @param the type of the topic elements + */ + void closeQualifiedSubscriber(@Disposes @Topic("") @CacheFactory("") Subscriber subscriber) + { + subscriber.close(); + } + + // ---- data members ---------------------------------------------------- + + /** + * The producer to use to obtain {@link ConfigurableCacheFactory} + * instances. + */ + private ConfigurableCacheFactoryProducer m_cacheFactoryProducer; + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/PofExtractor.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/PofExtractor.java new file mode 100644 index 0000000000000..20ba60bccd201 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/PofExtractor.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; + +/** + * A {@link ValueExtractorBinding} annotation representing a {@link + * com.tangosol.util.extractor.PofExtractor}. + * + * @author Jonathan Knight 2019.10.25 + */ +@Inherited +@ValueExtractorBinding +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(PofExtractor.Extractors.class) +public @interface PofExtractor + { + /** + * Returns the POF indexes to use to extract the value. + * + * @return the POF indexes to use to extract the value + */ + @Nonbinding int[] value(); + + /** + * Returns the type being extracted. + * + * @return the type being extracted + */ + @Nonbinding Class type() default Object.class; + + // ---- inner interface: Extractors ------------------------------------- + + /** + * A holder for the repeatable {@link PofExtractor} annotation. + */ + @Inherited + @ValueExtractorBinding + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface Extractors + { + @Nonbinding PofExtractor[] value(); + + // ---- inner class: Literal ---------------------------------------- + + /** + * An annotation literal for the {@link Extractors} annotation. + */ + class Literal + extends AnnotationLiteral + implements Extractors + { + /** + * Construct {@code Literal} instance. + * + * @param aExtractors the extractors + */ + private Literal(PofExtractor... aExtractors) + { + m_aExtractors = aExtractors; + } + + /** + * Create an {@link Extractors.Literal}. + * + * @param aExtractors the extractors + * + * @return an {@link Extractors.Literal} containing the specified + * extractors + */ + public static Literal of(PofExtractor... aExtractors) + { + return new Literal(aExtractors); + } + + /** + * The extractor annotations contained in this annotation. + * + * @return the extractor annotations contained in this annotation + */ + public PofExtractor[] value() + { + return m_aExtractors; + } + + // ---- data members -------------------------------------------- + + /** + * The extractors value for this literal. + */ + private final PofExtractor[] m_aExtractors; + } + } + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.PofExtractor} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements PofExtractor + { + /** + * + * @param clzType + * @param anIndices + */ + private Literal(Class clzType, int[] anIndices) + { + f_clzType = clzType; + f_anIndices = anIndices; + } + + /** + * Create a {@link PofExtractor.Literal}. + * + * @param value the POF indexes to use to extract the value + * + * @return a {@link PofExtractor.Literal} with the specified value + */ + public static Literal of(int... value) + { + return new Literal(Object.class, value); + } + + /** + * Create a {@link PofExtractor.Literal}. + * + * @param value the POF indexes to use to extract the value + * + * @return a {@link PofExtractor.Literal} with the specified value + */ + public static Literal of(Class type, int... value) + { + return new Literal(type, value); + } + + /** + * The POF indexes to use to extract a value. + * + * @return the POF indexes to use to extract a value + */ + public int[] value() + { + return f_anIndices; + } + + /** + * The type being extracted. + * + * @return the type being extracted + */ + public Class type() + { + return f_clzType; + } + + // ---- data members ------------------------------------------------ + + /** + * The POF indexes to use to extract the value. + */ + private final int[] f_anIndices; + + /** + * The type being extracted. + */ + private final Class f_clzType; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/PropertyExtractor.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/PropertyExtractor.java new file mode 100644 index 0000000000000..accf0f7cd23ea --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/PropertyExtractor.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; + +/** + * A {@link ValueExtractorBinding} annotation representing a {@link + * com.tangosol.util.extractor.UniversalExtractor}. + * + * @author Jonathan Knight 2019.10.25 + */ +@Inherited +@ValueExtractorBinding +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(PropertyExtractor.Extractors.class) +public @interface PropertyExtractor + { + /** + * Returns the a method or property name to use when creating a {@link + * com.tangosol.util.extractor.UniversalExtractor}. + *

+ * If the value does not end in {@code "()"} the value is assumed to be a + * property name. If the value is prefixed with one of the accessor prefixes + * {@code "get"} or {@code "is"} and ends in {@code "()"} this extractor is + * a property extractor. Otherwise, if the value just ends in {@code "()"} + * this value is considered a method name. + * + * @return the value used for the where clause when creating a {@link + * com.tangosol.util.extractor.UniversalExtractor} + */ + @Nonbinding String value(); + + // ---- inner interface: Extractors ------------------------------------- + + /** + * A holder for the repeatable {@link PropertyExtractor} annotation. + */ + @Inherited + @ValueExtractorBinding + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface Extractors + { + @Nonbinding PropertyExtractor[] value(); + + // ---- inner class: Literal ---------------------------------------- + + /** + * An annotation literal for the {@link Extractors} annotation. + */ + class Literal + extends AnnotationLiteral + implements Extractors + { + /** + * Construct {@code Literal} instance. + * + * @param aExtractors the extractors + */ + private Literal(PropertyExtractor... aExtractors) + { + f_aExtractors = aExtractors; + } + + /** + * Create an {@link Extractors.Literal}. + * + * @param aExtractors the extractors + * + * @return an {@link Extractors.Literal} containing the specified + * extractors + */ + public static Literal of(PropertyExtractor... aExtractors) + { + return new Literal(aExtractors); + } + + /** + * Obtain the extractor annotations contained in this annotation. + * + * @return the extractor annotations contained in this annotation + */ + public PropertyExtractor[] value() + { + return f_aExtractors; + } + + // ---- data members -------------------------------------------- + + /** + * The extractors array for this literal. + */ + private final PropertyExtractor[] f_aExtractors; + } + } + + // ---- inner class: Literal ---------------------------------------- + + /** + * An annotation literal for the {@link PropertyExtractor} annotation. + */ + class Literal + extends AnnotationLiteral + implements PropertyExtractor + { + /** + * Construct {@code Literal} instance. + * + * @param sPropertyName the name of the property to extract + */ + private Literal(String sPropertyName) + { + f_sPropertyName = sPropertyName; + } + + /** + * Create a {@link PropertyExtractor.Literal}. + * + * @param sPropertyName the name of the property to extract + * + * @return a {@link PropertyExtractor.Literal} with the specified value + */ + public static Literal of(String sPropertyName) + { + return new Literal(sPropertyName); + } + + /** + * The name of the property to extract. + * + * @return the name of the property to extract + */ + public String value() + { + return f_sPropertyName; + } + + // ---- data members -------------------------------------------- + + /** + * The name of the property to extract. + */ + private final String f_sPropertyName; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SerializerFormat.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SerializerFormat.java new file mode 100644 index 0000000000000..d297726190dc0 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SerializerFormat.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used when injecting a {@link com.tangosol.io.Serializer} + * to identify the specific {@link com.tangosol.io.Serializer} to inject. + * + * @author Jonathan Knight 2019.11.20 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface SerializerFormat + { + /** + * Obtain the value used to identify a specific serializer. + * + * @return value used to identify a specific serializer + */ + @Nonbinding String value(); + + // ---- inner class: Literal ---------------------------------------- + + /** + * An annotation literal for the {@link SerializerFormat} annotation. + */ + class Literal + extends AnnotationLiteral + implements SerializerFormat + { + /** + * Construct {@code Literal} instance. + * + * @param sName the name of the serializer + */ + private Literal(String sName) + { + this.m_sName = sName; + } + + /** + * Create a {@link SerializerFormat.Literal}. + * + * @param sName the name of the serializer + * + * @return a {@link SerializerFormat.Literal} with the specified value + */ + public static Literal of(String sName) + { + return new Literal(sName); + } + + /** + * The name of the serializer. + * + * @return the name of the serializer + */ + public String value() + { + return m_sName; + } + + // ---- data members ------------------------------------------------ + + /** + * The name of the serializer. + */ + private final String m_sName; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SerializerProducer.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SerializerProducer.java new file mode 100644 index 0000000000000..059dd1c64405b --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SerializerProducer.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.io.Serializer; +import com.tangosol.io.SerializerFactory; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.OperationalContext; + +import com.tangosol.util.Base; +import com.tangosol.util.ExternalizableHelper; + +import java.lang.reflect.Member; + +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.literal.NamedLiteral; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.InjectionPoint; + +import javax.inject.Inject; + +/** + * A CDI producer of {@link Serializer} instances. + * + * @author Jonathan Knight 2019.11.20 + */ +@ApplicationScoped +public class SerializerProducer + { + // ---- constructors ---------------------------------------------------- + + /** + * Create a {@link SerializerProducer}. + * + * @param context the Coherence {@link OperationalContext} + */ + @Inject + SerializerProducer(OperationalContext context, BeanManager beanManager) + { + f_mapSerializerFactory = context.getSerializerMap(); + f_beanManager = beanManager; + } + + /** + * Create an instance of {@link SerializerProducer}. + * + * @return an instance of {@link SerializerProducer} + */ + public static SerializerProducer create() + { + return builder().build(); + } + + /** + * Create an instance of a {@link SerializerProducer.Builder}. + * + * @return an instance of {@link SerializerProducer.Builder} + */ + public static Builder builder() + { + return new Builder(); + } + + // ---- producer methods ------------------------------------------------ + + /** + * Produces an instance of the default {@link Serializer}. + * + * @param ip the {@link InjectionPoint} that the {@link Serializer} will be + * injected into + * + * @return an instance of the default {@link Serializer} + */ + @Produces + public Serializer getDefaultSerializer(InjectionPoint ip) + { + Member member = ip.getMember(); + ClassLoader loader = member == null + ? Base.getContextClassLoader() + : member.getDeclaringClass().getClassLoader(); + return getNamedSerializer("", loader); + } + + /** + * Produces instances of a named {@link Serializer}. + *

+ * Named Serializers are first looked up in the Coherence operational + * configuration and if not found there they will be looked up in as a + * {@link javax.inject.Named} CDI bean of type {@link Serializer}. + * + * @param ip the {@link InjectionPoint} that the {@link Serializer} will be + * injected into + * + * @return an instance of a named {@link Serializer} + */ + @Produces + @SerializerFormat("") + public Serializer getNamedSerializer(InjectionPoint ip) + { + String name = ip.getQualifiers() + .stream() + .filter(q -> q instanceof SerializerFormat) + .map(q -> ((SerializerFormat) q).value()) + .findFirst() + .orElse(""); + + Member member = ip.getMember(); + ClassLoader loader = member == null + ? Base.getContextClassLoader() + : member.getDeclaringClass().getClassLoader(); + + return getNamedSerializer(name, loader); + } + + /** + * Produces instances of a named {@link Serializer}. + *

+ * Named Serializers are first looked up in the Coherence operational + * configuration and if not found there they will be looked up in as a + * {@link javax.inject.Named} CDI bean of type {@link Serializer}. + * + * @param sName the name of the serializer + * @param loader the {@link ClassLoader} to use to create a {@link Serializer} + * + * @return an instance of a named {@link Serializer} + * + * @throws java.lang.NullPointerException if the name parameter is null + * @throws java.lang.IllegalArgumentException if no serializer is discoverable + * with the specified name + */ + public Serializer getNamedSerializer(String sName, ClassLoader loader) + { + if (sName.trim().isEmpty()) + { + return ExternalizableHelper.ensureSerializer(loader); + } + + // first, try the serializers configured in Coherence + SerializerFactory factory = f_mapSerializerFactory.get(sName); + if (factory != null) + { + return factory.createSerializer(loader); + } + + if (f_beanManager == null) + { + throw new IllegalArgumentException("Cannot locate a serializer named '" + + sName + "' in the Coherence operational configuration"); + } + + // not in Coherence configuration so try for named CDI bean serializers + Instance instance = f_beanManager.createInstance().select(Serializer.class, NamedLiteral.of(sName)); + if (instance.isResolvable()) + { + return instance.get(); + } + + throw new IllegalArgumentException("Cannot locate a serializer named '" + + sName + "' neither as a @Named annotated bean nor in the Coherence operational configuration"); + } + + // ----- inner class: Builder ------------------------------------------- + + /** + * A builder that builds {@link com.oracle.coherence.cdi.SerializerProducer} + * instances. + */ + public static class Builder + { + /** + * Private constructor. + */ + private Builder() + { + } + + /** + * Set the Coherence {@link OperationalContext} that the {@link + * SerializerProducer} will use to discover named {@link Serializer}s + * configured in the Coherence operational configuration. + * + * @param context the Coherence {@link OperationalContext} + * + * @return this {@link Builder} + */ + public Builder context(OperationalContext context) + { + m_operationalContext = context; + return this; + } + + /** + * Set the Coherence {@link BeanManager} that the {@link + * SerializerProducer} will use to discover named {@link Serializer}s + * beans. + * + * @param beanManager the {@link BeanManager} to use to discover {@link + * Serializer} beans + * + * @return this {@link Builder} + */ + public Builder beanManager(BeanManager beanManager) + { + m_beanManager = beanManager; + return this; + } + + /** + * Build a new instance of a {@link SerializerProducer}. + * + * @return a new instance of a {@link SerializerProducer} + */ + public SerializerProducer build() + { + return new SerializerProducer(ensureOperationalContext(), m_beanManager); + } + + private OperationalContext ensureOperationalContext() + { + if (m_operationalContext == null) + { + return (OperationalContext) CacheFactory.getCluster(); + } + return m_operationalContext; + } + + // ---- data members ------------------------------------------------ + + /** + * The Coherence {@link com.tangosol.net.OperationalContext}. + */ + private OperationalContext m_operationalContext; + + /** + * The {@link javax.enterprise.inject.spi.BeanManager} that the {@link + * com.oracle.coherence.cdi.SerializerProducer} will use to discover + * named {@link Serializer} beans. + */ + private BeanManager m_beanManager; + } + + // ---- data members ---------------------------------------------------- + + /** + * The map of named {@link com.tangosol.io.SerializerFactory} instances + * configured in the Coherence operational configuration. + */ + private final Map f_mapSerializerFactory; + + /** + * The CDI {@link BeanManager}. + */ + private final BeanManager f_beanManager; + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SessionProducer.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SessionProducer.java new file mode 100644 index 0000000000000..a68a17bbb22d1 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SessionProducer.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.net.CacheFactoryBuilder; +import com.tangosol.net.Session; +import com.tangosol.net.options.WithClassLoader; +import com.tangosol.net.options.WithConfiguration; + +import com.tangosol.util.Base; + +import java.lang.reflect.Member; + +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; + +import javax.inject.Inject; + +/** + * A CDI producer for {@link Session} instances. + * + * @author Jonathan Knight 2019.11.06 + */ +@ApplicationScoped +public class SessionProducer + { + // ---- constructors ---------------------------------------------------- + + /** + * Default constructor used by CDI. + * + * @param uriResolver the URI resolver to use + */ + @Inject + SessionProducer(CacheFactoryUriResolver uriResolver) + { + this(uriResolver, com.tangosol.net.CacheFactory.getCacheFactoryBuilder()); + } + + /** + * Create a ConfigurableCacheFactoryProducer. + * + * @param uriResolver the URI resolver to use + * @param cacheFactoryBuilder the {@link CacheFactoryBuilder} to use to + * obtain {@link Session} instances + */ + SessionProducer(CacheFactoryUriResolver uriResolver, CacheFactoryBuilder cacheFactoryBuilder) + { + f_uriResolver = uriResolver; + f_cacheFactoryBuilder = cacheFactoryBuilder; + } + + // ---- producer methods ------------------------------------------------ + + /** + * Produces the default {@link Session}. + * + * @param injectionPoint the {@link InjectionPoint} that the cache factory + * it to be injected into + * + * @return the default {@link Session} + */ + @Produces + public Session getDefaultSession(InjectionPoint injectionPoint) + { + return getNamedSession(injectionPoint); + } + + /** + * Produces a named {@link Session}. + *

+ * If the value of the name qualifier is blank or empty String the default + * {@link Session} will be returned. + *

+ * The name parameter will be resolved to a cache configuration URI using + * the {@link CacheFactoryUriResolver} bean. + * + * @param injectionPoint the {@link InjectionPoint} that the cache factory + * it to be injected into + * + * @return the named {@link Session} + */ + @Produces + @CacheFactory("") + public Session getNamedSession(InjectionPoint injectionPoint) + { + String sName = injectionPoint.getQualifiers() + .stream() + .filter(q -> q.annotationType().isAssignableFrom(CacheFactory.class)) + .map(q -> ((CacheFactory) q).value()) + .findFirst() + .orElse(null); + + String sUri = f_uriResolver.resolve(sName); + WithConfiguration cfg; + + if (sUri == null || sUri.trim().isEmpty()) + { + cfg = WithConfiguration.autoDetect(); + } + else + { + cfg = WithConfiguration.using(sUri); + } + + Member member = injectionPoint.getMember(); + ClassLoader loader = member == null + ? Base.getContextClassLoader() + : member.getDeclaringClass().getClassLoader(); + Map map = f_mapSession.computeIfAbsent(loader, l -> new HashMap<>()); + return map.computeIfAbsent(sUri, u -> f_cacheFactoryBuilder.createSession(cfg, WithClassLoader.using(loader))); + } + + // ---- data members ---------------------------------------------------- + + /** + * Cache factory builder. + */ + private final CacheFactoryBuilder f_cacheFactoryBuilder; + + /** + * Cache factory URI resolver. + */ + private final CacheFactoryUriResolver f_uriResolver; + + /** + * Session map, keyed by class loader. + */ + private final Map> f_mapSession = new HashMap<>(); + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SubscriberGroup.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SubscriberGroup.java new file mode 100644 index 0000000000000..d024975264697 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/SubscriberGroup.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used when injecting {@link com.tangosol.net.topic.Subscriber} + * to a {@link com.tangosol.net.topic.NamedTopic} to indicate the name of the + * subscriber group that the subscriber should belong to. + * + * @author Jonathan Knight 2019.10.23 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface SubscriberGroup + { + /** + * The name of the subscriber group. + * + * @return the name of the subscriber group + */ + @Nonbinding String value(); + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link SubscriberGroup} annotation. + */ + class Literal + extends AnnotationLiteral + implements SubscriberGroup + { + /** + * Construct {@code Literal} instance + * + * @param sName the name of the subscriber group + */ + private Literal(String sName) + { + f_sName = sName; + } + + /** + * Create a {@link SubscriberGroup.Literal}. + * + * @param sName the name of the subscriber group + * + * @return a {@link SubscriberGroup.Literal} with the specified value + */ + public static Literal of(String sName) + { + return new Literal(sName); + } + + /** + * The name of the subscriber group. + * + * @return the name of the subscriber group + */ + public String value() + { + return f_sName; + } + + // ---- data members ------------------------------------------------ + + /** + * The name of the subscriber group. + */ + private final String f_sName; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/Topic.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/Topic.java new file mode 100644 index 0000000000000..ba2572529540b --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/Topic.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used when injecting Coherence resource to indicate a + * specific topic name. + * + * @author Jonathan Knight 2019.10.23 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Topic + { + /** + * The name of the topic. + * + * @return the name of the topic + */ + @Nonbinding String value(); + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.Topic} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Topic + { + /** + * Construct {@code Literal} instance. + * + * @param sName the name of the topic + */ + private Literal(String sName) + { + this.f_sName = sName; + } + + /** + * Create a {@link com.oracle.coherence.cdi.Topic.Literal}. + * + * @param sName the name of the topic + * + * @return a {@link com.oracle.coherence.cdi.Topic.Literal} with the + * specified value + */ + public static Literal of(String sName) + { + return new Literal(sName); + } + + /** + * The name of the topic. + * + * @return the name of the topic + */ + public String value() + { + return f_sName; + } + + // ---- data members ------------------------------------------------ + + /** + * The name of the topic. + */ + private final String f_sName; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ValueExtractorBinding.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ValueExtractorBinding.java new file mode 100644 index 0000000000000..8354626fb31fb --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ValueExtractorBinding.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Specifies that an annotation type is a {@link com.tangosol.util.ValueExtractor} + * binding type. + * + *

+ * @Inherited
+ * @ValueExtractorBinding
+ * @Target({TYPE, METHOD, CONSTRUCTOR})
+ * @Retention(RUNTIME)
+ * public @interface CustomerNameExtractor {}
+ * 
+ * + *

+ * ValueExtractor bindings are intermediate annotations that may be used to + * associate {@link com.tangosol.util.ValueExtractor}s with target beans. + *

+ * ValueExtractor bindings are used by annotating a {@link + * com.oracle.coherence.cdi.ValueExtractorFactory} bean with the binding type + * annotations. Wherever the same annotation is used at an injection point that + * requires a {@link com.tangosol.util.ValueExtractor} the corresponding + * factory's {@link com.oracle.coherence.cdi.ValueExtractorFactory#create(java.lang.annotation.Annotation)} + * method is called to produce a {@link com.tangosol.util.ValueExtractor} + * instance. + * + * @author Jonathan Knight 2019.10.25 + */ +@Target(ANNOTATION_TYPE) +@Retention(RUNTIME) +@Documented +public @interface ValueExtractorBinding + { + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ValueExtractorFactory.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ValueExtractorFactory.java new file mode 100644 index 0000000000000..e0b45dc895e58 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ValueExtractorFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; + +import com.tangosol.util.ValueExtractor; + +/** + * A factory that produces instances of {@link com.tangosol.util.ValueExtractor} + * for a given {@link java.lang.annotation.Annotation}. + *

+ * A {@link com.oracle.coherence.cdi.ValueExtractorFactory} is normally a CDI + * bean that is also annotated with a {@link com.oracle.coherence.cdi.ValueExtractorBinding} + * annotation. Whenever an injection point annotated with the corresponding + * {@link com.oracle.coherence.cdi.ValueExtractorBinding} annotation is + * encountered the {@link com.oracle.coherence.cdi.ValueExtractorFactory} bean's + * {@link com.oracle.coherence.cdi.ValueExtractorFactory#create(java.lang.annotation.Annotation)} + * method is called to create an instance of a {@link com.tangosol.util.ValueExtractor}. + * + * @param the annotation type that the factory supports + * @param the type of the value to extract from + * @param the type of value that will be extracted + * + * @author Jonathan Knight 2019.10.25 + */ +public interface ValueExtractorFactory + { + /** + * Create a {@link com.tangosol.util.ValueExtractor} instance. + * + * @param annotation the {@link java.lang.annotation.Annotation} that + * defines the ValueExtractor + * + * @return a {@link com.tangosol.util.ValueExtractor} instance + */ + ValueExtractor create(A annotation); + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ValueExtractorProducer.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ValueExtractorProducer.java new file mode 100644 index 0000000000000..52d7468c8e993 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/ValueExtractorProducer.java @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import com.tangosol.util.Extractors; +import com.tangosol.util.ValueExtractor; +import com.tangosol.util.extractor.MultiExtractor; + +import java.lang.annotation.Annotation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.Annotated; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.DefinitionException; +import javax.enterprise.inject.spi.InjectionPoint; + +import javax.inject.Inject; + +/** + * A CDI bean that produces {@link ValueExtractor} instances using {@link + * ValueExtractorFactory} beans annotated with {@link ValueExtractorBinding} + * annotations. + * + * @author Jonathan Knight 2019.10.25 + */ +@ApplicationScoped +class ValueExtractorProducer + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code ValueExtractorProducer} instance. + * + * @param beanManager a {@code BeanManager} to use + * @param extractorFactoryResolver a {@code ValueExtractorFactoryResolver} to use + */ + @Inject + ValueExtractorProducer(BeanManager beanManager, ValueExtractorFactoryResolver extractorFactoryResolver) + { + f_beanManager = beanManager; + f_extractorFactoryResolver = extractorFactoryResolver; + } + + // ---- producer methods ------------------------------------------------ + + /** + * Create an instance of a {@link ValueExtractor} based on injection point + * metadata. + * + * @param injectionPoint the injection point to create an extractor for + * @param the type of object to extract the value from + * @param the type of extracted value + * + * @return an instance of a {@link ValueExtractor} + */ + @Produces + @SuppressWarnings("unchecked") + ValueExtractor getValueExtractor(InjectionPoint injectionPoint) + { + List list = new ArrayList<>(); + Annotated annotated = injectionPoint.getAnnotated(); + + if (annotated != null) + { + for (Annotation annotation : annotated.getAnnotations()) + { + if (annotation.annotationType().isAnnotationPresent(ValueExtractorBinding.class)) + { + Class clazz = f_extractorFactoryResolver.resolve(annotation); + if (clazz != null) + { + ValueExtractorFactory supplier = f_beanManager.createInstance().select(clazz).get(); + if (supplier != null) + { + ValueExtractor extractor = supplier.create(annotation); + if (extractor instanceof MultiExtractor) + { + Collections.addAll(list, ((MultiExtractor) extractor).getExtractors()); + } + else + { + list.add(extractor); + } + } + } + else + { + throw new DefinitionException( + "unsatisfied dependency - no ValueExtractorFactory bean found annotated with " + annotation); + } + } + } + } + + ValueExtractor[] aExtractors = list.toArray(new ValueExtractor[0]); + if (aExtractors.length == 0) + { + return null; + } + else if (aExtractors.length == 1) + { + return aExtractors[0]; + } + else + { + return Extractors.multi(aExtractors); + } + } + + // ---- inner class: UniversalExtractorSupplier ------------------------- + + /** + * A {{@link ValueExtractorFactory} that produces{@link ValueExtractor} + * instances for a given property or method name. + */ + @PropertyExtractor("") + @ApplicationScoped + static class UniversalExtractorSupplier + implements ValueExtractorFactory + { + @Override + public ValueExtractor create(PropertyExtractor annotation) + { + return Extractors.extract(annotation.value()); + } + } + + // ---- inner class: UniversalExtractorsSupplier ------------------------ + + /** + * A {{@link ValueExtractorFactory} that produces {@link + * com.tangosol.util.extractor.MultiExtractor} containing {@link + * ValueExtractor} instances produced from the annotations contained in a + * {@link PropertyExtractor.Extractors} annotation. + */ + @PropertyExtractor.Extractors({}) + @ApplicationScoped + static class UniversalExtractorsSupplier + implements ValueExtractorFactory + { + @Override + @SuppressWarnings("unchecked") + public ValueExtractor create(PropertyExtractor.Extractors annotation) + { + ValueExtractor[] extractors = Arrays.stream(annotation.value()) + .map(f_extractorSupplier::create) + .toArray(ValueExtractor[]::new); + return Extractors.multi(extractors); + } + + // ---- data members ------------------------------------------------ + + /** + * Extractor supplier. + */ + private final UniversalExtractorSupplier f_extractorSupplier = new UniversalExtractorSupplier(); + } + + // ---- inner class: ChainedExtractorSupplier --------------------------- + + /** + * A {{@link ValueExtractorFactory} that produces chained {@link + * ValueExtractor} instances for an array of property or method names. + */ + @ChainedExtractor("") + @ApplicationScoped + static class ChainedExtractorSupplier + implements ValueExtractorFactory + { + @Override + public ValueExtractor create(ChainedExtractor annotation) + { + return Extractors.chained(annotation.value()); + } + } + + // ---- inner class: ChainedExtractorsSupplier -------------------------- + + /** + * A {{@link ValueExtractorFactory} that produces {@link + * com.tangosol.util.extractor.MultiExtractor} containing {@link + * ValueExtractor} instances produced from the annotations contained in a + * {@link ChainedExtractor.Extractors} annotation. + */ + @ChainedExtractor.Extractors({}) + @ApplicationScoped + static class ChainedExtractorsSupplier + implements + ValueExtractorFactory + { + @Override + @SuppressWarnings("unchecked") + public ValueExtractor create(ChainedExtractor.Extractors annotation) + { + ValueExtractor[] extractors = Arrays.stream(annotation.value()) + .map(f_ExtractorSupplier::create) + .toArray(ValueExtractor[]::new); + return Extractors.multi(extractors); + } + + // ---- data members ------------------------------------------------ + + /** + * Extractor supplier. + */ + private final ChainedExtractorSupplier f_ExtractorSupplier = new ChainedExtractorSupplier(); + } + + // ---- inner class: PofExtractorSupplier ------------------------------- + + /** + * A {{@link ValueExtractorFactory} that produces{@link ValueExtractor} + * instances for a given property or method name. + */ + @PofExtractor(0) + @ApplicationScoped + static class PofExtractorSupplier + implements ValueExtractorFactory + { + @Override + @SuppressWarnings("unchecked") + public ValueExtractor create(PofExtractor annotation) + { + Class clazz = annotation.type().equals(Object.class) + ? null + : annotation.type(); + return Extractors.fromPof(clazz, annotation.value()); + } + } + + // ---- inner class: PofExtractorsSupplier ------------------------------- + + /** + * A {{@link ValueExtractorFactory} that produces {@link + * com.tangosol.util.extractor.MultiExtractor} containing {@link + * ValueExtractor} instances produced from the annotations contained in a + * {@link PofExtractor.Extractors} annotation. + */ + @PofExtractor.Extractors({}) + @ApplicationScoped + static class PofExtractorsSupplier + implements + ValueExtractorFactory + { + @Override + @SuppressWarnings("unchecked") + public ValueExtractor create(PofExtractor.Extractors annotation) + { + ValueExtractor[] extractors = Arrays.stream(annotation.value()) + .map(f_extractorSupplier::create) + .toArray(ValueExtractor[]::new); + return Extractors.multi(extractors); + } + + // ---- data members ------------------------------------------------ + + /** + * Extractor supplier. + */ + private final PofExtractorSupplier f_extractorSupplier = new PofExtractorSupplier(); + } + + // ---- inner class: ValueExtractorFactoryResolver ---------------------- + + /** + * A resolver of {{@link ValueExtractorFactory} bean classes for a given + * {{@link ValueExtractorBinding} annotation. + */ + static class ValueExtractorFactoryResolver + { + /** + * Construct {@code ValueExtractorFactoryResolver} instance. + * + * @param mapExtractorFactory the map of extractor bindings to extractor factories + */ + ValueExtractorFactoryResolver(Map>> mapExtractorFactory) + { + m_mapExtractorFactory = mapExtractorFactory; + } + + /** + * Obtain the {{@link ValueExtractorFactory} class for a given {{@link + * ValueExtractorBinding} annotation. + * + * @param annotation the extractor binding to obtain the {{@link + * ValueExtractorFactory} class for + * @param the type of the {{@link ValueExtractorBinding} + * annotation + * + * @return the {{@link ValueExtractorFactory} class for a given {{@link + * ValueExtractorBinding} annotation + */ + @SuppressWarnings("unchecked") + Class> resolve(A annotation) + { + AnnotationInstance instance = AnnotationInstance.create(annotation); + return (Class>) m_mapExtractorFactory.get(instance); + } + + // ---- data members ---------------------------------------------------- + + /** + * The map of extractor bindings to extractor factories. + */ + private Map>> m_mapExtractorFactory; + } + + // ---- data members ---------------------------------------------------- + + /** + * The current Bean Manager. + */ + private final BeanManager f_beanManager; + + /** + * The resolver that can resolve a {{@link ValueExtractorFactory} bean from + * a {{@link ValueExtractorBinding} annotation. + */ + private final ValueExtractorFactoryResolver f_extractorFactoryResolver; + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/WhereFilter.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/WhereFilter.java new file mode 100644 index 0000000000000..f6a9d1c5b7d85 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/WhereFilter.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; +import javax.enterprise.util.Nonbinding; + +/** + * A {@link com.oracle.coherence.cdi.FilterBinding} annotation representing a + * {@link com.tangosol.util.Filter} produced from a CohQL where clause. + * + * @author Jonathan Knight 2019.10.24 + */ +@Inherited +@FilterBinding +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface WhereFilter + { + /** + * The CohQL query expression. + * + * @return the CohQL query expression + */ + @Nonbinding String value(); + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link WhereFilter} annotation. + */ + class Literal + extends AnnotationLiteral + implements WhereFilter + { + /** + * Construct {@code Literal} instance. + * + * @param sQuery the CohQL query expression + */ + private Literal(String sQuery) + { + this.f_sQuery = sQuery; + } + + /** + * Create a {@link WhereFilter.Literal}. + * + * @param sQuery the CohQL query expression + * + * @return a {@link WhereFilter.Literal} with the specified CohQL query + */ + public static Literal of(String sQuery) + { + return new Literal(sQuery); + } + + /** + * The CohQL query expression. + * + * @return the CohQL query expression + */ + public String value() + { + return f_sQuery; + } + + // ---- data members ------------------------------------------------ + + /** + * The CohQL query expression. + */ + private final String f_sQuery; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Activated.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Activated.java new file mode 100644 index 0000000000000..2d58ad0a2f64d --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Activated.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any ACTIVATED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Activated + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Activated} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Activated + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Activating.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Activating.java new file mode 100644 index 0000000000000..ab3394ec6f689 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Activating.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any ACTIVATING event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Activating + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Activating} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Activating + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Arrived.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Arrived.java new file mode 100644 index 0000000000000..604fca81669bc --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Arrived.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any ARRIVED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Arrived + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Arrived} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Arrived + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Assigned.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Assigned.java new file mode 100644 index 0000000000000..5ba2487db371b --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Assigned.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any ASSIGNED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Assigned + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Assigned} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Assigned + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Backlog.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Backlog.java new file mode 100644 index 0000000000000..02870d9a92fe1 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Backlog.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any BACKLOG event. + * + * @author Aleks Seovic 2020.04.13 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Backlog + { + /** + * Obtain the type of backlog event. + * + * @return the type of backlog event + */ + Type value(); + + // ---- inner class: Type ----------------------------------------------- + + /** + * The backlog event type. + */ + enum Type + { + /** + * Indicates that a participant was previously + * backlogged but is no longer so. + */ + NORMAL, + + /** + * Indicates that a participant is backlogged; if + * the participant is remote it indicates the + * remote participant has more work than it can handle; + * if the participant is local it indicates this + * participant has more work than it can handle. + */ + EXCESSIVE + } + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link Backlog} annotation. + */ + class Literal + extends AnnotationLiteral + implements Backlog + { + /** + * Construct {@link Literal} instance. + * + * @param type the backlog event type + */ + private Literal(Type type) + { + f_type = type; + } + + /** + * Create a {@link Literal}. + * + * @param type the backlog event type + * + * @return a {@link Literal} with the specified value + */ + public static Literal of(Type type) + { + return new Literal(type); + } + + /** + * The backlog event type. + * + * @return the backlog event type + */ + public Type value() + { + return f_type; + } + + // ---- data members ------------------------------------------------ + + /** + * The backlog event type. + */ + private final Type f_type; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/CacheName.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/CacheName.java new file mode 100644 index 0000000000000..96cab614ea804 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/CacheName.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used when injecting Coherence resource to indicate a + * specific cache name. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface CacheName + { + /** + * Obtain the value used to identify a specific cache. + * + * @return value used to identify a specific cache + */ + String value(); + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link CacheName} annotation. + */ + class Literal + extends AnnotationLiteral + implements CacheName + { + /** + * Construct {@link CacheName.Literal} instance. + * + * @param sName the cache name + */ + private Literal(String sName) + { + f_sName = sName; + } + + /** + * Create a {@link CacheName.Literal}. + * + * @param sName the cache name + * + * @return a {@link CacheName.Literal} with the specified value + */ + public static Literal of(String sName) + { + return new Literal(sName); + } + + /** + * The name used to identify a specific cache. + * + * @return the name used to identify a specific cache + */ + public String value() + { + return f_sName; + } + + // ---- data members ------------------------------------------------ + + /** + * The cache name. + */ + private final String f_sName; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/CacheServerStarted.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/CacheServerStarted.java new file mode 100644 index 0000000000000..d840c926ef334 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/CacheServerStarted.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +/** + * An event that will be fired when Coherence cache server has been started. + * + * @author Aleks Seovic 2020.03.30 + */ +public class CacheServerStarted + { + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Committed.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Committed.java new file mode 100644 index 0000000000000..35016d49df711 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Committed.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any COMMITTED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Committed + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Committed} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Committed + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Committing.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Committing.java new file mode 100644 index 0000000000000..5f1ffd73076dc --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Committing.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any COMMITTING event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Committing + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Committing} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Committing + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Connecting.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Connecting.java new file mode 100644 index 0000000000000..0f943b403588a --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Connecting.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any CONNECTING event. + * + * @author Aleks Seovic 2020.04.13 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Connecting + { + /** + * An annotation literal for the {@link Connecting} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Connecting + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Created.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Created.java new file mode 100644 index 0000000000000..8679b5d2ee05b --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Created.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any CREATED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Created + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Created} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Created + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Departed.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Departed.java new file mode 100644 index 0000000000000..0f5ba65183c24 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Departed.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any DEPARTED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Departed + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Departed} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Departed + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Departing.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Departing.java new file mode 100644 index 0000000000000..336d4d94924d5 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Departing.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any DEPARTING event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Departing + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Departing} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Departing + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Destroyed.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Destroyed.java new file mode 100644 index 0000000000000..1bf23227eb74b --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Destroyed.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any DESTROYED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Destroyed + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Destroyed} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Destroyed + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Disconnected.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Disconnected.java new file mode 100644 index 0000000000000..46e68df1a4d58 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Disconnected.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any DISCONNECTED event. + * + * @author Aleks Seovic 2020.04.13 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Disconnected + { + /** + * An annotation literal for the {@link Disconnected} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Disconnected + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Disposing.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Disposing.java new file mode 100644 index 0000000000000..e1b771c9eb9ae --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Disposing.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any DISPOSING event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Disposing + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Disposing} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Disposing + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Error.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Error.java new file mode 100644 index 0000000000000..b808135660fa9 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Error.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any ERROR event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Error + { + /** + * An annotation literal for the {@link Error} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Error + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Executed.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Executed.java new file mode 100644 index 0000000000000..45d14ec03b00e --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Executed.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any EXECUTED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Executed + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Executed} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Executed + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Executing.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Executing.java new file mode 100644 index 0000000000000..bce99878305b7 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Executing.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any EXECUTING event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Executing + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Executing} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Executing + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Inserted.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Inserted.java new file mode 100644 index 0000000000000..48e8c65eb6222 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Inserted.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any INSERTED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Inserted + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Inserted} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Inserted + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Inserting.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Inserting.java new file mode 100644 index 0000000000000..ed9d2f93c91d4 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Inserting.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any INSERTING event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Inserting + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Inserting} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Inserting + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Local.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Local.java new file mode 100644 index 0000000000000..1924c92c32872 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Local.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any LOCAL event. + * + * @author Aleks Seovic 2020.04.13 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Local + { + /** + * An annotation literal for the {@link Local} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Local + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Lost.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Lost.java new file mode 100644 index 0000000000000..a61c2a42f1a8f --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Lost.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any LOST event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Lost + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Lost} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Lost + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/ParticipantName.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/ParticipantName.java new file mode 100644 index 0000000000000..0593459f73b11 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/ParticipantName.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used to indicate a specific participant name. + * + * @author Aleks Seovic 2020.04.13 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface ParticipantName + { + /** + * The participant name. + * + * @return the participant name + */ + String value(); + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link ParticipantName} annotation. + */ + class Literal + extends AnnotationLiteral + implements ParticipantName + { + /** + * Construct {@link ParticipantName.Literal} instance. + * + * @param sName the participant name + */ + private Literal(String sName) + { + f_sName = sName; + } + + /** + * Create a {@link Literal}. + * + * @param sName the participant name + * + * @return a {@link Literal} with the specified value + */ + public static Literal of(String sName) + { + return new Literal(sName); + } + + /** + * The participant name. + * + * @return the participant name + */ + public String value() + { + return f_sName; + } + + // ---- data members ------------------------------------------------ + + /** + * The participant name. + */ + private final String f_sName; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Processor.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Processor.java new file mode 100644 index 0000000000000..eec81b8bb7d48 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Processor.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +import com.tangosol.util.InvocableMap; + +/** + * A qualifier annotation used to indicate processor class when observing {@link + * com.tangosol.net.events.annotation.EntryProcessorEvents}. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Processor + { + /** + * The processor class. + * + * @return the processor class + */ + Class value(); + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Processor} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Processor + { + /** + * Construct {@code Literal} instance. + * + * @param clzProcessor the processor class + */ + private Literal(Class clzProcessor) + { + this.f_clzProcessor = clzProcessor; + } + + /** + * Create a {@link com.oracle.coherence.cdi.events.Processor.Literal}. + * + * @param clzProcessor the processor class + * + * @return a {@link com.oracle.coherence.cdi.events.Processor.Literal} + * with the specified value + */ + public static Literal of(Class clzProcessor) + { + return new Literal(clzProcessor); + } + + /** + * The processor class. + * + * @return the processor class + */ + public Class value() + { + return f_clzProcessor; + } + + // ---- data members ------------------------------------------------ + + /** + * The processor class. + */ + private final Class f_clzProcessor; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Recovered.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Recovered.java new file mode 100644 index 0000000000000..3f391f7e3f375 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Recovered.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any RECOVERED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Recovered + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Recovered} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Recovered + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Remote.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Remote.java new file mode 100644 index 0000000000000..b2af75cb840a4 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Remote.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any REMOTE event. + * + * @author Aleks Seovic 2020.04.13 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Remote + { + /** + * An annotation literal for the {@link Remote} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Remote + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Removed.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Removed.java new file mode 100644 index 0000000000000..d16c727de05f2 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Removed.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any REMOVED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Removed + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Removed} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Removed + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Removing.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Removing.java new file mode 100644 index 0000000000000..feabbad1b8e2d --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Removing.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any REMOVING event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Removing + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Removing} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Removing + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Replicating.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Replicating.java new file mode 100644 index 0000000000000..3889e1506a1b8 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Replicating.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any REPLICATING event. + * + * @author Aleks Seovic 2020.04.13 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Replicating + { + /** + * An annotation literal for the {@link Replicating} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Replicating + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Rollback.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Rollback.java new file mode 100644 index 0000000000000..6b57fae5d5ec6 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Rollback.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any ROLLBACK event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Rollback + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Rollback} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Rollback + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/ServiceName.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/ServiceName.java new file mode 100644 index 0000000000000..0aa8ffc7a4cf6 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/ServiceName.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used to indicate a specific service name. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface ServiceName + { + /** + * The value used to identify a specific service. + * + * @return the value used to identify a specific service + */ + String value(); + + // ---- inner class: Literal -------------------------------------------- + + /** + * An annotation literal for the {@link ServiceName} annotation. + */ + class Literal + extends AnnotationLiteral + implements ServiceName + { + /** + * Construct {@link ServiceName.Literal} instance. + * + * @param sName the service name + */ + private Literal(String sName) + { + f_sName = sName; + } + + /** + * Create a {@link ServiceName.Literal}. + * + * @param sName the service name + * + * @return a {@link ServiceName.Literal} with the specified value + */ + public static Literal of(String sName) + { + return new Literal(sName); + } + + /** + * The name used to identify a specific service. + * + * @return the name used to identify a specific service + */ + public String value() + { + return f_sName; + } + + // ---- data members ------------------------------------------------ + + /** + * The service name. + */ + private final String f_sName; + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Synced.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Synced.java new file mode 100644 index 0000000000000..b25b4497065dd --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Synced.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any SYNCED event. + * + * @author Aleks Seovic 2020.04.13 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Synced + { + /** + * An annotation literal for the {@link Synced} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Synced + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Syncing.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Syncing.java new file mode 100644 index 0000000000000..8f9c2ce8117da --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Syncing.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any SYNCING event. + * + * @author Aleks Seovic 2020.04.13 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Syncing + { + /** + * An annotation literal for the {@link Syncing} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Syncing + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Truncated.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Truncated.java new file mode 100644 index 0000000000000..aba72d542f6fd --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Truncated.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any TRUNCATED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Truncated + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Truncated} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Truncated + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Updated.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Updated.java new file mode 100644 index 0000000000000..51794c1162b7d --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Updated.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any UPDATED event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Updated + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Updated} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Updated + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Updating.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Updating.java new file mode 100644 index 0000000000000..133e5dc9f0d6a --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/Updating.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.events; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.util.AnnotationLiteral; + +import javax.inject.Qualifier; + +/** + * A qualifier annotation used for any UPDATING event. + * + * @author Aleks Seovic 2020.04.01 + */ +@Qualifier +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface Updating + { + /** + * An annotation literal for the {@link com.oracle.coherence.cdi.events.Updating} + * annotation. + */ + class Literal + extends AnnotationLiteral + implements Updating + { + public static final Literal INSTANCE = new Literal(); + } + } diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/package-info.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/package-info.java new file mode 100644 index 0000000000000..370d05b286d0f --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/events/package-info.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +/** + * Support for adapting Coherence interceptors to standard CDI events. + *

+ * This package contains the qualifiers necessary to define CDI observers for + * various Coherence events. + * + * @author Aleks Seovic 2020.04.01 + */ +package com.oracle.coherence.cdi.events; diff --git a/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/package-info.java b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/package-info.java new file mode 100644 index 0000000000000..120bdb33044c3 --- /dev/null +++ b/prj/coherence-cdi/src/main/java/com/oracle/coherence/cdi/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +/** + * Coherence CDI provides support for CDI (Contexts and Dependency Injection) + * within Coherence cluster members. + *

+ * It allows you both to inject Coherence-managed resources, such as + * `NamedCache` and `Session` instances into CDI managed beans, and to inject + * CDI beans into Coherence-managed resources, such as event interceptors and + * cache stores. + *

+ * In addition, Coherence CDI provides support for automatic injection of + * transient objects upon deserialization. This allows you to inject CDI managed + * beans such as services and repositories (to use DDD nomenclature) into + * transient objects, such as entry processor and data class instances, greatly + * simplifying implementation of true Domain Driven applications. + * + * @author Aleks Seovic 2019.10.09 + * @since 14.1.2 + */ +package com.oracle.coherence.cdi; diff --git a/prj/coherence-cdi/src/main/resources/META-INF/beans.xml b/prj/coherence-cdi/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000..3edaa1867945c --- /dev/null +++ b/prj/coherence-cdi/src/main/resources/META-INF/beans.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/prj/coherence-cdi/src/main/resources/META-INF/services/com.tangosol.net.events.InterceptorMetadataResolver b/prj/coherence-cdi/src/main/resources/META-INF/services/com.tangosol.net.events.InterceptorMetadataResolver new file mode 100644 index 0000000000000..2d7ab76245d74 --- /dev/null +++ b/prj/coherence-cdi/src/main/resources/META-INF/services/com.tangosol.net.events.InterceptorMetadataResolver @@ -0,0 +1,8 @@ +# +# Copyright (c) 2020 Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# + +com.oracle.coherence.cdi.CdiInterceptorMetadataResolver diff --git a/prj/coherence-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/prj/coherence-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 0000000000000..b98f37e53c0b6 --- /dev/null +++ b/prj/coherence-cdi/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1,8 @@ +# +# Copyright (c) 2019, 2020 Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# + +com.oracle.coherence.cdi.CoherenceExtension diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/AnnotationInstanceTest.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/AnnotationInstanceTest.java new file mode 100644 index 0000000000000..6d14779e3dfad --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/AnnotationInstanceTest.java @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import javax.enterprise.util.Nonbinding; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Tests for the {@link AnnotationInstance} class. + * + * @author Jonathan Knight 2019.10.24 + */ +public class AnnotationInstanceTest + { + + static Stream getBeanAnnotations() + { + return Stream.of(BeanOne.class.getAnnotations()); + } + + static Stream getBeanOneBeanTwoAnnotationsPairs() + { + List arguments = new ArrayList<>(); + Annotation[] annotations = BeanOne.class.getAnnotations(); + for (Annotation annotation : annotations) + { + arguments.add(Arguments.of(annotation, BeanTwo.class.getAnnotation(annotation.annotationType()))); + } + return arguments.stream(); + } + + static Stream getBeanOneBeanThreeAnnotationsPairs() + { + List arguments = new ArrayList<>(); + Annotation[] annotations = BeanOne.class.getAnnotations(); + for (Annotation annotation : annotations) + { + arguments.add(Arguments.of(annotation, BeanThree.class.getAnnotation(annotation.annotationType()))); + } + return arguments.stream(); + } + + @Test + void shouldBeEqualForSimpleAnnotation() + { + AnnotationInstance one = AnnotationInstance.create(Simple.class.getAnnotation(AnnOne.class)); + AnnotationInstance two = AnnotationInstance.create(Simple.class.getAnnotation(AnnOne.class)); + + assertThat(one.equals(two), is(true)); + assertThat(two.equals(one), is(true)); + } + + @Test + void shouldHaveZeroHashCodeSimpleAnnotation() + { + AnnotationInstance instance = AnnotationInstance.create(Simple.class.getAnnotation(AnnOne.class)); + assertThat(instance.hashCode(), is(0)); + } + + @ParameterizedTest + @MethodSource("getBeanAnnotations") + void shouldBeEqual(Annotation annotation) + { + AnnotationInstance one = AnnotationInstance.create(annotation); + AnnotationInstance two = AnnotationInstance.create(annotation); + + assertThat(one.equals(two), is(true)); + assertThat(two.equals(one), is(true)); + } + + @Test + void shouldBNoteEqualIfDifferentAnnotation() + { + AnnotationInstance one = AnnotationInstance.create(BeanOne.class.getAnnotation(AnnString.class)); + AnnotationInstance two = AnnotationInstance.create(BeanOne.class.getAnnotation(AnnFloat.class)); + + assertThat(one.equals(two), is(false)); + assertThat(two.equals(one), is(false)); + } + + @Test + void shouldBNoteEqualToNonAnnotationInstance() + { + AnnotationInstance one = AnnotationInstance.create(BeanOne.class.getAnnotation(AnnString.class)); + + assertThat(one.equals("foo"), is(false)); + } + + @Test + void shouldBNoteEqualToNull() + { + AnnotationInstance one = AnnotationInstance.create(BeanOne.class.getAnnotation(AnnString.class)); + + assertThat(one.equals(null), is(false)); + } + + @ParameterizedTest + @MethodSource("getBeanAnnotations") + void shouldHaveSameHashCode(Annotation annotation) + { + AnnotationInstance one = AnnotationInstance.create(annotation); + AnnotationInstance two = AnnotationInstance.create(annotation); + + assertThat(one.hashCode(), is(two.hashCode())); + } + + @ParameterizedTest + @MethodSource("getBeanAnnotations") + void shouldHaveToString(Annotation annotation) + { + AnnotationInstance instance = AnnotationInstance.create(annotation); + + assertThat(instance.toString(), is(notNullValue())); + assertThat(instance.toString(), is(not(""))); + } + + @ParameterizedTest + @MethodSource("getBeanOneBeanTwoAnnotationsPairs") + void shouldNotBeEqual(Annotation annotationOne, Annotation annotationTwo) + { + AnnotationInstance one = AnnotationInstance.create(annotationOne); + AnnotationInstance two = AnnotationInstance.create(annotationTwo); + + assertThat(one.equals(two), is(false)); + assertThat(two.equals(one), is(false)); + } + + @ParameterizedTest + @MethodSource("getBeanOneBeanTwoAnnotationsPairs") + void shouldNotHaveSameHashCode(Annotation annotationOne, Annotation annotationTwo) + { + AnnotationInstance one = AnnotationInstance.create(annotationOne); + AnnotationInstance two = AnnotationInstance.create(annotationTwo); + + assertThat(one.hashCode(), is(not(two.hashCode()))); + } + + @ParameterizedTest + @MethodSource("getBeanOneBeanThreeAnnotationsPairs") + void shouldBeEqualIgnoringNonBindingValues(Annotation annotationOne, Annotation annotationTwo) + { + AnnotationInstance one = AnnotationInstance.create(annotationOne); + AnnotationInstance two = AnnotationInstance.create(annotationTwo); + + assertThat(one.equals(two), is(true)); + assertThat(two.equals(one), is(true)); + } + + @ParameterizedTest + @MethodSource("getBeanOneBeanThreeAnnotationsPairs") + void shouldHaveSameHashCodeIgnoringNonBindingValues(Annotation annotationOne, Annotation annotationTwo) + { + AnnotationInstance one = AnnotationInstance.create(annotationOne); + AnnotationInstance two = AnnotationInstance.create(annotationTwo); + + assertThat(one.hashCode(), is(two.hashCode())); + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnOne + { + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnByteArray + { + byte[] value() default {1, 2, 3}; + + @Nonbinding byte[] nonBinding() default {1, 2, 3}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnShortArray + { + short[] value() default {4, 5, 6}; + + @Nonbinding short[] nonBinding() default {4, 5, 6}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnIntArray + { + int[] value() default {7, 8, 9}; + + @Nonbinding int[] nonBinding() default {7, 8, 9}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnLongArray + { + long[] value() default {10L, 11L, 12L}; + + @Nonbinding long[] nonBinding() default {10L, 11L, 12L}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnFloatArray + { + float[] value() default {13.1f, 14.2f, 15.3f}; + + @Nonbinding float[] nonBinding() default {13.1f, 14.2f, 15.3f}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnDoubleArray + { + double[] value() default {16.1d, 17.2d, 18.3d}; + + @Nonbinding double[] nonBinding() default {16.1d, 17.2d, 18.3d}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnCharArray + { + char[] value() default {'a', 'b', 'c'}; + + @Nonbinding char[] nonBinding() default {'a', 'b', 'c'}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnBooleanArray + { + boolean[] value() default {true, false}; + + @Nonbinding boolean[] nonBinding() default {true, false}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnStringArray + { + String[] value() default {"one", "two", "three"}; + + @Nonbinding String[] nonBinding() default {"one", "two", "three"}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnClassArray + { + Class[] value() default {Integer.class, Byte.class}; + + @Nonbinding Class[] nonBinding() default {Integer.class, Byte.class}; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnByte + { + byte value() default 1; + + @Nonbinding byte nonBinding() default 1; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnShort + { + short value() default 2; + + @Nonbinding short nonBinding() default 2; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnInt + { + int value() default 3; + + @Nonbinding int nonBinding() default 3; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnLong + { + long value() default 4L; + + @Nonbinding long nonBinding() default 4L; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnFloat + { + float value() default 5.6f; + + @Nonbinding float nonBinding() default 5.6f; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnDouble + { + double value() default 7.8f; + + @Nonbinding double nonBinding() default 7.8f; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnChar + { + char value() default 'z'; + + @Nonbinding char nonBinding() default 'z'; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnBoolean + { + boolean value() default true; + + @Nonbinding boolean nonBinding() default true; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnString + { + String value() default "foo"; + + @Nonbinding String nonBinding() default "foo"; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @interface AnnClass + { + Class value() default String.class; + + @Nonbinding Class nonBinding() default String.class; + } + + @AnnOne + static class Simple + { + } + + @AnnByteArray + @AnnShortArray + @AnnIntArray + @AnnLongArray + @AnnFloatArray + @AnnDoubleArray + @AnnCharArray + @AnnBooleanArray + @AnnStringArray + @AnnClassArray + @AnnByte + @AnnShort + @AnnInt + @AnnLong + @AnnFloat + @AnnDouble + @AnnChar + @AnnBoolean + @AnnString + @AnnClass + static class BeanOne + { + } + + @AnnByteArray(value = {101, 102, 103}) + @AnnShortArray(value = {104, 105, 106}) + @AnnIntArray(value = {107, 108, 109}) + @AnnLongArray(value = {110L, 111L, 112L}) + @AnnFloatArray(value = {113.1f, 114.2f, 115.3f}) + @AnnDoubleArray(value = {116.1d, 117.2d, 118.3d}) + @AnnCharArray(value = {'x', 'y', 'z'}) + @AnnBooleanArray(value = {false, true}) + @AnnStringArray(value = {"three", "four"}) + @AnnClassArray(value = {Float.class, Double.class}) + @AnnByte(value = 100) + @AnnShort(value = 100) + @AnnInt(value = 300) + @AnnLong(value = 400L) + @AnnFloat(value = 500.1f) + @AnnDouble(value = 600.1d) + @AnnChar(value = 'n') + @AnnBoolean(value = false) + @AnnString(value = "bar") + @AnnClass(value = Boolean.class) + static class BeanTwo + { + } + + @AnnByteArray(nonBinding = {101, 102, 103}) + @AnnShortArray(nonBinding = {104, 105, 106}) + @AnnIntArray(nonBinding = {107, 108, 109}) + @AnnLongArray(nonBinding = {110L, 111L, 112L}) + @AnnFloatArray(nonBinding = {113.1f, 114.2f, 115.3f}) + @AnnDoubleArray(nonBinding = {116.1d, 117.2d, 118.3d}) + @AnnCharArray(nonBinding = {'x', 'y', 'z'}) + @AnnBooleanArray(nonBinding = {false, true}) + @AnnStringArray(nonBinding = {"three", "four"}) + @AnnClassArray(nonBinding = {Float.class, Double.class}) + @AnnByte(nonBinding = 100) + @AnnShort(nonBinding = 100) + @AnnInt(nonBinding = 300) + @AnnLong(nonBinding = 400L) + @AnnFloat(nonBinding = 500.1f) + @AnnDouble(nonBinding = 600.1d) + @AnnChar(nonBinding = 'n') + @AnnBoolean(nonBinding = false) + @AnnString(nonBinding = "bar") + @AnnClass(nonBinding = Boolean.class) + static class BeanThree + { + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/BeanBuilderTest.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/BeanBuilderTest.java new file mode 100644 index 0000000000000..f1e5c7c3660f7 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/BeanBuilderTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; +import javax.inject.Named; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.SystemPropertyParameterResolver; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Unit tests for {@link com.oracle.coherence.cdi.Injectable}. + * + * @author Aleks Seovic 2019.10.02 + */ +class BeanBuilderTest + { + public static final SystemPropertyParameterResolver RESOLVER = new SystemPropertyParameterResolver(); + + private static SeContainer container; + + @BeforeAll + static void initContainer() + { + SeContainerInitializer containerInit = SeContainerInitializer.newInstance(); + container = containerInit.initialize(); + } + + @AfterAll + static void shutdownContainer() + { + container.close(); + } + + @Test + void testRealizeSuccess() + { + BeanBuilder builder = new BeanBuilder("beanX"); + Object bean = builder.realize(new SystemPropertyParameterResolver(), null, null); + assertThat(bean, notNullValue()); + assertThat(bean, instanceOf(BeanX.class)); + } + + @Test + void testRealizeMissingBean() + { + assertThrows(ConfigurationException.class, () -> + { + BeanBuilder builder = new BeanBuilder("beanY"); + Object bean = builder.realize(RESOLVER, null, null); + assertThat(bean, notNullValue()); + assertThat(bean, instanceOf(BeanX.class)); + }); + } + + @Test + void testRealizesSuccess() + { + BeanBuilder builder = new BeanBuilder("beanX"); + boolean fRealizes = builder.realizes(BeanX.class, RESOLVER, null); + assertThat(fRealizes, is(true)); + } + + @Test + void testRealizesFailureWrongClass() + { + BeanBuilder builder = new BeanBuilder("beanX"); + boolean fRealizes = builder.realizes(Injectable.class, RESOLVER, null); + assertThat(fRealizes, is(false)); + } + + @Test + void testRealizesFailureMissingBean() + { + BeanBuilder builder = new BeanBuilder("beanY"); + boolean fRealizes = builder.realizes(BeanX.class, RESOLVER, null); + assertThat(fRealizes, is(false)); + } + + @Named("beanX") + @ApplicationScoped + static class BeanX + { + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/CacheViewProducerIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/CacheViewProducerIT.java new file mode 100644 index 0000000000000..f8a1f52b02227 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/CacheViewProducerIT.java @@ -0,0 +1,571 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import com.oracle.coherence.cdi.data.Person; +import com.oracle.coherence.cdi.data.PhoneNumber; + +import com.tangosol.net.NamedCache; +import com.tangosol.net.cache.CacheMap; +import com.tangosol.net.cache.ContinuousQueryCache; +import com.tangosol.util.ConcurrentMap; +import com.tangosol.util.Filters; +import com.tangosol.util.InvocableMap; +import com.tangosol.util.ObservableMap; +import com.tangosol.util.QueryMap; + +import org.hamcrest.Matchers; +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for the CQC producers using the Weld JUnit extension. + * + * @author Jonathan Knight 2019.10.24 + */ +@ExtendWith(WeldJunit5Extension.class) +class CacheViewProducerIT + { + + @WeldSetup + private WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(CtorBean.class) + .addBeanClass(ContinuousQueryCacheFieldsBean.class) + .addBeanClass(SuperTypesBean.class) + .addBeanClass(DifferentCacheFactoryBean.class) + .addBeanClass(ContinuousQueryCacheWithFiltersBean.class) + .addBeanClass(WithTransformersBean.class) + .addBeanClass(NamedCacheProducer.class) + .addBeanClass(FilterProducer.class) + .addBeanClass(FilterProducer.AlwaysFilterSupplier.class) + .addBeanClass(FilterProducer.WhereFilterSupplier.class) + .addBeanClass(ValueExtractorProducer.class) + .addBeanClass(ValueExtractorProducer.UniversalExtractorSupplier.class) + .addBeanClass(ValueExtractorProducer.UniversalExtractorsSupplier.class) + .addBeanClass(ValueExtractorProducer.ChainedExtractorSupplier.class) + .addBeanClass(ValueExtractorProducer.ChainedExtractorsSupplier.class) + .addBeanClass(ValueExtractorProducer.PofExtractorSupplier.class) + .addBeanClass(ValueExtractorProducer.PofExtractorsSupplier.class) + .addBeanClass(CacheFactoryUriResolver.Default.class) + .addBeanClass(ConfigurableCacheFactoryProducer.class) + .addExtension(new CoherenceExtension())); + + @BeforeAll + static void setup() + { + System.setProperty("coherence.distributed.localstorage", "true"); + System.setProperty("coherence.ttl", "0"); + } + + @Test + void shouldGetDynamicContinuousQueryCache() + { + Annotation cache = Cache.Literal.of("numbers"); + Annotation cacheView = CacheView.Literal.INSTANCE; + Instance instance = weld.select(ContinuousQueryCache.class, cache, cacheView); + + assertThat(instance.isResolvable(), is(true)); + ContinuousQueryCache cqc = instance.get(); + assertThat(cqc, is(instanceOf(ContinuousQueryCache.class))); + assertThat(cqc.getCache().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectContinuousQueryCacheUsingFieldName() + { + ContinuousQueryCacheFieldsBean bean = weld.select(ContinuousQueryCacheFieldsBean.class).get(); + assertThat(bean.getNumbers(), is(notNullValue())); + assertThat(bean.getNumbers(), is(instanceOf(ContinuousQueryCache.class))); + assertThat(bean.getNumbers().getCache().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectQualifiedNamedCache() + { + ContinuousQueryCacheFieldsBean bean = weld.select(ContinuousQueryCacheFieldsBean.class).get(); + assertThat(bean.getNamedCache(), is(notNullValue())); + assertThat(bean.getNamedCache(), is(instanceOf(ContinuousQueryCache.class))); + assertThat(bean.getNamedCache().getCache().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectContinuousQueryCacheWithGenerics() + { + ContinuousQueryCacheFieldsBean bean = weld.select(ContinuousQueryCacheFieldsBean.class).get(); + assertThat(bean.getGenericCache(), is(notNullValue())); + assertThat(bean.getGenericCache(), is(instanceOf(ContinuousQueryCache.class))); + assertThat(bean.getGenericCache().getCache().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectContinuousQueryCacheWithGenericKeys() + { + ContinuousQueryCacheFieldsBean bean = weld.select(ContinuousQueryCacheFieldsBean.class).get(); + assertThat(bean.getGenericKeys(), is(notNullValue())); + assertThat(bean.getGenericKeys(), is(instanceOf(ContinuousQueryCache.class))); + assertThat(bean.getGenericKeys().getCache().getCacheName(), is("genericKeys")); + } + + @Test + void shouldInjectContinuousQueryCacheWithGenericValues() + { + ContinuousQueryCacheFieldsBean bean = weld.select(ContinuousQueryCacheFieldsBean.class).get(); + assertThat(bean.getGenericValues(), is(notNullValue())); + assertThat(bean.getGenericValues(), is(instanceOf(ContinuousQueryCache.class))); + assertThat(bean.getGenericValues().getCache().getCacheName(), is("genericValues")); + } + + @Test + void shouldInjectCachesFromDifferentCacheFactories() + { + DifferentCacheFactoryBean bean = weld.select(DifferentCacheFactoryBean.class).get(); + + assertThat(bean.getDefaultCcfNumbers(), is(notNullValue())); + assertThat(bean.getDefaultCcfNumbers(), is(instanceOf(ContinuousQueryCache.class))); + assertThat(bean.getDefaultCcfNumbers().getCache().getCacheName(), Matchers.is("numbers")); + + assertThat(bean.getSpecificCcfNumbers(), is(notNullValue())); + assertThat(bean.getSpecificCcfNumbers(), is(instanceOf(ContinuousQueryCache.class))); + assertThat(bean.getSpecificCcfNumbers().getCache().getCacheName(), Matchers.is("numbers")); + + assertThat(bean.getDefaultCcfNumbers().getCache().getCacheService(), + is(not(bean.getSpecificCcfNumbers().getCache().getCacheService()))); + } + + @Test + void testCtorInjection() + { + CtorBean bean = weld.select(CtorBean.class).get(); + + assertThat(bean.getNumbers(), Matchers.notNullValue()); + assertThat(bean.getNumbers(), is(instanceOf(ContinuousQueryCache.class))); + assertThat(bean.getNumbers().getCache().getCacheName(), Matchers.is("numbers")); + } + + @Test + void shouldInjectSuperTypeContinuousQueryCache() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + ContinuousQueryCache cache = bean.getContinuousQueryCache(); + assertThat(cache, is(notNullValue())); + assertThat(cache, is(sameInstance(bean.getContinuousQueryCache()))); + } + + @Test + void shouldInjectSuperTypeNamedCache() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + NamedCache cache = bean.getNamedCache(); + assertThat(cache, is(notNullValue())); + } + + @Test + void shouldInjectSuperTypeInvocableMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + InvocableMap map = bean.getInvocableMap(); + assertThat(map, is(notNullValue())); + } + + @Test + void shouldInjectSuperTypeObservableMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + ObservableMap map = bean.getObservableMap(); + assertThat(map, is(notNullValue())); + } + + @Test + void shouldInjectSuperTypeConcurrentMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + ConcurrentMap map = bean.getConcurrentMap(); + assertThat(map, is(notNullValue())); + } + + @Test + void shouldInjectSuperTypeQueryMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + QueryMap map = bean.getQueryMap(); + assertThat(map, is(notNullValue())); + } + + @Test + void shouldInjectSuperTypeCacheMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + CacheMap map = bean.getCacheMap(); + assertThat(map, is(notNullValue())); + } + + @Test + void shouldInjectContinuousQueryCacheWithFilters() + { + ContinuousQueryCacheWithFiltersBean withFilters = weld.select(ContinuousQueryCacheWithFiltersBean.class).get(); + NamedCache cache = withFilters.getCache(); + ContinuousQueryCache always = withFilters.getAlways(); + ContinuousQueryCache foo = withFilters.getFoo(); + + // populate the underlying cache + populate(cache); + assertThat(always.size(), is(cache.size())); + + Set> entries = cache.entrySet(Filters.equal("lastName", "foo")); + assertThat(foo.size(), is(entries.size())); + for (Map.Entry entry : entries) + { + assertThat(foo.get(entry.getKey()), is(entry.getValue())); + } + } + + @Test + void shouldInjectContinuousQueryCacheWithTransformer() + { + WithTransformersBean bean = weld.select(WithTransformersBean.class).get(); + NamedCache cache = bean.getNamedCache(); + ContinuousQueryCache names = bean.getNames(); + + // populate the underlying cache + populate(cache); + + assertThat(names.size(), is(cache.size())); + for (Map.Entry entry : cache.entrySet()) + { + assertThat(names.get(entry.getKey()), is(entry.getValue().getFirstName())); + } + } + + @Test + void shouldInjectContinuousQueryCacheWithTransformerAndFilter() + { + WithTransformersBean bean = weld.select(WithTransformersBean.class).get(); + NamedCache cache = bean.getNamedCache(); + ContinuousQueryCache filtered = bean.getFilteredNames(); + + // populate the underlying cache + populate(cache); + + Set> entries = cache.entrySet(Filters.equal("lastName", "foo")); + assertThat(filtered.size(), is(entries.size())); + for (Map.Entry entry : entries) + { + assertThat(filtered.get(entry.getKey()), is(entry.getValue().getPhoneNumber().getNumber())); + } + } + + @Test + void shouldInjectContinuousQueryCacheWithKeysOnly() + { + WithTransformersBean bean = weld.select(WithTransformersBean.class).get(); + NamedCache cache = bean.getNamedCache(); + ContinuousQueryCache keysOnly = bean.getKeysOnly(); + + // populate the underlying cache + populate(cache); + + assertThat(keysOnly.size(), is(cache.size())); + assertThat(keysOnly.isCacheValues(), is(false)); + } + + private void populate(NamedCache cache) + { + for (int i = 0; i < 100; i++) + { + String lastName = (i % 2 == 0) ? "foo" : "bar"; + Person bean = new Person(String.valueOf(i), + lastName, + LocalDate.now(), + new PhoneNumber(44, "12345" + i)); + + cache.put(lastName + "-" + i, bean); + } + } + + // ----- test beans ----------------------------------------------------- + + @ApplicationScoped + private static class ContinuousQueryCacheFieldsBean + { + @Inject + private ContinuousQueryCache numbers; + + @Inject + @Cache("numbers") + @CacheView + private ContinuousQueryCache namedCache; + + @Inject + @Cache("numbers") + @CacheView + private ContinuousQueryCache genericCache; + + @Inject + @CacheView + private ContinuousQueryCache, String, String> genericKeys; + + @Inject + @CacheView + private ContinuousQueryCache, String> genericValues; + + public ContinuousQueryCache getNumbers() + { + return numbers; + } + + public ContinuousQueryCache getNamedCache() + { + return namedCache; + } + + public ContinuousQueryCache getGenericCache() + { + return genericCache; + } + + public ContinuousQueryCache, String, String> getGenericKeys() + { + return genericKeys; + } + + public ContinuousQueryCache, String> getGenericValues() + { + return genericValues; + } + } + + @ApplicationScoped + private static class ContinuousQueryCacheWithFiltersBean + { + @Inject + private NamedCache beans; + + @Inject + @AlwaysFilter + @Cache("beans") + @CacheView + private ContinuousQueryCache always; + + @Inject + @WhereFilter("lastName = 'foo'") + @Cache("beans") + @CacheView + private ContinuousQueryCache foo; + + public NamedCache getCache() + { + return beans; + } + + public ContinuousQueryCache getAlways() + { + return always; + } + + public ContinuousQueryCache getFoo() + { + return foo; + } + } + + @ApplicationScoped + private static class DifferentCacheFactoryBean + { + @Inject + @Cache("numbers") + @CacheView + private ContinuousQueryCache defaultCcfNumbers; + + @Inject + @Cache("numbers") + @CacheView + @CacheFactory("test-cache-config.xml") + private ContinuousQueryCache specificCcfNumbers; + + @Inject + @CacheFactory("test-cache-config.xml") + private NamedCache numbers; + + public ContinuousQueryCache getDefaultCcfNumbers() + { + return defaultCcfNumbers; + } + + public ContinuousQueryCache getSpecificCcfNumbers() + { + return specificCcfNumbers; + } + } + + @ApplicationScoped + private static class CtorBean + { + private final NamedCache view; + + private final ContinuousQueryCache numbers; + + @Inject + CtorBean(@Cache("numbers") @CacheView NamedCache view, + @Cache("numbers") ContinuousQueryCache numbers) + { + this.view = view; + this.numbers = numbers; + } + + NamedCache getView() + { + return view; + } + + ContinuousQueryCache getNumbers() + { + return numbers; + } + } + + @ApplicationScoped + private static class SuperTypesBean + { + @Inject + @Cache("numbers") + @CacheView + private ContinuousQueryCache cqc; + + @Inject + @Cache("numbers") + @CacheView + private NamedCache namedCache; + + @Inject + @Cache("numbers") + @CacheView + private InvocableMap invocableMap; + + @Inject + @Cache("numbers") + @CacheView + private ObservableMap observableMap; + + @Inject + @Cache("numbers") + @CacheView + private ConcurrentMap concurrentMap; + + @Inject + @Cache("numbers") + @CacheView + private QueryMap queryMap; + + @Inject + @Cache("numbers") + @CacheView + private CacheMap cacheMap; + + ContinuousQueryCache getContinuousQueryCache() + { + return cqc; + } + + NamedCache getNamedCache() + { + return namedCache; + } + + InvocableMap getInvocableMap() + { + return invocableMap; + } + + ObservableMap getObservableMap() + { + return observableMap; + } + + ConcurrentMap getConcurrentMap() + { + return concurrentMap; + } + + QueryMap getQueryMap() + { + return queryMap; + } + + CacheMap getCacheMap() + { + return cacheMap; + } + } + + @ApplicationScoped + private static class WithTransformersBean + { + @Inject + @Cache("people") + private NamedCache namedCache; + + @Inject + @Cache("people") + @CacheView(cacheValues = false) + private ContinuousQueryCache keysOnly; + + @Inject + @Cache("people") + @CacheView + @PropertyExtractor("firstName") + private ContinuousQueryCache names; + + @Inject + @Cache("people") + @CacheView + @ChainedExtractor({"phoneNumber", "number"}) + @WhereFilter("lastName = 'foo'") + private ContinuousQueryCache filteredNames; + + NamedCache getNamedCache() + { + return namedCache; + } + + ContinuousQueryCache getNames() + { + return names; + } + + ContinuousQueryCache getFilteredNames() + { + return filteredNames; + } + + ContinuousQueryCache getKeysOnly() + { + return keysOnly; + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/CdiNamespaceHandlerIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/CdiNamespaceHandlerIT.java new file mode 100644 index 0000000000000..bc84cacc726d8 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/CdiNamespaceHandlerIT.java @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import javax.inject.Named; + +import com.oracle.coherence.cdi.events.Activated; +import com.oracle.coherence.cdi.events.Activating; +import com.oracle.coherence.cdi.events.CacheName; +import com.oracle.coherence.cdi.events.Created; +import com.oracle.coherence.cdi.events.Destroyed; +import com.oracle.coherence.cdi.events.Disposing; +import com.oracle.coherence.cdi.events.ServiceName; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.MemberEvent; +import com.tangosol.net.NamedCache; +import com.tangosol.net.events.Event; +import com.tangosol.net.events.EventDispatcher.InterceptorRegistrationEvent; +import com.tangosol.net.events.EventInterceptor; +import com.tangosol.net.events.annotation.CacheLifecycleEvents; +import com.tangosol.net.events.annotation.EntryEvents; +import com.tangosol.net.events.annotation.LifecycleEvents; +import com.tangosol.net.events.application.LifecycleEvent; +import com.tangosol.net.events.partition.cache.CacheLifecycleEvent; +import com.tangosol.net.events.partition.cache.EntryEvent; +import com.tangosol.net.partition.PartitionEvent; + +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; + +import static com.tangosol.net.events.partition.cache.CacheLifecycleEvent.Type.CREATED; +import static com.tangosol.net.events.partition.cache.CacheLifecycleEvent.Type.DESTROYED; +import static com.tangosol.net.events.partition.cache.EntryEvent.Type.INSERTING; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for the {@link com.oracle.coherence.cdi.CdiNamespaceHandler} + * using the Weld JUnit extension. + * + * @author as 2020.03.31 + */ +@ExtendWith(WeldJunit5Extension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class CdiNamespaceHandlerIT + { + + @WeldSetup + private WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(CacheFactoryUriResolver.Default.class) + .addBeanClass(ConfigurableCacheFactoryProducer.class) + .addBeanClass(CacheStore.class) + .addBeanClass(PartitionListener.class) + .addBeanClass(MemberListener.class) + .addBeanClass(CacheListener.class) + .addBeanClass(StorageListener.class) + .addBeanClass(RegistrationListener.class) + .addBeanClass(ActivationListener.class)); + + @Inject + @CacheFactory("cdi-beans-cache-config.xml") + private ConfigurableCacheFactory ccf; + + @Inject + private ActivationListener activationListener; + + @Inject + private StorageListener serviceListener; + + @Inject + private MemberListener memberListener; + + @Inject + private PartitionListener partitionListener; + + @Inject + private CacheStore cacheStore; + + @Test + @Order(10) + void shouldNotifyActivationListener() + { + ccf.activate(); + assertThat(activationListener.isActivated(), is(true)); + } + + @Test + @Order(20) + void shouldConvertValuesToUppercase() + { + NamedCache numbers = ccf.ensureCache("numbers", null); + numbers.put(1L, "one"); + numbers.put(2L, "two"); + assertThat(numbers.get(1L), is("ONE")); + assertThat(numbers.get(2L), is("TWO")); + } + + @Test + @Order(25) + void shouldUpdateCacheStore() + { + assertThat(cacheStore.getStoreMap().get(1L), is("ONE")); + assertThat(cacheStore.getStoreMap().get(2L), is("TWO")); + } + + @Test + @Order(25) + void shouldLoadFromCacheStore() + { + NamedCache numbers = ccf.ensureCache("numbers", null); + assertThat(numbers.get(10L), is("10")); + assertThat(numbers.get(20L), is("20")); + } + + @Test + @Order(30) + void shouldUpdateCacheNames() + { + assertThat(serviceListener.hasCache("apples"), is(false)); + NamedCache apples = ccf.ensureCache("apples", null); + assertThat(serviceListener.hasCache("apples"), is(true)); + ccf.destroyCache(apples); + assertThat(serviceListener.hasCache("apples"), is(false)); + } + + @Test + @Order(40) + void shouldJoin() + { + assertThat(memberListener.hasJoined(), is(true)); + } + + @Test + @Order(50) + void shouldAssignPartitions() + { + assertThat(partitionListener.getId(), is(PartitionEvent.PARTITION_ASSIGNED)); + } + + // ---- helper classes -------------------------------------------------- + + @ApplicationScoped + private static class CdiObservers + { + // will be called for all events + private void onEvent(@Observes Event event) + { + System.out.println("onEvent: " + event); + } + + // will be called for all lifecycle events + private void onLifecycleEvent(@Observes LifecycleEvent event) + { + System.out.println("onLifecycleEvent: " + event); + } + + // will be called for specific lifecycle events + private void onActivating(@Observes @Activating LifecycleEvent event) + { + System.out.println("onActivating: " + event); + } + + private void onActivated(@Observes @Activated LifecycleEvent event) + { + System.out.println("onActivated: " + event); + } + + private void onDisposing(@Observes @Disposing LifecycleEvent event) + { + System.out.println("onDisposing: " + event); + } + + // will be called for all cache lifecycle events + private void onCacheLifecycleEvent(@Observes CacheLifecycleEvent event) + { + System.out.println("onCacheLifecycleEvent: " + event); + } + + private void onCreated(@Observes @Created CacheLifecycleEvent event) + { + System.out.println("onCreated: " + event); + } + + private void onCreatedCache(@Observes @Created @ServiceName("PartitionedCache") CacheLifecycleEvent event) + { + System.out.println("onCreatedCache: " + event); + } + + private void onCreatedApples(@Observes @Created @CacheName("apples") CacheLifecycleEvent event) + { + System.out.println("onCreatedApples: " + event); + } + } + + @ApplicationScoped + @Named("registrationListener") + public static class RegistrationListener + implements EventInterceptor> + { + @Override + public void onEvent(InterceptorRegistrationEvent e) + { + if (e.getType() == InterceptorRegistrationEvent.Type.INSERTED) + { + System.out.println("REGISTERED: " + e.getIdentifier() + ", EVENTS: " + e.getEventTypes()); + } + } + } + + @ApplicationScoped + @Named("activationListener") + @LifecycleEvents + public static class ActivationListener + implements EventInterceptor + { + private boolean activated = false; + + @Inject + private javax.enterprise.event.Event lifecycleEvent; + + boolean isActivated() + { + return activated; + } + + @Override + public synchronized void onEvent(LifecycleEvent e) + { + if (e.getType() == LifecycleEvent.Type.ACTIVATING) + { + lifecycleEvent.select(Activating.Literal.INSTANCE).fire(e); + } + else if (e.getType() == LifecycleEvent.Type.ACTIVATED) + { + lifecycleEvent.select(Activated.Literal.INSTANCE).fire(e); + activated = true; + } + else if (e.getType() == LifecycleEvent.Type.DISPOSING) + { + lifecycleEvent.select(Disposing.Literal.INSTANCE).fire(e); + } + } + } + + @ApplicationScoped + @Named("storageListener") + @CacheLifecycleEvents({CREATED, DESTROYED}) + public static class StorageListener + implements EventInterceptor + { + private Set caches = new HashSet<>(); + + @Inject + private javax.enterprise.event.Event cacheLifecycleEvent; + + boolean hasCache(String cacheName) + { + return caches.contains(cacheName); + } + + @Override + public synchronized void onEvent(CacheLifecycleEvent e) + { + System.out.println(e); + + CacheName cache = CacheName.Literal.of(e.getCacheName()); + ServiceName service = ServiceName.Literal.of(e.getDispatcher().getBackingMapContext() + .getManagerContext().getCacheService().getInfo().getServiceName()); + + if (e.getType() == CREATED) + { + cacheLifecycleEvent.select(cache, service, Created.Literal.INSTANCE).fire(e); + caches.add(e.getCacheName()); + } + if (e.getType() == DESTROYED) + { + cacheLifecycleEvent.select(cache, service, Destroyed.Literal.INSTANCE).fire(e); + caches.remove(e.getCacheName()); + } + } + } + + @ApplicationScoped + @Named("cacheListener") + @EntryEvents(INSERTING) + public static class CacheListener + implements EventInterceptor> + { + @Override + public synchronized void onEvent(EntryEvent e) + { + e.getEntrySet().forEach(entry -> entry.setValue(entry.getValue().toUpperCase())); + } + } + + @ApplicationScoped + @Named("memberListener") + public static class MemberListener + implements com.tangosol.net.MemberListener + { + private volatile boolean fJoined; + + public boolean hasJoined() + { + return fJoined; + } + + @Override + public void memberJoined(MemberEvent memberEvent) + { + fJoined = true; + System.out.println(memberEvent); + } + + @Override + public void memberLeaving(MemberEvent memberEvent) + { + System.out.println(memberEvent); + } + + @Override + public void memberLeft(MemberEvent memberEvent) + { + System.out.println(memberEvent); + } + } + + @ApplicationScoped + @Named("partitionListener") + public static class PartitionListener + implements com.tangosol.net.partition.PartitionListener + { + private int id; + + public int getId() + { + return id; + } + + @Override + public void onPartitionEvent(PartitionEvent partitionEvent) + { + id = partitionEvent.getId(); + System.out.println(partitionEvent); + } + } + + @ApplicationScoped + @Named("cacheStore") + public static class CacheStore + implements com.tangosol.net.cache.CacheStore + { + private Map storeMap = new HashMap<>(); + + public Map getStoreMap() + { + return storeMap; + } + + @Override + public void store(Long key, String value) + { + storeMap.put(key, value); + } + + @Override + public void storeAll(Map map) + { + storeMap.putAll(map); + } + + @Override + public void erase(Long key) + { + storeMap.remove(key); + } + + @Override + public void eraseAll(Collection keys) + { + keys.forEach(storeMap::remove); + } + + @Override + public String load(Long key) + { + return key.toString(); + } + + @Override + public Map loadAll(Collection keys) + { + return keys.stream().collect(Collectors.toMap(k -> k, Object::toString)); + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/CdiNamespaceHandlerTest.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/CdiNamespaceHandlerTest.java new file mode 100644 index 0000000000000..ec6d9bf9343ab --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/CdiNamespaceHandlerTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; + +import com.tangosol.coherence.config.ParameterMacroExpressionParser; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.SystemPropertyParameterResolver; +import com.tangosol.config.xml.DefaultProcessingContext; +import com.tangosol.config.xml.DocumentProcessor; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Unit tests for {@link Injectable}. + * + * @author Aleks Seovic 2019.10.02 + */ +class CdiNamespaceHandlerTest + { + private static SeContainer container; + + private static ProcessingContext context; + + @BeforeAll + static void initContainer() + { + SeContainerInitializer containerInit = SeContainerInitializer.newInstance(); + container = containerInit.initialize(); + context = new DefaultProcessingContext( + new DocumentProcessor.DefaultDependencies() + .setExpressionParser(new ParameterMacroExpressionParser())); + } + + @AfterAll + static void shutdownContainer() + { + container.close(); + } + + @Test + void testSuccess() + { + Object bean = realize("beanX"); + assertThat(bean, notNullValue()); + assertThat(bean, instanceOf(BeanBuilderTest.BeanX.class)); + } + + @Test + void testFailureMissingBean() + { + assertThrows(ConfigurationException.class, () -> + realize("beanY") + ); + } + + @Test + void testFailureUndefined() + { + assertThrows(ConfigurationException.class, () -> + realize("") + ); + } + + private Object realize(String sXml) + { + XmlElement xml = XmlHelper.loadXml(sXml).getRoot(); + CdiNamespaceHandler handler = new CdiNamespaceHandler(); + ElementProcessor processor = handler.getElementProcessor(xml); + assertThat(processor, instanceOf(BeanProcessor.class)); + + BeanBuilder builder = ((BeanProcessor) processor).process(context, xml); + return builder.realize(new SystemPropertyParameterResolver(), null, null); + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/ConfigurableCacheFactoryProducerIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/ConfigurableCacheFactoryProducerIT.java new file mode 100644 index 0000000000000..98f874061045d --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/ConfigurableCacheFactoryProducerIT.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import com.tangosol.net.CacheFactoryBuilder; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.ExtensibleConfigurableCacheFactory; +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; + +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for the {@link ConfigurableCacheFactoryProducer} using the + * Weld JUnit extension. + * + * @author Jonathan Knight 2019.10.19 + */ +@ExtendWith(WeldJunit5Extension.class) +public class ConfigurableCacheFactoryProducerIT + { + + @WeldSetup + public WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(CacheFactoryUriResolver.Default.class) + .addBeanClass(ConfigurableCacheFactoryProducer.class)); + + /** + * Should inject the default CCF. + */ + @Inject + private ConfigurableCacheFactory defaultCacheFactory; + + /** + * Should inject the test CCF. + */ + @Inject + @CacheFactory("test-cache-config.xml") + private ConfigurableCacheFactory testCacheFactory; + + /** + * Should inject the the default CCF. + */ + @Inject + @CacheFactory("") + private ConfigurableCacheFactory qualifiedDefaultCacheFactory; + + /** + * Should inject the the default CCF. + */ + @Inject + @CacheFactory(" ") + private ConfigurableCacheFactory namedDefaultCacheFactory; + + /** + * Should inject cache factory builder. + */ + @Inject + private CacheFactoryBuilder builder; + + @Test + void shouldInjectDefaultConfigurableCacheFactory() + { + assertThat(defaultCacheFactory, is(notNullValue())); + assertThat(((ExtensibleConfigurableCacheFactory) defaultCacheFactory).getScopeName(), is("")); + } + + @Test + void shouldInjectTestConfigurableCacheFactory() + { + assertThat(testCacheFactory, is(notNullValue())); + assertThat(testCacheFactory, is(not(sameInstance(defaultCacheFactory)))); + assertThat(((ExtensibleConfigurableCacheFactory) testCacheFactory).getScopeName(), is("test")); + } + + @Test + void shouldInjectQualifiedDefaultConfigurableCacheFactory() + { + assertThat(qualifiedDefaultCacheFactory, is(notNullValue())); + assertThat(qualifiedDefaultCacheFactory, is(sameInstance(defaultCacheFactory))); + } + + @Test + void shouldInjectNamedDefaultConfigurableCacheFactory() + { + assertThat(namedDefaultCacheFactory, is(notNullValue())); + assertThat(namedDefaultCacheFactory, is(sameInstance(defaultCacheFactory))); + } + + @Test + void shouldGetDynamicCCF() + { + Annotation qualifier = CacheFactory.Literal.of("test-cache-config.xml"); + Instance instance = weld.select(ConfigurableCacheFactory.class, qualifier); + + assertThat(instance.isResolvable(), is(true)); + assertThat(instance.get(), is(sameInstance(testCacheFactory))); + } + + @Test + void shouldInjectCacheFactoryBuilder() + { + assertThat(builder, is(notNullValue())); + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/EventDispatcherIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/EventDispatcherIT.java new file mode 100644 index 0000000000000..8abc2401d8a24 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/EventDispatcherIT.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.time.LocalDate; +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; +import javax.inject.Inject; + +import com.oracle.coherence.cdi.data.Person; +import com.oracle.coherence.cdi.data.PhoneNumber; +import com.oracle.coherence.cdi.events.CacheName; +import com.oracle.coherence.cdi.events.Created; +import com.oracle.coherence.cdi.events.Destroyed; +import com.oracle.coherence.cdi.events.Executed; +import com.oracle.coherence.cdi.events.Executing; +import com.oracle.coherence.cdi.events.Inserted; +import com.oracle.coherence.cdi.events.Processor; +import com.oracle.coherence.cdi.events.Removed; +import com.oracle.coherence.cdi.events.Updated; + +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.NamedCache; +import com.tangosol.net.events.Event; +import com.tangosol.net.events.application.LifecycleEvent; +import com.tangosol.net.events.partition.TransactionEvent; +import com.tangosol.net.events.partition.TransferEvent; +import com.tangosol.net.events.partition.cache.CacheLifecycleEvent; +import com.tangosol.net.events.partition.cache.EntryEvent; +import com.tangosol.net.events.partition.cache.EntryProcessorEvent; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.InvocableMap; + +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for the {@link EventDispatcher} using the Weld JUnit + * extension. + * + * @author as 2020.04.03 + */ +@ExtendWith(WeldJunit5Extension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class EventDispatcherIT + { + + @WeldSetup + private WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(CacheFactoryUriResolver.Default.class) + .addBeanClass(ConfigurableCacheFactoryProducer.class) + .addBeanClass(TestObservers.class) + .addBeanClass(EventDispatcher.LifecycleEventHandler.class) + .addBeanClass(EventDispatcher.CacheLifecycleEventHandler.class) + .addBeanClass(EventDispatcher.EntryEventHandler.class) + .addBeanClass(EventDispatcher.EntryProcessorEventHandler.class) + .addBeanClass(EventDispatcher.TransactionEventHandler.class) + .addBeanClass(EventDispatcher.TransferEventHandler.class) + .addBeanClass(EventDispatcher.UnsolicitedCommitEventHandler.class) + .addBeanClass(EventDispatcher.class)); + + @Inject + @CacheFactory("cdi-events-cache-config.xml") + private ConfigurableCacheFactory ccf; + + @Inject + private TestObservers observers; + + @Test + void testEvents() + { + ccf.activate(); + + NamedCache people = ccf.ensureCache("people", null); + people.put("homer", new Person("Homer", "Simpson", LocalDate.now(), new PhoneNumber(1, "555-123-9999"))); + people.put("marge", new Person("Marge", "Simpson", LocalDate.now(), new PhoneNumber(1, "555-123-9999"))); + people.put("bart", new Person("Bart", "Simpson", LocalDate.now(), new PhoneNumber(1, "555-123-9999"))); + people.put("lisa", new Person("Lisa", "Simpson", LocalDate.now(), new PhoneNumber(1, "555-123-9999"))); + people.put("maggie", new Person("Maggie", "Simpson", LocalDate.now(), new PhoneNumber(1, "555-123-9999"))); + + people.invokeAll(new Uppercase()); + + people.clear(); + people.truncate(); + people.destroy(); + + ccf.dispose(); + + assertThat(observers.getEvents(), hasItem(LifecycleEvent.Type.ACTIVATING)); + assertThat(observers.getEvents(), hasItem(LifecycleEvent.Type.ACTIVATED)); + assertThat(observers.getEvents(), hasItem(LifecycleEvent.Type.DISPOSING)); + assertThat(observers.getEvents(), hasItem(CacheLifecycleEvent.Type.CREATED)); + assertThat(observers.getEvents(), hasItem(CacheLifecycleEvent.Type.DESTROYED)); + assertThat(observers.getEvents(), hasItem(CacheLifecycleEvent.Type.TRUNCATED)); + assertThat(observers.getEvents(), hasItem(TransferEvent.Type.ASSIGNED)); + assertThat(observers.getEvents(), hasItem(TransactionEvent.Type.COMMITTING)); + assertThat(observers.getEvents(), hasItem(TransactionEvent.Type.COMMITTED)); + assertThat(observers.getEvents(), hasItem(EntryProcessorEvent.Type.EXECUTING)); + assertThat(observers.getEvents(), hasItem(EntryProcessorEvent.Type.EXECUTED)); + assertThat(observers.getEvents(), hasItem(EntryEvent.Type.INSERTING)); + assertThat(observers.getEvents(), hasItem(EntryEvent.Type.INSERTED)); + assertThat(observers.getEvents(), hasItem(EntryEvent.Type.UPDATING)); + assertThat(observers.getEvents(), hasItem(EntryEvent.Type.UPDATED)); + assertThat(observers.getEvents(), hasItem(EntryEvent.Type.REMOVING)); + assertThat(observers.getEvents(), hasItem(EntryEvent.Type.REMOVED)); + } + + // ---- helper classes -------------------------------------------------- + + public static class Uppercase + implements InvocableMap.EntryProcessor + { + @Override + public Object process(InvocableMap.Entry entry) + { + Person p = entry.getValue(); + p.setLastName(p.getLastName().toUpperCase()); + entry.setValue(p); + return null; + } + } + + @SuppressWarnings("unchecked") + @ApplicationScoped + public static class TestObservers + { + private Set events = new TreeSet<>(Comparator.comparing(Enum::name)); + + Set getEvents() + { + return events; + } + + private void record(Event event) + { + events.add(event.getType()); + } + + private void onEvent(@Observes Event event) + { + record(event); + } + + // cache lifecycle events + private void onCreatedPeople(@Observes @Created @CacheName("people") CacheLifecycleEvent event) + { + record(event); + assertThat(event.getCacheName(), is("people")); + } + + private void onDestroyedPeople(@Observes @Destroyed @CacheName("people") CacheLifecycleEvent event) + { + record(event); + assertThat(event.getCacheName(), is("people")); + } + + private void onPersonInserted(@Observes @Inserted @CacheName("people") EntryEvent event) + { + record(event); + ((EntryEvent) event).getEntrySet().stream() + .map(BinaryEntry::getValue) + .forEach(person -> assertThat(person.getLastName(), is("Simpson"))); + } + + private void onPersonUpdated(@Observes @Updated @CacheName("people") EntryEvent event) + { + record(event); + ((EntryEvent) event).getEntrySet().stream() + .map(BinaryEntry::getValue) + .forEach(person -> assertThat(person.getLastName(), is("SIMPSON"))); + } + + private void onPersonRemoved(@Observes @Removed @CacheName("people") EntryEvent event) + { + record(event); + ((EntryEvent) event).getEntrySet().stream() + .map(BinaryEntry::getOriginalValue) + .forEach(person -> assertThat(person.getLastName(), is("SIMPSON"))); + } + + private void onExecuting(@Observes @Executing @CacheName("people") @Processor(Uppercase.class) EntryProcessorEvent event) + { + record(event); + assertThat(event.getProcessor().getClass(), is(Uppercase.class)); + assertThat(event.getEntrySet().size(), is(5)); + } + + private void onExecuted(@Observes @Executed @CacheName("people") @Processor(Uppercase.class) EntryProcessorEvent event) + { + record(event); + assertThat(event.getProcessor().getClass(), is(Uppercase.class)); + assertThat(event.getEntrySet().size(), is(0)); + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/FilterProducerIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/FilterProducerIT.java new file mode 100644 index 0000000000000..26b96748ae245 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/FilterProducerIT.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.util.Nonbinding; +import javax.inject.Inject; + +import com.tangosol.util.Filter; +import com.tangosol.util.Filters; +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.AllFilter; +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test for filter producers and annotations. + * + * @author Jonathan Knight 2019.10.24 + */ +@ExtendWith(WeldJunit5Extension.class) +class FilterProducerIT + { + + @WeldSetup + private WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(FilterProducer.class) + .addBeanClass(FilterProducer.AlwaysFilterSupplier.class) + .addBeanClass(FilterProducer.WhereFilterSupplier.class) + .addBeanClass(TestFilterFactory.class) + .addBeanClass(FilterBean.class) + .addExtension(new CoherenceExtension())); + + @Test + void shouldInjectAlwaysFilter() + { + FilterBean filterBean = weld.select(FilterBean.class).get(); + assertThat(filterBean, is(notNullValue())); + assertThat(filterBean.getAlwaysFilter(), is(instanceOf(com.tangosol.util.filter.AlwaysFilter.class))); + } + + @Test + void shouldInjectFilterFromCohQL() + { + FilterBean filterBean = weld.select(FilterBean.class).get(); + assertThat(filterBean, is(notNullValue())); + + Filter filter = filterBean.getWhereFilter(); + assertThat(filter, is(notNullValue())); + + BeanOne one = new BeanOne("foo", new BeanTwo(100)); + BeanOne two = new BeanOne("foo", new BeanTwo(200)); + + assertThat(filter.evaluate(one), is(true)); + assertThat(filter.evaluate(two), is(false)); + } + + @Test + void shouldInjectCustomFilter() + { + FilterBean filterBean = weld.select(FilterBean.class).get(); + assertThat(filterBean, is(notNullValue())); + + Filter filter = filterBean.getCustomFilter(); + assertThat(filter, is(notNullValue())); + + BeanOne one = new BeanOne("foo", new BeanTwo(100)); + BeanOne two = new BeanOne("bar", new BeanTwo(100)); + + assertThat(filter.evaluate(one), is(true)); + assertThat(filter.evaluate(two), is(false)); + } + + @Test + void shouldInjectAndFilter() + { + FilterBean filterBean = weld.select(FilterBean.class).get(); + assertThat(filterBean, is(notNullValue())); + + Filter filter = filterBean.getAndFilter(); + assertThat(filter, is(instanceOf(AllFilter.class))); + + BeanOne one = new BeanOne("bar", new BeanTwo(19)); + BeanOne two = new BeanOne("bar", new BeanTwo(100)); + BeanOne three = new BeanOne("foo", new BeanTwo(19)); + + assertThat(filter.evaluate(one), is(true)); + assertThat(filter.evaluate(two), is(false)); + assertThat(filter.evaluate(three), is(false)); + } + + // ----- helper classes ------------------------------------------------- + + @Inherited + @FilterBinding + @Documented + @Retention(RetentionPolicy.RUNTIME) + public @interface TestFilter + { + @Nonbinding String value() default "foo"; + } + @TestFilter + @ApplicationScoped + public static class TestFilterFactory + implements FilterFactory + { + @Override + public Filter create(TestFilter annotation) + { + ValueExtractor extractor = BeanOne::getField; + return Filters.equal(extractor, annotation.value()); + } + } + @ApplicationScoped + private static class FilterBean + { + @Inject + @AlwaysFilter + private Filter alwaysFilter; + + @Inject + @TestFilter + private Filter customFilter; + + @Inject + @WhereFilter("beanTwo.field = 100") + private Filter whereFilter; + + @Inject + @WhereFilter("beanTwo.field = 19") + @TestFilter("bar") + private Filter andFilter; + + public Filter getAlwaysFilter() + { + return alwaysFilter; + } + + public Filter getCustomFilter() + { + return customFilter; + } + + public Filter getWhereFilter() + { + return whereFilter; + } + + public Filter getAndFilter() + { + return andFilter; + } + } + + public class BeanOne + { + private String field; + + private BeanTwo beanTwo; + + private BeanOne(String field, BeanTwo beanTwo) + { + this.field = field; + this.beanTwo = beanTwo; + } + + public String getField() + { + return field; + } + + public BeanTwo getBeanTwo() + { + return beanTwo; + } + } + + public class BeanTwo + { + private int field; + + private BeanTwo(int field) + { + this.field = field; + } + + public int getField() + { + return field; + } + + public void setField(int field) + { + this.field = field; + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/InjectableIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/InjectableIT.java new file mode 100644 index 0000000000000..008fda01d2349 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/InjectableIT.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import com.oracle.coherence.cdi.data.Account; +import com.oracle.coherence.cdi.data.Person; +import com.oracle.coherence.cdi.data.PhoneNumber; +import com.oracle.coherence.cdi.events.CacheName; +import com.oracle.coherence.cdi.events.Created; +import com.oracle.coherence.cdi.events.Destroyed; +import com.oracle.coherence.cdi.events.Executed; +import com.oracle.coherence.cdi.events.Executing; +import com.oracle.coherence.cdi.events.Inserted; +import com.oracle.coherence.cdi.events.Processor; +import com.oracle.coherence.cdi.events.Removed; +import com.oracle.coherence.cdi.events.Updated; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.NamedCache; +import com.tangosol.net.events.Event; +import com.tangosol.net.events.application.LifecycleEvent; +import com.tangosol.net.events.partition.TransactionEvent; +import com.tangosol.net.events.partition.TransferEvent; +import com.tangosol.net.events.partition.cache.CacheLifecycleEvent; +import com.tangosol.net.events.partition.cache.EntryEvent; +import com.tangosol.net.events.partition.cache.EntryProcessorEvent; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.InvocableMap; +import java.time.LocalDate; +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for the {@link EventDispatcher} using the Weld JUnit + * extension. + * + * @author as 2020.04.03 + */ +@ExtendWith(WeldJunit5Extension.class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class InjectableIT + { + + @WeldSetup + private WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(CacheFactoryUriResolver.Default.class) + .addBeanClass(ConfigurableCacheFactoryProducer.class) + .addBeanClass(CurrencyConverter.class) + .addBeanClass(TestObservers.class) + .addBeanClass(EventDispatcher.class)); + + @Inject + @CacheFactory("injectable-cache-config.xml") + private ConfigurableCacheFactory ccf; + + @Test + void testNsfWithdrawal() + { + ccf.activate(); + + NamedCache accounts = ccf.ensureCache("accounts", null); + accounts.put("X", new Account("X", 5000L)); + + accounts.invoke("X", new CreditAccount(100L)); + assertThat(accounts.get("X").getBalance(), is(-5000L)); + } + + // ---- helper classes -------------------------------------------------- + + @Dependent + public static class CreditAccount + implements InvocableMap.EntryProcessor, Injectable + { + private long amount; + + @Inject + private CurrencyConverter currencyConverter; + + public CreditAccount() + { + } + + public CreditAccount(long amount) + { + this.amount = amount; + } + + @Override + public Long process(InvocableMap.Entry entry) + { + Account account = entry.getValue(); + account.withdraw(currencyConverter.convert(amount)); + entry.setValue(account); + return account.getBalance(); + } + } + + @ApplicationScoped + public static class CurrencyConverter + { + public long convert(long amount) + { + return amount * 100; + } + } + + @ApplicationScoped + public static class TestObservers + { + private void onAccountOverdrawn(@Observes Account.Overdrawn event) + { + System.out.println(event); + assertThat(event.getAmount(), is(5000L)); + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/InjectableTest.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/InjectableTest.java new file mode 100644 index 0000000000000..fe66eadbb8b62 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/InjectableTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.io.Serializable; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; +import javax.inject.Inject; + +import com.oracle.coherence.common.base.Converter; + +import com.tangosol.util.Binary; +import com.tangosol.util.ExternalizableHelper; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; + +/** + * Unit tests for {@link com.oracle.coherence.cdi.Injectable}. + * + * @author Aleks Seovic 2019.10.02 + */ +class InjectableTest + { + private static SeContainer container; + + @BeforeAll + static void initContainer() + { + SeContainerInitializer containerInit = SeContainerInitializer.newInstance(); + container = containerInit.initialize(); + } + + @AfterAll + static void shutdownContainer() + { + container.close(); + } + + @Test + void testInjectable() + { + InjectableBean bean = new InjectableBean("aleks"); + assertThat(bean.converter, nullValue()); + assertThat(bean.postConstructCalled, is(false)); + + Binary bin = ExternalizableHelper.toBinary(bean); + bean = ExternalizableHelper.fromBinary(bin); + assertThat(bean.converter, notNullValue()); + assertThat(bean.postConstructCalled, is(true)); + assertThat(bean.getConvertedText(), is("ALEKS")); + } + + @Dependent + public static class InjectableBean + implements Injectable, Serializable + { + @Inject + private Converter converter; + + private String text; + private boolean postConstructCalled; + + InjectableBean() + { + } + + InjectableBean(String text) + { + this.text = text; + } + + @PostConstruct + void postConstruct() + { + postConstructCalled = true; + System.out.println("Deserialized: " + text); + } + + String getConvertedText() + { + return converter.convert(text); + } + } + + @ApplicationScoped + public static class ToUpperConverter + implements Converter + { + @Override + public String convert(String s) + { + return s.toUpperCase(); + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/NamedCacheProducerIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/NamedCacheProducerIT.java new file mode 100644 index 0000000000000..35d71f6ef4cd5 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/NamedCacheProducerIT.java @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; +import java.util.List; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import com.tangosol.net.AsyncNamedCache; +import com.tangosol.net.NamedCache; +import com.tangosol.net.cache.CacheMap; +import com.tangosol.util.ConcurrentMap; +import com.tangosol.util.InvocableMap; +import com.tangosol.util.ObservableMap; +import com.tangosol.util.QueryMap; + +import org.hamcrest.Matchers; +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for the {@link com.oracle.coherence.cdi.ConfigurableCacheFactoryProducer} + * using the Weld JUnit extension. + * + * @author Jonathan Knight 2019.10.19 + */ +@ExtendWith(WeldJunit5Extension.class) +class NamedCacheProducerIT + { + + @WeldSetup + private WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(CtorBean.class) + .addBeanClass(NamedCacheFieldsBean.class) + .addBeanClass(AsyncNamedCacheFieldsBean.class) + .addBeanClass(SuperTypesBean.class) + .addBeanClass(DifferentCacheFactoryBean.class) + .addBeanClass(FilterProducer.class) + .addBeanClass(FilterProducer.AlwaysFilterSupplier.class) + .addBeanClass(FilterProducer.WhereFilterSupplier.class) + .addBeanClass(ValueExtractorProducer.class) + .addBeanClass(ValueExtractorProducer.UniversalExtractorSupplier.class) + .addBeanClass(ValueExtractorProducer.UniversalExtractorsSupplier.class) + .addBeanClass(ValueExtractorProducer.ChainedExtractorSupplier.class) + .addBeanClass(ValueExtractorProducer.ChainedExtractorsSupplier.class) + .addBeanClass(ValueExtractorProducer.PofExtractorSupplier.class) + .addBeanClass(ValueExtractorProducer.PofExtractorsSupplier.class) + .addBeanClass(NamedCacheProducer.class) + .addBeanClass(CacheFactoryUriResolver.Default.class) + .addBeanClass(ConfigurableCacheFactoryProducer.class) + .addExtension(new CoherenceExtension())); + + @Test + void shouldGetDynamicNamedCache() + { + Annotation qualifier = Cache.Literal.of("numbers"); + Instance instance = weld.select(NamedCache.class, qualifier); + + assertThat(instance.isResolvable(), is(true)); + assertThat(instance.get().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectNamedCacheUsingFieldName() + { + NamedCacheFieldsBean bean = weld.select(NamedCacheFieldsBean.class).get(); + assertThat(bean.getNumbers(), is(notNullValue())); + assertThat(bean.getNumbers().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectQualifiedNamedCache() + { + NamedCacheFieldsBean bean = weld.select(NamedCacheFieldsBean.class).get(); + assertThat(bean.getNamedCache(), is(notNullValue())); + assertThat(bean.getNamedCache().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectNamedCacheWithGenerics() + { + NamedCacheFieldsBean bean = weld.select(NamedCacheFieldsBean.class).get(); + assertThat(bean.getGenericCache(), is(notNullValue())); + assertThat(bean.getGenericCache().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectNamedCacheWithGenericKeys() + { + NamedCacheFieldsBean bean = weld.select(NamedCacheFieldsBean.class).get(); + assertThat(bean.getGenericKeys(), is(notNullValue())); + assertThat(bean.getGenericKeys().getCacheName(), is("genericKeys")); + } + + @Test + void shouldInjectNamedCacheWithGenericValues() + { + NamedCacheFieldsBean bean = weld.select(NamedCacheFieldsBean.class).get(); + assertThat(bean.getGenericValues(), is(notNullValue())); + assertThat(bean.getGenericValues().getCacheName(), is("genericValues")); + } + + @Test + void shouldInjectAsyncNamedCacheUsingFieldName() + { + AsyncNamedCacheFieldsBean bean = weld.select(AsyncNamedCacheFieldsBean.class).get(); + assertThat(bean.getNumbers(), is(notNullValue())); + assertThat(bean.getNumbers().getNamedCache().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectQualifiedAsyncNamedCache() + { + AsyncNamedCacheFieldsBean bean = weld.select(AsyncNamedCacheFieldsBean.class).get(); + assertThat(bean.getNamedCache(), is(notNullValue())); + assertThat(bean.getNamedCache().getNamedCache().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectAsyncNamedCacheWithGenerics() + { + AsyncNamedCacheFieldsBean bean = weld.select(AsyncNamedCacheFieldsBean.class).get(); + assertThat(bean.getGenericCache(), is(notNullValue())); + assertThat(bean.getGenericCache().getNamedCache().getCacheName(), is("numbers")); + } + + @Test + void shouldInjectAsyncNamedCacheWithGenericKeys() + { + AsyncNamedCacheFieldsBean bean = weld.select(AsyncNamedCacheFieldsBean.class).get(); + assertThat(bean.getGenericKeys(), is(notNullValue())); + assertThat(bean.getGenericKeys().getNamedCache().getCacheName(), is("genericKeys")); + } + + @Test + void shouldInjectAsyncNamedCacheWithGenericValues() + { + AsyncNamedCacheFieldsBean bean = weld.select(AsyncNamedCacheFieldsBean.class).get(); + assertThat(bean.getGenericValues(), is(notNullValue())); + assertThat(bean.getGenericValues().getNamedCache().getCacheName(), is("genericValues")); + } + + @Test + void shouldInjectCachesFromDifferentCacheFactories() + { + DifferentCacheFactoryBean bean = weld.select(DifferentCacheFactoryBean.class).get(); + + assertThat(bean.getDefaultCcfNumbers(), is(notNullValue())); + assertThat(bean.getDefaultCcfNumbers().getCacheName(), Matchers.is("numbers")); + assertThat(bean.getDefaultCcfAsyncNumbers(), is(notNullValue())); + assertThat(bean.getDefaultCcfAsyncNumbers().getNamedCache().getCacheName(), Matchers.is("numbers")); + assertThat(bean.getDefaultCcfAsyncNumbers().getNamedCache(), is(bean.getDefaultCcfNumbers())); + + assertThat(bean.getSpecificCcfNumbers(), is(notNullValue())); + assertThat(bean.getSpecificCcfNumbers().getCacheName(), Matchers.is("numbers")); + assertThat(bean.getSpecificCcfAsyncNumbers(), is(notNullValue())); + assertThat(bean.getSpecificCcfAsyncNumbers().getNamedCache().getCacheName(), Matchers.is("numbers")); + assertThat(bean.getSpecificCcfAsyncNumbers().getNamedCache(), is(bean.getSpecificCcfNumbers())); + + assertThat(bean.getDefaultCcfNumbers(), is(not(bean.getSpecificCcfNumbers()))); + } + + @Test + void testCtorInjection() + { + CtorBean bean = weld.select(CtorBean.class).get(); + + assertThat(bean.getNumbers(), Matchers.notNullValue()); + assertThat(bean.getNumbers().getCacheName(), Matchers.is("numbers")); + assertThat(bean.getLetters(), Matchers.notNullValue()); + assertThat(bean.getLetters().getNamedCache().getCacheName(), Matchers.is("letters")); + } + + @Test + void shouldInjectSuperTypeInvocableMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + InvocableMap map = bean.getInvocableMap(); + assertThat(map, is(notNullValue())); + assertThat(map, is(sameInstance(bean.getNamedCache()))); + } + + @Test + void shouldInjectSuperTypeObservableMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + ObservableMap map = bean.getObservableMap(); + assertThat(map, is(notNullValue())); + assertThat(map, is(sameInstance(bean.getNamedCache()))); + } + + @Test + void shouldInjectSuperTypeConcurrentMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + ConcurrentMap map = bean.getConcurrentMap(); + assertThat(map, is(notNullValue())); + assertThat(map, is(sameInstance(bean.getNamedCache()))); + } + + @Test + void shouldInjectSuperTypeQueryMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + QueryMap map = bean.getQueryMap(); + assertThat(map, is(notNullValue())); + assertThat(map, is(sameInstance(bean.getNamedCache()))); + } + + @Test + void shouldInjectSuperTypeCacheMap() + { + SuperTypesBean bean = weld.select(SuperTypesBean.class).get(); + CacheMap map = bean.getCacheMap(); + assertThat(map, is(notNullValue())); + assertThat(map, is(sameInstance(bean.getNamedCache()))); + } + + // ----- test beans ----------------------------------------------------- + + @ApplicationScoped + private static class NamedCacheFieldsBean + { + @Inject + private NamedCache numbers; + + @Inject + @Cache("numbers") + private NamedCache namedCache; + + @Inject + @Cache("numbers") + private NamedCache genericCache; + + @Inject + private NamedCache, String> genericKeys; + + @Inject + private NamedCache> genericValues; + + public NamedCache getNumbers() + { + return numbers; + } + + public NamedCache getNamedCache() + { + return namedCache; + } + + public NamedCache getGenericCache() + { + return genericCache; + } + + public NamedCache, String> getGenericKeys() + { + return genericKeys; + } + + public NamedCache> getGenericValues() + { + return genericValues; + } + } + + @ApplicationScoped + private static class AsyncNamedCacheFieldsBean + { + @Inject + private AsyncNamedCache numbers; + + @Inject + @Cache("numbers") + private AsyncNamedCache namedCache; + + @Inject + @Cache("numbers") + private AsyncNamedCache genericCache; + + @Inject + private AsyncNamedCache, String> genericKeys; + + @Inject + private AsyncNamedCache> genericValues; + + public AsyncNamedCache getNumbers() + { + return numbers; + } + + public AsyncNamedCache getNamedCache() + { + return namedCache; + } + + public AsyncNamedCache getGenericCache() + { + return genericCache; + } + + public AsyncNamedCache, String> getGenericKeys() + { + return genericKeys; + } + + public AsyncNamedCache> getGenericValues() + { + return genericValues; + } + } + + @ApplicationScoped + private static class DifferentCacheFactoryBean + { + @Inject + @Cache("numbers") + private NamedCache defaultCcfNumbers; + + @Inject + @Cache("numbers") + private AsyncNamedCache defaultCcfAsyncNumbers; + + @Inject + @Cache("numbers") + @CacheFactory("test-cache-config.xml") + private NamedCache specificCcfNumbers; + + @Inject + @Cache("numbers") + @CacheFactory("test-cache-config.xml") + private AsyncNamedCache specificCcfAsyncNumbers; + + public NamedCache getDefaultCcfNumbers() + { + return defaultCcfNumbers; + } + + public AsyncNamedCache getDefaultCcfAsyncNumbers() + { + return defaultCcfAsyncNumbers; + } + + public NamedCache getSpecificCcfNumbers() + { + return specificCcfNumbers; + } + + public AsyncNamedCache getSpecificCcfAsyncNumbers() + { + return specificCcfAsyncNumbers; + } + } + + @ApplicationScoped + private static class CtorBean + { + + private final NamedCache numbers; + + private final AsyncNamedCache letters; + + @Inject + CtorBean(@Cache("numbers") NamedCache numbers, + @Cache("letters") AsyncNamedCache letters) + { + + this.numbers = numbers; + this.letters = letters; + } + + NamedCache getNumbers() + { + return numbers; + } + + AsyncNamedCache getLetters() + { + return letters; + } + } + + @ApplicationScoped + private static class SuperTypesBean + { + @Inject + @Cache("numbers") + private NamedCache namedCache; + + @Inject + @Cache("numbers") + private InvocableMap invocableMap; + + @Inject + @Cache("numbers") + private ObservableMap observableMap; + + @Inject + @Cache("numbers") + private ConcurrentMap concurrentMap; + + @Inject + @Cache("numbers") + private QueryMap queryMap; + + @Inject + @Cache("numbers") + private CacheMap cacheMap; + + NamedCache getNamedCache() + { + return namedCache; + } + + InvocableMap getInvocableMap() + { + return invocableMap; + } + + ObservableMap getObservableMap() + { + return observableMap; + } + + ConcurrentMap getConcurrentMap() + { + return concurrentMap; + } + + QueryMap getQueryMap() + { + return queryMap; + } + + CacheMap getCacheMap() + { + return cacheMap; + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/NamedTopicProducerIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/NamedTopicProducerIT.java new file mode 100644 index 0000000000000..0846fcfc09897 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/NamedTopicProducerIT.java @@ -0,0 +1,462 @@ +/* + * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.context.control.RequestContextController; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import com.tangosol.net.topic.NamedTopic; + +import com.tangosol.net.topic.Publisher; +import com.tangosol.net.topic.Subscriber; +import org.hamcrest.Matchers; + +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for the {@link NamedTopicProducer} using the Weld JUnit + * extension. + * + * @author Jonathan Knight 2019.10.23 + */ +@ExtendWith(WeldJunit5Extension.class) +class NamedTopicProducerIT + { + + @WeldSetup + private WeldInitiator weld = WeldInitiator.from(RequestScopedSubscribers.class, + CtorBean.class, + NamedTopicFieldsBean.class, + NamedTopicPublisherFieldsBean.class, + NamedTopicSubscriberFieldsBean.class, + DifferentCacheFactoryBean.class, + RequestScopedPublishers.class, + RequestScopedSubscribers.class, + NamedTopicProducer.class, + CacheFactoryUriResolver.Default.class, + ConfigurableCacheFactoryProducer.class) + .build(); + + @Inject + private RequestContextController contextController; + + @Test + void shouldGetDynamicNamedTopic() + { + Annotation qualifier = Topic.Literal.of("numbers"); + Instance instance = weld.select(NamedTopic.class, qualifier); + + assertThat(instance.isResolvable(), is(true)); + assertThat(instance.get().getName(), is("numbers")); + } + + @Test + void shouldInjectNamedTopicUsingFieldName() + { + NamedTopicFieldsBean bean = weld.select(NamedTopicFieldsBean.class).get(); + assertThat(bean.getNumbers(), is(notNullValue())); + assertThat(bean.getNumbers().getName(), is("numbers")); + } + + @Test + void shouldInjectQualifiedNamedTopic() + { + NamedTopicFieldsBean bean = weld.select(NamedTopicFieldsBean.class).get(); + assertThat(bean.getNamedTopic(), is(notNullValue())); + assertThat(bean.getNamedTopic().getName(), is("numbers")); + } + + @Test + void shouldInjectNamedTopicWithGenerics() + { + NamedTopicFieldsBean bean = weld.select(NamedTopicFieldsBean.class).get(); + assertThat(bean.getGenericTopic(), is(notNullValue())); + assertThat(bean.getGenericTopic().getName(), is("numbers")); + } + + @Test + void shouldInjectNamedTopicWithGenericValues() + { + NamedTopicFieldsBean bean = weld.select(NamedTopicFieldsBean.class).get(); + assertThat(bean.getGenericValues(), is(notNullValue())); + assertThat(bean.getGenericValues().getName(), is("genericValues")); + } + + @Test + void shouldInjectTopicsFromDifferentCacheFactories() throws Exception + { + DifferentCacheFactoryBean bean = weld.select(DifferentCacheFactoryBean.class).get(); + + NamedTopic defaultTopic = bean.getDefaultCcfNumbers(); + NamedTopic specificTopic = bean.getSpecificCcfNumbers(); + + assertThat(defaultTopic, is(notNullValue())); + assertThat(defaultTopic.getName(), Matchers.is("numbers")); + + assertThat(specificTopic, is(notNullValue())); + assertThat(specificTopic.getName(), Matchers.is("numbers")); + + assertThat(defaultTopic, is(not(sameInstance(specificTopic)))); + + Subscriber defaultSubscriber = bean.getDefaultSubscriber(); + CompletableFuture> defaultFuture = defaultSubscriber.receive(); + Subscriber specificSubscriber = bean.getSpecificSubscriber(); + CompletableFuture> specificFuture = specificSubscriber.receive(); + + bean.getDefaultPublisher().send("value-one"); + bean.getSpecificPublisher().send("value-two"); + + Subscriber.Element valueOne = defaultFuture.get(1, TimeUnit.MINUTES); + Subscriber.Element valueTwo = specificFuture.get(1, TimeUnit.MINUTES); + + assertThat(valueOne.getValue(), is("value-one")); + assertThat(valueTwo.getValue(), is("value-two")); + } + + @Test + void testCtorInjection() + { + CtorBean bean = weld.select(CtorBean.class).get(); + + assertThat(bean.getNumbers(), Matchers.notNullValue()); + assertThat(bean.getNumbers().getName(), Matchers.is("numbers")); + } + + @Test + void shouldCloseSubscriberOnScopeDeactivation() + { + // Activate RequestScope + boolean activated = contextController.activate(); + assertThat(activated, is(true)); + + // Obtain a RequestScoped bean that consequently has a RequestScoped Subscriber + RequestScopedSubscribers bean = weld.select(RequestScopedSubscribers.class).get(); + Subscriber subscriber = bean.getSubscriber(); + + assertThat(subscriber.isActive(), is(true)); + + // Deactivate the RequestScope + contextController.deactivate(); + + assertThat(subscriber.isActive(), is(false)); + } + + @Test + void shouldCloseQualifiedSubscriberOnScopeDeactivation() + { + // Activate RequestScope + boolean activated = contextController.activate(); + assertThat(activated, is(true)); + + // Obtain a RequestScoped bean that consequently has a RequestScoped Subscriber + RequestScopedSubscribers bean = weld.select(RequestScopedSubscribers.class).get(); + Subscriber subscriber = bean.getQualifiedSubscriber(); + + assertThat(subscriber.isActive(), is(true)); + + // Deactivate the RequestScope + contextController.deactivate(); + + assertThat(subscriber.isActive(), is(false)); + } + + @Test + void shouldClosePublisherOnScopeDeactivation() + { + // Activate RequestScope + boolean activated = contextController.activate(); + assertThat(activated, is(true)); + + // Obtain a RequestScoped bean that consequently has a RequestScoped Publisher + RequestScopedPublishers bean = weld.select(RequestScopedPublishers.class).get(); + AtomicBoolean isClosed = new AtomicBoolean(false); + Publisher publisher = bean.getPublisher(); + + publisher.onClose(() -> isClosed.set(true)); + + // Deactivate the RequestScope + contextController.deactivate(); + + assertThat(isClosed.get(), is(true)); + } + + @Test + void shouldCloseQualifiedPublisherOnScopeDeactivation() + { + // Activate RequestScope + boolean activated = contextController.activate(); + assertThat(activated, is(true)); + + // Obtain a RequestScoped bean that consequently has a RequestScoped Publisher + RequestScopedPublishers bean = weld.select(RequestScopedPublishers.class).get(); + AtomicBoolean isClosed = new AtomicBoolean(false); + Publisher publisher = bean.getQualifiedPublisher(); + + publisher.onClose(() -> isClosed.set(true)); + + // Deactivate the RequestScope + contextController.deactivate(); + + assertThat(isClosed.get(), is(true)); + } + + // ----- test beans ----------------------------------------------------- + + @RequestScoped + private static class RequestScopedSubscribers + { + @Inject + private Subscriber numbers; + + @Inject + @Topic("numbers") + private Subscriber qualifiedSubscriber; + + Subscriber getSubscriber() + { + return numbers; + } + + Subscriber getQualifiedSubscriber() + { + return qualifiedSubscriber; + } + } + + @RequestScoped + private static class RequestScopedPublishers + { + @Inject + private Publisher numbers; + + @Inject + @Topic("numbers") + private Publisher qualifiedPublisher; + + Publisher getPublisher() + { + return numbers; + } + + Publisher getQualifiedPublisher() + { + return qualifiedPublisher; + } + } + + @ApplicationScoped + private static class NamedTopicFieldsBean + { + @Inject + private NamedTopic numbers; + + @Inject + @Topic("numbers") + private NamedTopic namedTopic; + + @Inject + @Topic("numbers") + private NamedTopic genericTopic; + + @Inject + private NamedTopic> genericValues; + + public NamedTopic getNumbers() + { + return numbers; + } + + public NamedTopic getNamedTopic() + { + return namedTopic; + } + + public NamedTopic getGenericTopic() + { + return genericTopic; + } + + public NamedTopic> getGenericValues() + { + return genericValues; + } + } + + @ApplicationScoped + private static class NamedTopicPublisherFieldsBean + { + @Inject + private Publisher numbers; + + @Inject + @Topic("numbers") + private Publisher namedTopic; + + @Inject + @Topic("numbers") + private Publisher genericTopic; + + @Inject + private Publisher> genericValues; + + public Publisher getNumbers() + { + return numbers; + } + + public Publisher getPublisher() + { + return namedTopic; + } + + public Publisher getGenericPublisher() + { + return genericTopic; + } + + public Publisher> getGenericValuesPublisher() + { + return genericValues; + } + } + + @ApplicationScoped + private static class NamedTopicSubscriberFieldsBean + { + @Inject + private Subscriber numbers; + + @Inject + @Topic("numbers") + private Subscriber namedTopic; + + @Inject + @Topic("numbers") + private Subscriber genericTopic; + + @Inject + private Subscriber> genericValues; + + public Subscriber getNumbers() + { + return numbers; + } + + public Subscriber getSubscriber() + { + return namedTopic; + } + + public Subscriber getGenericSubscriber() + { + return genericTopic; + } + + public Subscriber> getGenericValuesSubscriber() + { + return genericValues; + } + } + + @ApplicationScoped + private static class DifferentCacheFactoryBean + { + @Inject + @Topic("numbers") + private NamedTopic defaultCcfNumbers; + + @Inject + @Topic("numbers") + private Publisher defaultPublisher; + + @Inject + @Topic("numbers") + private Subscriber defaultSubscriber; + + @Inject + @Topic("numbers") + @CacheFactory("test-cache-config.xml") + private NamedTopic specificCcfNumbers; + + @Inject + @Topic("numbers") + @CacheFactory("test-cache-config.xml") + private Publisher specificPublisher; + + @Inject + @Topic("numbers") + @CacheFactory("test-cache-config.xml") + private Subscriber specificSubscriber; + + public NamedTopic getDefaultCcfNumbers() + { + return defaultCcfNumbers; + } + + public NamedTopic getSpecificCcfNumbers() + { + return specificCcfNumbers; + } + + public Publisher getDefaultPublisher() + { + return defaultPublisher; + } + + public Subscriber getDefaultSubscriber() + { + return defaultSubscriber; + } + + public Publisher getSpecificPublisher() + { + return specificPublisher; + } + + public Subscriber getSpecificSubscriber() + { + return specificSubscriber; + } + } + + @ApplicationScoped + private static class CtorBean + { + private final NamedTopic numbers; + + @Inject + CtorBean(@Topic("numbers") NamedTopic topic) + { + this.numbers = topic; + } + + NamedTopic getNumbers() + { + return numbers; + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/SerializerProducerIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/SerializerProducerIT.java new file mode 100644 index 0000000000000..b4a83aaf94060 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/SerializerProducerIT.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.inject.Named; + +import com.tangosol.io.DefaultSerializer; +import com.tangosol.io.Serializer; +import com.tangosol.io.WriteBuffer; +import com.tangosol.io.pof.ConfigurablePofContext; + +import org.hamcrest.Matchers; +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for the {@link ConfigurableCacheFactoryProducer} using the + * Weld JUnit extension. + * + * @author jk 2019.10.19 + */ +@ExtendWith(WeldJunit5Extension.class) +class SerializerProducerIT + { + + @WeldSetup + private WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(CustomSerializer.class) + .addBeanClass(CtorBean.class) + .addBeanClass(SerializerFieldsBean.class) + .addBeanClass(SerializerProducer.class) + .addBeanClass(ClusterProducer.class)); + + @Test + void shouldGetDynamicSerializer() + { + Annotation qualifier = SerializerFormat.Literal.of("java"); + Instance instance = weld.select(Serializer.class, qualifier); + + assertThat(instance.isResolvable(), is(true)); + assertThat(instance.get(), is(instanceOf(DefaultSerializer.class))); + } + + @Test + void shouldInjectDefaultSerializerUsingFieldName() + { + SerializerFieldsBean bean = weld.select(SerializerFieldsBean.class).get(); + assertThat(bean.getDefaultSerializer(), is(notNullValue())); + assertThat(bean.getDefaultSerializer(), is(instanceOf(DefaultSerializer.class))); + } + + @Test + void shouldInjectJavaSerializerUsingFieldName() + { + SerializerFieldsBean bean = weld.select(SerializerFieldsBean.class).get(); + assertThat(bean.getJava(), is(notNullValue())); + assertThat(bean.getJava(), is(instanceOf(DefaultSerializer.class))); + } + + @Test + void shouldInjectPofSerializerUsingFieldName() + { + SerializerFieldsBean bean = weld.select(SerializerFieldsBean.class).get(); + assertThat(bean.getPof(), is(notNullValue())); + assertThat(bean.getPof(), is(instanceOf(ConfigurablePofContext.class))); + } + + @Test + void shouldInjectCustomSerializerUsingFieldName() + { + SerializerFieldsBean bean = weld.select(SerializerFieldsBean.class).get(); + assertThat(bean.getCustom(), is(notNullValue())); + assertThat(bean.getCustom(), is(instanceOf(CustomSerializer.class))); + } + + @Test + void testCtorInjection() + { + CtorBean bean = weld.select(CtorBean.class).get(); + + assertThat(bean.getSerializer(), Matchers.notNullValue()); + assertThat(bean.getSerializer(), Matchers.is(instanceOf(ConfigurablePofContext.class))); + } + + // ----- test beans ----------------------------------------------------- + + @ApplicationScoped + private static class SerializerFieldsBean + { + @Inject + @SerializerFormat("") + private Serializer defaultSerializer; + + @Inject + @SerializerFormat("java") + private Serializer java; + + @Inject + @SerializerFormat("pof") + private Serializer pof; + + @Inject + @SerializerFormat("custom") + private Serializer custom; + + Serializer getDefaultSerializer() + { + return defaultSerializer; + } + + Serializer getJava() + { + return java; + } + + Serializer getPof() + { + return pof; + } + + Serializer getCustom() + { + return custom; + } + } + + @ApplicationScoped + static class CtorBean + { + private final Serializer serializer; + + @Inject + CtorBean(@SerializerFormat("pof") Serializer serializer) + { + this.serializer = serializer; + } + + public Serializer getSerializer() + { + return serializer; + } + } + + @Named("custom") + @ApplicationScoped + static class CustomSerializer + implements Serializer + { + @Override + public void serialize(WriteBuffer.BufferOutput out, Object o) + { + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/SessionProducerIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/SessionProducerIT.java new file mode 100644 index 0000000000000..8b15b9f5f8afc --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/SessionProducerIT.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import com.tangosol.net.Session; + +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Integration test for the {@link com.oracle.coherence.cdi.SessionProducer} + * using the Weld JUnit extension. + * + * @author jk 2019.10.19 + */ +@ExtendWith(WeldJunit5Extension.class) +class SessionProducerIT + { + + @WeldSetup + private WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(CacheFactoryUriResolver.Default.class) + .addBeanClass(SessionProducer.class)); + + /** + * Should inject the default Session. + */ + @Inject + private Session defaultSession; + + /** + * Should inject the test Session. + */ + @Inject + @CacheFactory("test-cache-config.xml") + private Session testSession; + + /** + * Should inject the the default Session. + */ + @Inject + @CacheFactory("") + private Session qualifiedDefaultSession; + + /** + * Should inject the the default Session. + */ + @Inject + @CacheFactory(" ") + private Session namedDefaultSession; + + @Test + void shouldInjectDefaultSession() + { + assertThat(defaultSession, is(notNullValue())); + } + + @Test + void shouldInjectTestSession() + { + assertThat(testSession, is(notNullValue())); + } + + @Test + void shouldInjectQualifiedDefaultSession() + { + assertThat(qualifiedDefaultSession, is(notNullValue())); + assertThat(qualifiedDefaultSession, is(sameInstance(defaultSession))); + } + + @Test + void shouldInjectNamedDefaultSession() + { + assertThat(namedDefaultSession, is(notNullValue())); + assertThat(namedDefaultSession, is(sameInstance(defaultSession))); + } + + @Test + void shouldGetDynamicCCF() + { + Annotation qualifier = CacheFactory.Literal.of("test-cache-config.xml"); + Instance instance = weld.select(Session.class, qualifier); + + assertThat(instance.isResolvable(), is(true)); + assertThat(instance.get(), is(sameInstance(testSession))); + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/SessionProducerTest.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/SessionProducerTest.java new file mode 100644 index 0000000000000..f3a30182fcd1c --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/SessionProducerTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Member; +import java.util.Collections; +import java.util.Set; + +import javax.enterprise.inject.spi.InjectionPoint; + +import com.oracle.coherence.common.util.Options; + +import com.tangosol.net.CacheFactoryBuilder; +import com.tangosol.net.Session; +import com.tangosol.net.options.WithClassLoader; +import com.tangosol.net.options.WithConfiguration; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @author Jonathan Knight 2019.11.06 + */ +@SuppressWarnings("unchecked") +class SessionProducerTest + { + + @Test + void shouldProduceDefaultSession() + { + CacheFactoryBuilder builder = mock(CacheFactoryBuilder.class); + CacheFactoryUriResolver resolver = mock(CacheFactoryUriResolver.class); + Session session = mock(Session.class); + InjectionPoint injectionPoint = mock(InjectionPoint.class); + Member member = mock(Member.class); + Class cls = getClass(); + + when(injectionPoint.getMember()).thenReturn(member); + when(member.getDeclaringClass()).thenReturn(cls); + when(resolver.resolve(anyString())).thenReturn("foo"); + when(builder.createSession(any(Session.Option.class))).thenReturn(session); + + SessionProducer producer = new SessionProducer(resolver, builder); + Session result = producer.getDefaultSession(injectionPoint); + assertThat(result, is(sameInstance(session))); + + ArgumentCaptor args = ArgumentCaptor.forClass(Session.Option.class); + verify(builder).createSession(args.capture()); + + Options options = Options.from(Session.Option.class, args.getAllValues().toArray(new Session.Option[0])); + WithConfiguration withConfiguration = options.get(WithConfiguration.class); + WithClassLoader withClassLoader = options.get(WithClassLoader.class); + + assertThat(withConfiguration, is(notNullValue())); + assertThat(withConfiguration.getLocation(), is(WithConfiguration.autoDetect().getLocation())); + assertThat(withClassLoader, is(notNullValue())); + assertThat(withClassLoader.getClassLoader(), is(sameInstance(cls.getClassLoader()))); + } + + @Test + void shouldProduceNamedSession() + { + CacheFactoryBuilder builder = mock(CacheFactoryBuilder.class); + CacheFactoryUriResolver resolver = mock(CacheFactoryUriResolver.class); + Session session = mock(Session.class); + InjectionPoint injectionPoint = mock(InjectionPoint.class); + Member member = mock(Member.class); + Class cls = getClass(); + Set qualifiers = Collections.singleton(CacheFactory.Literal.of("foo")); + + when(injectionPoint.getMember()).thenReturn(member); + when(injectionPoint.getQualifiers()).thenReturn(qualifiers); + when(member.getDeclaringClass()).thenReturn(cls); + when(resolver.resolve(anyString())).thenReturn("bar"); + when(builder.createSession(any(Session.Option.class))).thenReturn(session); + + SessionProducer producer = new SessionProducer(resolver, builder); + Session result = producer.getDefaultSession(injectionPoint); + assertThat(result, is(sameInstance(session))); + + ArgumentCaptor args = ArgumentCaptor.forClass(Session.Option.class); + verify(builder).createSession(args.capture()); + verify(resolver).resolve("foo"); + + Options options = Options.from(Session.Option.class, args.getAllValues().toArray(new Session.Option[0])); + WithConfiguration withConfiguration = options.get(WithConfiguration.class); + WithClassLoader withClassLoader = options.get(WithClassLoader.class); + + assertThat(withConfiguration, is(notNullValue())); + assertThat(withConfiguration.getLocation(), is("bar")); + assertThat(withClassLoader, is(notNullValue())); + assertThat(withClassLoader.getClassLoader(), is(sameInstance(cls.getClassLoader()))); + } + + @Test + void shouldProduceSameSessionWithSameQualifiers() + { + CacheFactoryBuilder builder = mock(CacheFactoryBuilder.class); + CacheFactoryUriResolver resolver = mock(CacheFactoryUriResolver.class); + Session session = mock(Session.class); + Class cls = getClass(); + InjectionPoint injectionPointOne = mock(InjectionPoint.class); + Member memberOne = mock(Member.class); + Set qualifiersOne = Collections.singleton(CacheFactory.Literal.of("foo")); + InjectionPoint injectionPointTwo = mock(InjectionPoint.class); + Member memberTwo = mock(Member.class); + Set qualifiersTwo = Collections.singleton(CacheFactory.Literal.of("foo")); + + when(injectionPointOne.getMember()).thenReturn(memberOne); + when(injectionPointOne.getQualifiers()).thenReturn(qualifiersOne); + when(memberOne.getDeclaringClass()).thenReturn(cls); + when(injectionPointTwo.getMember()).thenReturn(memberTwo); + when(injectionPointTwo.getQualifiers()).thenReturn(qualifiersTwo); + when(memberTwo.getDeclaringClass()).thenReturn(cls); + when(resolver.resolve(anyString())).thenReturn("bar"); + when(builder.createSession(any(Session.Option.class))).thenReturn(session); + + SessionProducer producer = new SessionProducer(resolver, builder); + + Session resultOne = producer.getDefaultSession(injectionPointOne); + Session resultTwo = producer.getDefaultSession(injectionPointTwo); + assertThat(resultOne, is(sameInstance(session))); + assertThat(resultTwo, is(sameInstance(session))); + + ArgumentCaptor args = ArgumentCaptor.forClass(Session.Option.class); + verify(builder, times(1)).createSession(args.capture()); + } + + @Test + void shouldProduceDifferentSessionWithSameQualifiers() + { + CacheFactoryBuilder builder = mock(CacheFactoryBuilder.class); + CacheFactoryUriResolver resolver = mock(CacheFactoryUriResolver.class); + Session sessionOne = mock(Session.class); + Session sessionTwo = mock(Session.class); + Class cls = getClass(); + InjectionPoint injectionPointOne = mock(InjectionPoint.class); + Member memberOne = mock(Member.class); + Set qualifiersOne = Collections.singleton(CacheFactory.Literal.of("foo")); + InjectionPoint injectionPointTwo = mock(InjectionPoint.class); + Member memberTwo = mock(Member.class); + Set qualifiersTwo = Collections.singleton(CacheFactory.Literal.of("bar")); + + when(injectionPointOne.getMember()).thenReturn(memberOne); + when(injectionPointOne.getQualifiers()).thenReturn(qualifiersOne); + when(memberOne.getDeclaringClass()).thenReturn(cls); + when(injectionPointTwo.getMember()).thenReturn(memberTwo); + when(injectionPointTwo.getQualifiers()).thenReturn(qualifiersTwo); + when(memberTwo.getDeclaringClass()).thenReturn(cls); + when(resolver.resolve("foo")).thenReturn("foo-uri"); + when(resolver.resolve("bar")).thenReturn("bar-uri"); + when(builder.createSession(any(Session.Option.class))).thenReturn(sessionOne, sessionTwo); + + SessionProducer producer = new SessionProducer(resolver, builder); + + Session resultOne = producer.getDefaultSession(injectionPointOne); + Session resultTwo = producer.getDefaultSession(injectionPointTwo); + assertThat(resultOne, is(sameInstance(sessionOne))); + assertThat(resultTwo, is(sameInstance(sessionTwo))); + + ArgumentCaptor args = ArgumentCaptor.forClass(Session.Option.class); + verify(builder, times(2)).createSession(args.capture()); + } + + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/ValueExtractorProducerIT.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/ValueExtractorProducerIT.java new file mode 100644 index 0000000000000..f3794160a4a33 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/ValueExtractorProducerIT.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.lang.annotation.Documented; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import com.oracle.coherence.cdi.data.Person; +import com.oracle.coherence.cdi.data.PhoneNumber; + +import com.tangosol.io.Serializer; +import com.tangosol.io.pof.ConfigurablePofContext; +import com.tangosol.net.BackingMapContext; +import com.tangosol.net.cache.BackingMapBinaryEntry; +import com.tangosol.util.Binary; +import com.tangosol.util.ExternalizableHelper; +import com.tangosol.util.InvocableMapHelper; +import com.tangosol.util.MapIndex; +import com.tangosol.util.ValueExtractor; + +import org.jboss.weld.junit5.WeldInitiator; +import org.jboss.weld.junit5.WeldJunit5Extension; +import org.jboss.weld.junit5.WeldSetup; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test for filter producers and annotations. + * + * @author Jonathan Knight 2019.10.24 + */ +@SuppressWarnings("unchecked") +@ExtendWith(WeldJunit5Extension.class) +class ValueExtractorProducerIT + { + + private ConfigurablePofContext pofContext = new ConfigurablePofContext("test-pof-config.xml"); + + private Person person; + + private PhoneNumber phoneNumber; + + private Binary binaryKey; + + private Binary binaryPerson; + + private Map.Entry entry; + + @WeldSetup + private WeldInitiator weld = WeldInitiator.of(WeldInitiator.createWeld() + .addBeanClass(ValueExtractorProducer.class) + .addBeanClass(ValueExtractorProducer.UniversalExtractorSupplier.class) + .addBeanClass(ValueExtractorProducer.UniversalExtractorsSupplier.class) + .addBeanClass(ValueExtractorProducer.ChainedExtractorSupplier.class) + .addBeanClass(ValueExtractorProducer.ChainedExtractorsSupplier.class) + .addBeanClass(ValueExtractorProducer.PofExtractorSupplier.class) + .addBeanClass(ValueExtractorProducer.PofExtractorsSupplier.class) + .addBeanClass(TestExtractorFactory.class) + .addBeanClass(ExtractorBean.class) + .addExtension(new CoherenceExtension())); + + @BeforeEach + void setup() + { + phoneNumber = new PhoneNumber(44, "04242424242"); + person = new Person("Arthur", "Dent", + LocalDate.of(1978, 3, 8), + phoneNumber); + + binaryKey = ExternalizableHelper.toBinary("AD", pofContext); + binaryPerson = ExternalizableHelper.toBinary(person, pofContext); + + BackingMapContext ctx = mock(BackingMapContext.class); + Map index = new HashMap<>(); + + when(ctx.getIndexMap()).thenReturn(index); + + entry = new BackingMapBinaryEntry(binaryKey, binaryPerson, binaryPerson, null) + { + @Override + public Object getKey() + { + return "AD"; + } + + @Override + public Object getValue() + { + return person; + } + + @Override + public BackingMapContext getBackingMapContext() + { + return ctx; + } + + @Override + public Serializer getSerializer() + { + return pofContext; + } + }; + } + + @Test + void shouldInjectPropertyExtractor() + { + ExtractorBean bean = weld.select(ExtractorBean.class).get(); + assertThat(bean, is(notNullValue())); + assertThat(bean.getFirstNameExtractor(), is(notNullValue())); + + String value = InvocableMapHelper.extractFromEntry(bean.getFirstNameExtractor(), entry); + assertThat(value, is(person.getFirstName())); + } + + @Test + void shouldInjectMultiPropertyExtractor() + { + ExtractorBean bean = weld.select(ExtractorBean.class).get(); + assertThat(bean, is(notNullValue())); + assertThat(bean.getMultiPropertyExtractor(), is(notNullValue())); + + List value = InvocableMapHelper.extractFromEntry(bean.getMultiPropertyExtractor(), entry); + assertThat(value, contains(person.getFirstName(), person.getLastName())); + } + + @Test + void shouldInjectChainedExtractor() + { + ExtractorBean bean = weld.select(ExtractorBean.class).get(); + assertThat(bean, is(notNullValue())); + assertThat(bean.getChainedExtractor(), is(notNullValue())); + + String value = InvocableMapHelper.extractFromEntry(bean.getChainedExtractor(), entry); + assertThat(value, is(person.getPhoneNumber().getNumber())); + } + + @Test + void shouldInjectMultiChainedExtractor() + { + ExtractorBean bean = weld.select(ExtractorBean.class).get(); + assertThat(bean, is(notNullValue())); + assertThat(bean.getMultiChainedExtractor(), is(notNullValue())); + + List value = InvocableMapHelper.extractFromEntry(bean.getMultiChainedExtractor(), entry); + assertThat(value, contains(phoneNumber.getCountryCode(), phoneNumber.getNumber())); + } + + @Test + void shouldInjectCustomExtractor() + { + ExtractorBean bean = weld.select(ExtractorBean.class).get(); + assertThat(bean, is(notNullValue())); + assertThat(bean.getCustomExtractor(), is(notNullValue())); + + String value = InvocableMapHelper.extractFromEntry(bean.getCustomExtractor(), entry); + assertThat(value, is(person.getLastName())); + } + + @Test + void shouldInjectPofExtractor() + { + ExtractorBean bean = weld.select(ExtractorBean.class).get(); + assertThat(bean, is(notNullValue())); + assertThat(bean.getPofExtractor(), is(notNullValue())); + + Integer value = InvocableMapHelper.extractFromEntry(bean.getPofExtractor(), entry); + assertThat(value, is(person.getPhoneNumber().getCountryCode())); + } + + @Test + void shouldInjectMultiPofExtractor() + { + ExtractorBean bean = weld.select(ExtractorBean.class).get(); + assertThat(bean, is(notNullValue())); + assertThat(bean.getMultiPofExtractor(), is(notNullValue())); + + List value = InvocableMapHelper.extractFromEntry(bean.getMultiPofExtractor(), entry); + assertThat(value, contains(phoneNumber.getCountryCode(), phoneNumber.getNumber())); + } + + @Test + void shouldInjectMultiExtractor() + { + ExtractorBean bean = weld.select(ExtractorBean.class).get(); + assertThat(bean, is(notNullValue())); + assertThat(bean.getMultiExtractor(), is(notNullValue())); + + List value = InvocableMapHelper.extractFromEntry(bean.getMultiExtractor(), entry); + assertThat(value, contains(person.getLastName(), + person.getFirstName(), + person.getPhoneNumber().getNumber())); + } + + // ----- helper classes ------------------------------------------------- + + @Inherited + @ValueExtractorBinding + @Documented + @Retention(RetentionPolicy.RUNTIME) + public @interface TestExtractor + { + } + @TestExtractor + @ApplicationScoped + public static class TestExtractorFactory + implements ValueExtractorFactory + { + @Override + public ValueExtractor create(TestExtractor annotation) + { + return Person::getLastName; + } + } + @ApplicationScoped + private static class ExtractorBean + { + @Inject + @PropertyExtractor("firstName") + private ValueExtractor firstNameExtractor; + + @Inject + @ChainedExtractor({"phoneNumber", "number"}) + private ValueExtractor chainedExtractor; + + @Inject + @TestExtractor + private ValueExtractor customExtractor; + + @Inject + @PofExtractor({3, 0}) + private ValueExtractor pofExtractor; + + @Inject + @TestExtractor + @PropertyExtractor("firstName") + @ChainedExtractor({"phoneNumber", "number"}) + private ValueExtractor> multiExtractor; + + @Inject + @PropertyExtractor("firstName") + @PropertyExtractor("lastName") + private ValueExtractor> multiPropertyExtractor; + + @Inject + @ChainedExtractor({"phoneNumber", "countryCode"}) + @ChainedExtractor({"phoneNumber", "number"}) + private ValueExtractor> multiChainedExtractor; + + @Inject + @PofExtractor({3, 0}) + @PofExtractor({3, 1}) + private ValueExtractor> multiPofExtractor; + + public ValueExtractor getFirstNameExtractor() + { + return firstNameExtractor; + } + + public ValueExtractor getChainedExtractor() + { + return chainedExtractor; + } + + public ValueExtractor getCustomExtractor() + { + return customExtractor; + } + + public ValueExtractor getPofExtractor() + { + return pofExtractor; + } + + public ValueExtractor> getMultiExtractor() + { + return multiExtractor; + } + + public ValueExtractor> getMultiPropertyExtractor() + { + return multiPropertyExtractor; + } + + public ValueExtractor> getMultiChainedExtractor() + { + return multiChainedExtractor; + } + + public ValueExtractor> getMultiPofExtractor() + { + return multiPofExtractor; + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/ValueExtractorProducerTest.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/ValueExtractorProducerTest.java new file mode 100644 index 0000000000000..fab0859bf86c1 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/ValueExtractorProducerTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Map; + +import com.oracle.coherence.cdi.data.Person; +import com.oracle.coherence.cdi.data.PhoneNumber; + +import com.tangosol.io.Serializer; +import com.tangosol.io.pof.ConfigurablePofContext; +import com.tangosol.net.BackingMapContext; +import com.tangosol.net.cache.BackingMapBinaryEntry; +import com.tangosol.util.Binary; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.ExternalizableHelper; +import com.tangosol.util.InvocableMapHelper; +import com.tangosol.util.MapIndex; +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.extractor.MultiExtractor; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@SuppressWarnings("unchecked") +class ValueExtractorProducerTest + { + + private static ConfigurablePofContext pofContext = new ConfigurablePofContext("test-pof-config.xml"); + + private static Person person; + + private static PhoneNumber phoneNumber; + + private static Binary binaryKey; + + private static Binary binaryPerson; + + private static Map.Entry entry; + + @BeforeAll + static void setup() + { + phoneNumber = new PhoneNumber(44, "04242424242"); + person = new Person("Arthur", "Dent", + LocalDate.of(1978, 3, 8), + phoneNumber); + + binaryKey = ExternalizableHelper.toBinary("AD", pofContext); + binaryPerson = ExternalizableHelper.toBinary(person, pofContext); + + BackingMapContext ctx = mock(BackingMapContext.class); + Map index = new HashMap<>(); + + when(ctx.getIndexMap()).thenReturn(index); + + entry = new BackingMapBinaryEntry(binaryKey, binaryPerson, binaryPerson, null) + { + @Override + public Object getKey() + { + return "AD"; + } + + @Override + public Object getValue() + { + return person; + } + + @Override + public BackingMapContext getBackingMapContext() + { + return ctx; + } + + @Override + public Serializer getSerializer() + { + return pofContext; + } + }; + } + + @Test + void shouldCreatePropertyExtractor() + { + PropertyExtractor annotation = PropertyExtractor.Literal.of("firstName"); + ValueExtractorProducer.UniversalExtractorSupplier supplier = new ValueExtractorProducer.UniversalExtractorSupplier(); + ValueExtractor extractor = supplier.create(annotation); + + assertThat(extractor, is(notNullValue())); + assertThat(extractor.extract(person), is(person.getFirstName())); + } + + @Test + void shouldCreateMultiPropertyExtractor() + { + PropertyExtractor annotation1 = PropertyExtractor.Literal.of("firstName"); + PropertyExtractor annotation2 = PropertyExtractor.Literal.of("lastName"); + PropertyExtractor.Extractors annotation = PropertyExtractor.Extractors.Literal.of(annotation1, annotation2); + ValueExtractorProducer.UniversalExtractorsSupplier supplier = new ValueExtractorProducer.UniversalExtractorsSupplier(); + ValueExtractor extractor = supplier.create(annotation); + + assertThat(extractor, is(notNullValue())); + Object extracted = extractor.extract(person); + assertThat(extracted, is(instanceOf(Iterable.class))); + assertThat(((Iterable) extracted), contains(person.getFirstName(), person.getLastName())); + } + + @Test + void shouldCreateChainedExtractor() + { + ChainedExtractor annotation = ChainedExtractor.Literal.of("phoneNumber", "countryCode"); + ValueExtractorProducer.ChainedExtractorSupplier supplier = new ValueExtractorProducer.ChainedExtractorSupplier(); + ValueExtractor extractor = supplier.create(annotation); + + assertThat(extractor, is(notNullValue())); + assertThat(extractor.extract(person), is(person.getPhoneNumber().getCountryCode())); + } + + @Test + void shouldCreateMultiChainedExtractor() + { + ChainedExtractor annotation1 = ChainedExtractor.Literal.of("phoneNumber", "countryCode"); + ChainedExtractor annotation2 = ChainedExtractor.Literal.of("phoneNumber", "number"); + ChainedExtractor.Extractors annotation = ChainedExtractor.Extractors.Literal.of(annotation1, annotation2); + ValueExtractorProducer.ChainedExtractorsSupplier supplier = new ValueExtractorProducer.ChainedExtractorsSupplier(); + ValueExtractor extractor = supplier.create(annotation); + + assertThat(extractor, is(notNullValue())); + + Object extracted = extractor.extract(person); + assertThat(extracted, is(instanceOf(Iterable.class))); + assertThat(((Iterable) extracted), contains(phoneNumber.getCountryCode(), phoneNumber.getNumber())); + } + + @Test + void shouldCreatePofExtractor() + { + PofExtractor annotation = PofExtractor.Literal.of(Integer.class, 3, 0); + ValueExtractorProducer.PofExtractorSupplier supplier = new ValueExtractorProducer.PofExtractorSupplier(); + ValueExtractor extractor = supplier.create(annotation); + + assertThat(extractor, is(notNullValue())); + + assertThat(InvocableMapHelper.extractFromEntry(extractor, entry), is(person.getPhoneNumber().getCountryCode())); + } + + @Test + void shouldCreateMultiPofExtractor() + { + PofExtractor annotation1 = PofExtractor.Literal.of(3, 0); + PofExtractor annotation2 = PofExtractor.Literal.of(3, 1); + PofExtractor.Extractors annotation = PofExtractor.Extractors.Literal.of(annotation1, annotation2); + ValueExtractorProducer.PofExtractorsSupplier supplier = new ValueExtractorProducer.PofExtractorsSupplier(); + ValueExtractor extractor = supplier.create(annotation); + + assertThat(extractor, is(instanceOf(MultiExtractor.class))); + + BackingMapContext ctx = mock(BackingMapContext.class); + Map index = new HashMap<>(); + + when(ctx.getIndexMap()).thenReturn(index); + + BinaryEntry entry = new BackingMapBinaryEntry(binaryKey, binaryPerson, binaryPerson, null) + { + @Override + public BackingMapContext getBackingMapContext() + { + return ctx; + } + }; + + Object extracted = InvocableMapHelper.extractFromEntry(extractor, entry); + + assertThat(extracted, is(instanceOf(Iterable.class))); + assertThat((Iterable) extracted, contains(phoneNumber.getCountryCode(), phoneNumber.getNumber())); + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/Account.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/Account.java new file mode 100644 index 0000000000000..b14e39b2eb48b --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/Account.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.cdi.data; + +import com.oracle.coherence.cdi.Injectable; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import java.io.IOException; +import java.io.Serializable; + +import javax.enterprise.context.Dependent; +import javax.enterprise.event.Event; + +import javax.inject.Inject; + +/** + * A simple class representing a bank account that can be used in tests requiring + * test data. + * + * @author Aleks Seovic 2020.04.08 + */ +@Dependent +public class Account + implements PortableObject, Serializable, Injectable + { + private String id; + private long balance; + + @Inject + private Event accountOverdrawnEvent; + + /** + * Default constructor for serialization. + */ + public Account() + { + } + + /** + * Create a {@link Account}. + * + * @param id account ID + * @param balance initial balance + */ + public Account(String id, long balance) + { + this.id = id; + this.balance = balance; + } + + public String getId() + { + return id; + } + + public long getBalance() + { + return balance; + } + + public long withdraw(long amount) + { + balance -= amount; + if (balance < 0) + { + accountOverdrawnEvent.fire(new Overdrawn(balance)); + } + return balance; + } + + @Override + public void readExternal(PofReader in) throws IOException + { + id = in.readString(0); + balance = in.readLong(1); + } + + @Override + public void writeExternal(PofWriter out) throws IOException + { + out.writeString(0, id); + out.writeLong(1, balance); + } + + public static class Overdrawn + { + private long amount; + + Overdrawn(long amount) + { + this.amount = Math.abs(amount); + } + + public long getAmount() + { + return amount; + } + + public String toString() + { + return "Overdrawn{" + + "amount=" + amount + + '}'; + } + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/Person.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/Person.java new file mode 100644 index 0000000000000..7ae0329e67f47 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/Person.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi.data; + +import java.io.IOException; +import java.io.Serializable; +import java.time.LocalDate; +import java.util.Objects; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +/** + * A simple class representing a person used in tests requiring data classes. + * + * @author Jonathan 2019.10.25 + */ +public class Person + implements PortableObject, Serializable + { + private String firstName; + + private String lastName; + + private LocalDate dateOfBirth; + + private PhoneNumber phoneNumber; + + /** + * Default constructor for serialization. + */ + public Person() + { + } + + /** + * Create a {@link Person}. + * + * @param firstName the person's first name + * @param lastName the person's last name + * @param dateOfBirth the person's date of birth + * @param phoneNumber the person's phone number + */ + public Person(String firstName, String lastName, LocalDate dateOfBirth, PhoneNumber phoneNumber) + { + this.firstName = firstName; + this.lastName = lastName; + this.dateOfBirth = dateOfBirth; + this.phoneNumber = phoneNumber; + } + + public String getFirstName() + { + return firstName; + } + + public void setFirstName(String firstName) + { + this.firstName = firstName; + } + + public String getLastName() + { + return lastName; + } + + public void setLastName(String lastName) + { + this.lastName = lastName; + } + + public LocalDate getDateOfBirth() + { + return dateOfBirth; + } + + public void setDateOfBirth(LocalDate dateOfBirth) + { + this.dateOfBirth = dateOfBirth; + } + + public PhoneNumber getPhoneNumber() + { + return phoneNumber; + } + + public void setPhoneNumber(PhoneNumber phoneNumber) + { + this.phoneNumber = phoneNumber; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + Person person = (Person) o; + return Objects.equals(firstName, person.firstName) + && Objects.equals(lastName, person.lastName) + && Objects.equals(dateOfBirth, person.dateOfBirth); + } + + @Override + public int hashCode() + { + return Objects.hash(firstName, lastName, dateOfBirth); + } + + @Override + public String toString() + { + return "{firstName: \"" + firstName + "\"" + + ", lastName: \"" + lastName + "\"" + + ", dateOfBirth: " + dateOfBirth + + ", phoneNumber: " + phoneNumber + + '}'; + } + + @Override + public void readExternal(PofReader in) throws IOException + { + firstName = in.readString(0); + lastName = in.readString(1); + dateOfBirth = in.readLocalDate(2); + phoneNumber = in.readObject(3); + } + + @Override + public void writeExternal(PofWriter out) throws IOException + { + out.writeString(0, firstName); + out.writeString(1, lastName); + out.writeDate(2, dateOfBirth); + out.writeObject(3, phoneNumber); + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/PhoneNumber.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/PhoneNumber.java new file mode 100644 index 0000000000000..4557d28c212e9 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/PhoneNumber.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.cdi.data; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Objects; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +/** + * A simple class representing an address that can be used in tests requiring + * test data. + * + * @author Jonathan Knight 2019.10.25 + */ +public class PhoneNumber + implements PortableObject, Serializable + { + + private int countryCode; + + private String number; + + /** + * Default constructor for serialization. + */ + public PhoneNumber() + { + } + + /** + * Create a {@link PhoneNumber}. + * + * @param countryCode the country code + * @param number the phone number + */ + public PhoneNumber(int countryCode, String number) + { + this.countryCode = countryCode; + this.number = number; + } + + public int getCountryCode() + { + return countryCode; + } + + public void setCountryCode(int countryCode) + { + this.countryCode = countryCode; + } + + public String getNumber() + { + return number; + } + + public void setNumber(String number) + { + this.number = number; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + PhoneNumber that = (PhoneNumber) o; + return Objects.equals(countryCode, that.countryCode) + && Objects.equals(number, that.number); + } + + @Override + public int hashCode() + { + return Objects.hash(countryCode, number); + } + + @Override + public String toString() + { + return "{countryCode: " + countryCode + + ", number: \"" + number + "\"}"; + } + + @Override + public void readExternal(PofReader in) throws IOException + { + countryCode = in.readInt(0); + number = in.readString(1); + } + + @Override + public void writeExternal(PofWriter out) throws IOException + { + out.writeInt(0, countryCode); + out.writeString(1, number); + } + } diff --git a/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/package-info.java b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/package-info.java new file mode 100644 index 0000000000000..0800ebd37ab18 --- /dev/null +++ b/prj/coherence-cdi/src/test/java/com/oracle/coherence/cdi/data/package-info.java @@ -0,0 +1,5 @@ +/** + * @author Jonathan Knight 2019.10.24 + */ + +package com.oracle.coherence.cdi.data; diff --git a/prj/coherence-cdi/src/test/resources/META-INF/beans.xml b/prj/coherence-cdi/src/test/resources/META-INF/beans.xml new file mode 100644 index 0000000000000..68afed250a8e1 --- /dev/null +++ b/prj/coherence-cdi/src/test/resources/META-INF/beans.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/prj/coherence-cdi/src/test/resources/cdi-beans-cache-config.xml b/prj/coherence-cdi/src/test/resources/cdi-beans-cache-config.xml new file mode 100644 index 0000000000000..c53476f0f2fe5 --- /dev/null +++ b/prj/coherence-cdi/src/test/resources/cdi-beans-cache-config.xml @@ -0,0 +1,77 @@ + + + + + + + + + registrationListener + + + + + activationListener + + + + + + + * + distributed-scheme + + + numbers + distributed-scheme + + + + cacheListener + + + + + + + + + distributed-scheme + PartitionedCache + true + + + partitionListener + + + memberListener + + + + + + + + cacheStore + + + + true + + + + storageListener + + + + + + \ No newline at end of file diff --git a/prj/coherence-cdi/src/test/resources/cdi-events-cache-config.xml b/prj/coherence-cdi/src/test/resources/cdi-events-cache-config.xml new file mode 100644 index 0000000000000..7f33a1a47c531 --- /dev/null +++ b/prj/coherence-cdi/src/test/resources/cdi-events-cache-config.xml @@ -0,0 +1,42 @@ + + + + + + + + + com.oracle.coherence.cdi.EventDispatcher + + + + + + + people + people-scheme + + + + + + people-scheme + People + true + + + + + true + + + + \ No newline at end of file diff --git a/prj/coherence-cdi/src/test/resources/coherence-cache-config.xml b/prj/coherence-cdi/src/test/resources/coherence-cache-config.xml new file mode 100644 index 0000000000000..619482f04a734 --- /dev/null +++ b/prj/coherence-cdi/src/test/resources/coherence-cache-config.xml @@ -0,0 +1,45 @@ + + + + + + + + * + distributed-scheme + + + + + + + * + topic-scheme + + + + + + distributed-scheme + PartitionedCache + true + + + + + true + + + + topic-scheme + TopicService + + + \ No newline at end of file diff --git a/prj/coherence-cdi/src/test/resources/injectable-cache-config.xml b/prj/coherence-cdi/src/test/resources/injectable-cache-config.xml new file mode 100644 index 0000000000000..885c0ac8554fa --- /dev/null +++ b/prj/coherence-cdi/src/test/resources/injectable-cache-config.xml @@ -0,0 +1,42 @@ + + + + + + + + + com.oracle.coherence.cdi.EventDispatcher + + + + + + + accounts + accounts-scheme + + + + + + accounts-scheme + Accounts + true + + + + + true + + + + \ No newline at end of file diff --git a/prj/coherence-cdi/src/test/resources/test-cache-config.xml b/prj/coherence-cdi/src/test/resources/test-cache-config.xml new file mode 100644 index 0000000000000..68d0d63dede2a --- /dev/null +++ b/prj/coherence-cdi/src/test/resources/test-cache-config.xml @@ -0,0 +1,47 @@ + + + + + + test + + + + * + distributed-scheme + + + + + + + * + topic-scheme + + + + + + distributed-scheme + PartitionedCache + true + + + + + true + + + + topic-scheme + TopicService + + + \ No newline at end of file diff --git a/prj/coherence-cdi/src/test/resources/test-pof-config.xml b/prj/coherence-cdi/src/test/resources/test-pof-config.xml new file mode 100644 index 0000000000000..fbf247e965064 --- /dev/null +++ b/prj/coherence-cdi/src/test/resources/test-pof-config.xml @@ -0,0 +1,24 @@ + + + + + + coherence-pof-config.xml + + + 1000 + com.oracle.coherence.cdi.data.Person + + + 1001 + com.oracle.coherence.cdi.data.PhoneNumber + + + \ No newline at end of file diff --git a/prj/coherence-core-components/pom.xml b/prj/coherence-core-components/pom.xml new file mode 100644 index 0000000000000..454331d8fc62e --- /dev/null +++ b/prj/coherence-core-components/pom.xml @@ -0,0 +1,454 @@ + + + + 4.0.0 + + + com.oracle.coherence.ce + main + 14.1.2-0-0-SNAPSHOT + ../pom.xml + + + coherence-core-components + Coherence Core Components (core, core-net) + + jar + + + Builds the TDE-based Coherence Components (core, core-net) + + + + + ${project.basedir}/../.. + + + ${project.build.directory}/artifact-classes + ${project.build.directory}/artifact-sources + + + ${project.build.directory}/packageless-sources + + + core + 1.3 + ${tde.projects.path}/${core.project}/${core.project.version} + + core-net + 3.0 + ${tde.projects.path}/${core-net.project}/${core-net.project.version} + core + Component.Application.Console.Coherence + + true + true + + + + + ${coherence.group.id} + coherence-discovery + ${project.version} + provided + + + + ${coherence.group.id} + coherence-core + ${project.version} + provided + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + copy-dependencies + initialize + + copy-dependencies + + + true + ${tde.projects.dependencies.path} + true + true + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + + + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven.assembly.plugin.version} + + + + + create-sources-jar + prepare-package + + single + + + sources + + src/assembly/sources.xml + + true + ${tde.source.not.required} + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + ${maven.antrun.plugin.version} + + + + + delete-coverage-src + initialize + + run + + + + + + + + + + + + + detect-conditional-compilation + initialize + + run + + + true + + + + + + + + + + + + + conditional-clean + initialize + + run + + + + + + + + + + + + + + + + + + + + ${tde.compile.not.required} + + + + + + create-core-net-target + process-resources + + run + + + + + + + + + + + update-last-compile + compile + + run + + + + + + + + + + + ${tde.compile.not.required} + + + + + + relocate-sources + process-classes + + run + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${tde.source.not.required} + + + + + + unpack-coherence-jar + prepare-package + + run + + + + + + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + + + + org.codehaus.mojo + exec-maven-plugin + + + compile-core + compile + + exec + + + ${java.executable} + + -Xms${java.memory.minimum} + -Xmx${java.memory.maximum} + -Djava.awt.headless=true + -Dtangosol.taps.repos=file://${tde.projects.path} + -Dtangosol.taps.prj=${core.project}:${core.project.version} + -classpath + ${tde.classpath} + com.tangosol.tde.component.application.console.Tcmd + -compile + -depend + -warnings:none + Component + + ${tde.compile.not.required} + + + + + compile-core-net + compile + + exec + + + ${java.executable} + + -Xms${java.memory.minimum} + -Xmx${java.memory.maximum} + -Djava.awt.headless=true + -Dtangosol.taps.repos=file://${tde.projects.path} + -Dtangosol.taps.prj=${core-net.project}:${core-net.project.version} + -classpath + ${tde.classpath} + com.tangosol.tde.component.application.console.Tcmd + -compile + -depend + -warnings:none + Component + + ${tde.compile.not.required} + + + + + relocate-classes + process-classes + + exec + + + ${java.executable} + + -Xms${java.memory.minimum} + -Xmx${java.memory.maximum} + -Djava.awt.headless=true + -Dtangosol.taps.repos=file://${tde.projects.path} + -Dtangosol.taps.prj=${core-net.project}:${core-net.project.version} + -classpath + ${tde.classpath} + com.tangosol.tde.component.application.console.Tcmd + -package + ${core-net.project.package} + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + ${artifact.classes.path} + + false + + ${project.name} + ${project.version.official} + ${project.build.number} + ${project.organization.name} + ${project.groupId} + ${project.version.official} + ${project.organization.name} + + + + + + + + + + code-coverage + + + code.coverage.enabled + true + + + + + + ${project.build.directory}/classes + ${project.basedir}/src/main/java + + + + + org.jacoco + jacoco-maven-plugin + + + instrument + + instrument + + prepare-package + + + + + + + + + diff --git a/prj/coherence-core-components/src/assembly/sources.xml b/prj/coherence-core-components/src/assembly/sources.xml new file mode 100644 index 0000000000000..6be1c2d707a51 --- /dev/null +++ b/prj/coherence-core-components/src/assembly/sources.xml @@ -0,0 +1,25 @@ + + + + sources + + jar + + false + + + ${artifact.sources.path} + + + ** + + + + diff --git a/prj/coherence-core/pom.xml b/prj/coherence-core/pom.xml new file mode 100644 index 0000000000000..6298304cab38a --- /dev/null +++ b/prj/coherence-core/pom.xml @@ -0,0 +1,222 @@ + + + + 4.0.0 + + + com.oracle.coherence.ce + main + 14.1.2-0-0-SNAPSHOT + ../pom.xml + + + coherence-core + Coherence Core + jar + + + false + + + + + ${coherence.group.id} + coherence-discovery + ${project.version} + provided + + + + + org.ow2.asm + asm + provided + + + + org.ow2.asm + asm-commons + provided + + + + + com.sleepycat + je + + + + + com.sun.codemodel + codemodel + + + + + org.graalvm.sdk + graal-sdk + provided + + + + org.graalvm.js + js + provided + + + + org.graalvm.js + js-scriptengine + provided + + + + org.graalvm.js + js-launcher + provided + + + + + org.hamcrest + hamcrest-all + test + + + + + javaee + javaee-api + provided + + + + + javax.inject + javax.inject + provided + true + + + + + javax.json.bind + javax.json.bind-api + provided + true + + + + + javax.json + javax.json-api + + + org.glassfish + javax.json + + + + + junit + junit + test + + + + + com.fasterxml.jackson.core + jackson-annotations + test + + + com.fasterxml.jackson.core + jackson-core + test + + + + + org.glassfish.jersey.core + jersey-server + provided + true + + + org.glassfish.jersey.inject + jersey-hk2 + provided + true + + + + org.mockito + mockito-core + test + + + + + + + + src/main/resources + + + + + ../../ext/license + + coherence-client.xml + coherence-community.xml + coherence-enterprise.xml + coherence-grid.xml + coherence-rtc.xml + coherence-standard.xml + processor-dictionary.xml + + + + + + ../../ext/license/keys + + tangosol.cer + tangosol.dat + + + + + + src/main/java + + com/tangosol/net/security/*.xml + com/tangosol/util/*.xml + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven.jar.plugin.version} + + + + test-jar + + + + + + + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Assertions.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Assertions.java new file mode 100644 index 0000000000000..46f9a3711b4ec --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Assertions.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +import com.tangosol.util.AssertionException; + +import static com.oracle.coherence.common.base.Loggers.err; +import static com.oracle.coherence.common.base.StackTrace.getExpression; +import static com.oracle.coherence.common.base.StackTrace.getStackTrace; + + +/** + * Class for providing assertion functionality. + * + * @author cp 2000.08.02 + * @since Coherence 12.4.1 + */ + +public abstract class Assertions + { + // ----- assertion support ---------------------------------------------- + + /** + * Definite assertion failure. + */ + public static RuntimeException azzert() + { + azzert(false, "Assertion: Unexpected execution of code at:"); + return null; + } + + /** + * Test an assertion. + */ + public static void azzert(boolean f) + { + if (!f) + { + azzertFailed(null); + } + } + + /** + * Test an assertion, and print the specified message if the assertion + * fails. + */ + public static void azzert(boolean f, String s) + { + if (!f) + { + azzertFailed(s); + } + } + + /** + * Throw an assertion exception. + * + * @param sMsg the assertion message + */ + public static void azzertFailed(String sMsg) + { + if (sMsg == null) + { + // default assertion message + sMsg = "Assertion failed:"; + + // try to load the source to print out the exact assertion + String sSource = getExpression("azzert"); + if (sSource != null) + { + sMsg += " " + sSource; + } + } + + // display the assertion + err(sMsg); + + // display the code that caused the assertion + String sStack = getStackTrace(); + err(sStack.substring(sStack.indexOf('\n', sStack.lastIndexOf(".azzert(")) + 1)); + + throw new AssertionException(sMsg); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Associated.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Associated.java new file mode 100644 index 0000000000000..cbff8816d7ae9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Associated.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * The Associated interface facilitates the creation of a very generic + * + * equivalence relation between different objects and allows to group them + * based on the equality of the "association key" object returned by the + * {@link #getAssociatedKey} method. + * + * @param the type of associated key + * + * @author gg 2012.03.11 + * + * @see Associator + */ +public interface Associated + { + /** + * Determine the host key (or base) object to which this object is associated. + *

+ * Note: It's expected that the returned object is suitable to be used + * as an immutable identity (e.g. a key in a Map). + *
+ * Note 2: Circular associations are not permitted. + * + * @return the host key that for this object, or null if this object has no + * association + */ + public T getAssociatedKey(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Associator.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Associator.java new file mode 100644 index 0000000000000..b88a60d94e1b6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Associator.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * The Associator interface facilitates the creation of a very generic + * + * equivalence relation between different objects and allows to group them + * based on the equality of the "association key" object returned by the + * {@link #getAssociatedKey} method. + * + * @author gg 2012.03.11 + * + * @see Associated + */ +public interface Associator + { + /** + * Determine the host key (or base) object to which the specified object is + * associated. + *

+ * Note: It's expected that the returned object is suitable to be used + * as an immutable identity (e.g. a key in a Map). + *
+ * Note 2: Circular associations are not permitted. + + * @param o the object to obtain an association for + * + * @return the host key that for this object, or null if this object has no + * association + */ + public Object getAssociatedKey(Object o); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Blocking.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Blocking.java new file mode 100644 index 0000000000000..f6f902a240fa1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Blocking.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.channels.Selector; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.LockSupport; + +/** + * Blocking provides a set of helper methods related to blocking a thread. + * + * @author mf 2015.02.24 + */ +public class Blocking + { + // Note: the blocking helpers are written to minimize their expense when + // they complete without timing out. As such they all take the basic + // approach of only checking for timeout *before* blocking, and truncating + // the blocking time such that the blocking operation will complete when + // timed out. In such a case the blocking operation will not throw an + // InterruptedException, but any subsequent blocking helper would immediately + // detect the timeout and interrupt the thread, which would then cause its + // blocking operation to throw InterruptedException (if appropriate). The + // benefit of this approach is that it avoids both unnecessary conditional + // logic, and testing/clearing the Thread's interrupt flag. Deferring the + // interrupt until the subsequent blocking operation is also legal as the + // original blocking operation simply appears to have completed spuriously. + + /** + * Return true if the thread is interrupted or {@link Timeout timed out}. + * + * Note as with Thread.interrupted this will clear the interrupted flag if + * it is set, it will not however clear the timeout. + * + * @return true if the thread is interrupted or {@link Timeout timed out} + */ + public static boolean interrupted() + { + // Note: we must check both the timeout and the interrupt, as checking for timeout + // sets the interrupt, we don't actually need to check the timeout result, just invoking + // isTimedOut() followed by an unconditional call to Thread.interrupted is sufficient. + Timeout.isTimedOut(); // will interrupt the thread if timed out + return Thread.interrupted(); + } + + /** + * Wait on the the specified monitor while still respecting the calling + * thread's {@link Timeout timeout}. + * + * @param oMonitor the monitor to wait on + * + * @throws InterruptedException if the thread is interrupted + */ + public static void wait(Object oMonitor) + throws InterruptedException + { + wait(oMonitor, 0, 0); + } + + /** + * Wait on the specified monitor while still respecting the calling + * thread's {@link Timeout timeout}. + * + * @param oMonitor the monitor to wait on + * @param cMillis the maximum number of milliseconds to wait + * + * @throws InterruptedException if the thread is interrupted + */ + public static void wait(Object oMonitor, long cMillis) + throws InterruptedException + + { + wait(oMonitor, cMillis, 0); + } + + /** + * Wait on the specified monitor while still respecting the calling + * thread's {@link Timeout timeout}. + * + * @param oMonitor the monitor to wait on + * @param cMillis the maximum number of milliseconds to wait + * @param cNanos the additional number of nanoseconds to wait + * + * @throws InterruptedException if the thread is interrupted + */ + public static void wait(Object oMonitor, long cMillis, int cNanos) + throws InterruptedException + { + long cMillisBlock = Math.min(Timeout.remainingTimeoutMillis(), cMillis == 0 ? Long.MAX_VALUE : cMillis); + oMonitor.wait(cMillisBlock == Long.MAX_VALUE ? 0 : cMillisBlock, cNanos); + } + + /** + * Invoke Thread.sleep() while still respecting the calling + * thread's {@link Timeout timeout}. + * + * @param cMillis the maximum number of milliseconds to sleep + * + * @throws InterruptedException if the thread is interrupted + */ + public static void sleep(long cMillis) + throws InterruptedException + { + sleep(cMillis, 0); + } + + /** + * Invoke Thread.sleep() while still respecting the calling + * thread's {@link Timeout timeout}. + * + * @param cMillis the maximum number of milliseconds to sleep + * @param cNanos the additional number of nanoseconds to sleep + * + * @throws InterruptedException if the thread is interrupted + */ + public static void sleep(long cMillis, int cNanos) + throws InterruptedException + { + Thread.sleep(Math.min(Timeout.remainingTimeoutMillis(), cMillis), cNanos); + } + + /** + * Invoke LockSupport.park() while still respecting the calling + * thread's {@link Timeout timeout}. + * + * @param oBlocker the blocker + */ + public static void park(Object oBlocker) + { + parkNanos(oBlocker, Long.MAX_VALUE); // park of 0 is a no-op + } + + /** + * Invoke LockSupport.parkNanos() while still respecting the calling + * thread's {@link Timeout timeout}. + * + * @param oBlocker the blocker + * @param cNanos the maximum number of nanoseconds to park for + */ + public static void parkNanos(Object oBlocker, long cNanos) + { + long cMillisTimeout = Timeout.remainingTimeoutMillis(); + long cNanosTimeout = cMillisTimeout >= Long.MAX_VALUE / 1000000 ? Long.MAX_VALUE : cMillisTimeout * 1000000; + if (cMillisTimeout == Long.MAX_VALUE && cNanos == Long.MAX_VALUE) + { + LockSupport.park(oBlocker); // common case no timeout + } + else + { + LockSupport.parkNanos(oBlocker, Math.min(cNanos, Math.min(cNanos, cNanosTimeout))); + } + } + + /** + * Acquire a lock while still respecting the calling thread's {@link Timeout timeout}. + * + * @param lock the lock to acquire + * + * @throws InterruptedException if the thread is interrupted + */ + public static void lockInterruptibly(Lock lock) + throws InterruptedException + { + while (!tryLock(lock, Long.MAX_VALUE, TimeUnit.MILLISECONDS)) {} + } + + /** + * Attempt to acquire a lock while still respecting the calling thread's {@link Timeout timeout}. + * + * @param lock the lock to acquire + * @param time the maximum amount of time to try for + * @param unit the unit which time represents + * + * @return true iff the lock was acquired + * + * @throws InterruptedException if the thread is interrupted + */ + public static boolean tryLock(Lock lock, long time, TimeUnit unit) + throws InterruptedException + { + long cMillisTimeout = Timeout.remainingTimeoutMillis(); + long cMillis = unit.toMillis(time); + if (cMillis == 0) + { + // handle a non-zero timeout which is less then a milli + return lock.tryLock(time, unit); + } + + return lock.tryLock(Math.min(cMillisTimeout, cMillis), TimeUnit.MILLISECONDS); + } + + /** + * Await for the Condition to be signaled while still respecting the calling thread's {@link Timeout timeout}. + * + * @param cond the condition to wait on + * + * @throws InterruptedException if the thread is interrupted + */ + public static void await(Condition cond) + throws InterruptedException + { + await(cond, Long.MAX_VALUE); + } + + /** + * Await for the Condition to be signaled while still respecting the calling thread's {@link Timeout timeout}. + * + * @param cond the condition to wait on + * @param cNanos the maximum amount of time to wait + * + * @throws InterruptedException if the thread is interrupted + */ + public static void await(Condition cond, long cNanos) + throws InterruptedException + { + await(cond, cNanos, TimeUnit.NANOSECONDS); + } + + /** + * Await for the Condition to be signaled while still respecting the calling thread's {@link Timeout timeout}. + * + * @param cond the condition to wait on + * @param time the maximum amount of time to wait + * @param unit the unit which time represents + * + * @throws InterruptedException if the thread is interrupted + */ + public static void await(Condition cond, long time, TimeUnit unit) + throws InterruptedException + { + long cMillisTimeout = Timeout.remainingTimeoutMillis(); + long cMillis = unit.toMillis(time); + if (cMillis == 0) + { + // handle a non-zero timeout which is less then a milli + cond.await(time, unit); + } + else + { + cond.await(Math.min(cMillisTimeout, cMillis), TimeUnit.MILLISECONDS); + } + } + + /** + * Wait on the Selector while still respecting the calling thread's {@link Timeout timeout}. + * + * If the thread performing the select is interrupted, this method will return immediately and that thread's + * interrupted status will be set. + * + * @param selector the selector to wait on + * + * @return the number of keys, possibly zero, whose ready-operation sets were updated + * + * @throws IOException if an I/O error occurs + */ + public static int select(Selector selector) + throws IOException + { + return select(selector, 0); + } + + /** + * Wait on the Selector while still respecting the calling thread's {@link Timeout timeout}. + * + * If the thread performing the select is interrupted, this method will return immediately and that thread's + * interrupted status will be set. + * + * @param selector the selector to wait on + * @param cMillis the maximum amount of time to wait + * + * @return the number of keys, possibly zero, whose ready-operation sets were updated + * + * @throws IOException if an I/O error occurs + */ + public static int select(Selector selector, long cMillis) + throws IOException + { + long cMillisBlock = Math.min(Timeout.remainingTimeoutMillis(), cMillis == 0 ? Long.MAX_VALUE : cMillis); + return selector.select(cMillisBlock == Long.MAX_VALUE ? 0 : cMillisBlock); + } + + /** + * Connect a socket while still respecting the calling thread's {@link Timeout timeout}. + * + * @param socket the socket to connect + * @param addr the address to connect to + * + * @throws IOException in an IO error occurs + */ + public static void connect(Socket socket, SocketAddress addr) + throws IOException + { + connect(socket, addr, 0); + } + + /** + * Connect a socket within a given timeout while still respecting the calling thread's {@link Timeout timeout}. + * + * @param socket the socket to connect + * @param addr the address to connect to + * @param cMillis the caller specified connect timeout + * + * @throws IOException in an IO error occurs + */ + public static void connect(Socket socket, SocketAddress addr, int cMillis) + throws IOException + { + long cMillisBlock = Math.min(Timeout.remainingTimeoutMillis(), cMillis == 0 ? Long.MAX_VALUE : cMillis); + socket.connect(addr, cMillisBlock >= Integer.MAX_VALUE ? 0 : (int) cMillisBlock); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/CanonicallyNamed.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/CanonicallyNamed.java new file mode 100644 index 0000000000000..85e76365cb4aa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/CanonicallyNamed.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +/** + * CanonicallyNamed provides a way for objects to identify themselves by name. + * By convention two objects with the same non-null canonical name are considered to be + * {@link Object#equals(Object) equal} and will have the same {@link Object#hashCode() hashCode}. + * + * @author mf/jf 2017.12.15 + * @since 12.2.1.4 + */ +public interface CanonicallyNamed + { + /** + * Return a canonical name for the entity or {@code null}. + * + * @return canonical name or {@code null} + */ + String getCanonicalName(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Classes.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Classes.java new file mode 100644 index 0000000000000..ad6d72df50818 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Classes.java @@ -0,0 +1,1021 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Hashtable; +import java.util.Vector; + +import static com.oracle.coherence.common.base.Formatting.isDecimal; + + +/** + * This abstract class contains dynamic (reflect-based) class, method, and + * field manipulation methods. + *

+ * Note: This class is primarily for supporting generated code. + * + * @author Cameron Purdy + * @version 1.00, 11/22/96 + */ +public abstract class Classes + { + /** + * Determine the simple (unqualified) name of a class. + * + * @param clz the class to determine the simple name of + * @return the simple name of the class + */ + static public String getSimpleName(Class clz) + { + String sFullClass = clz.getName(); + int ofDotClass = sFullClass.lastIndexOf('.'); + if (ofDotClass < 0) + { + return sFullClass; + } + else + { + String sName = sFullClass.substring(ofDotClass + 1); + int ofInnerClass = sName.lastIndexOf('$'); + return ofInnerClass < 0 ? sName : sName.substring(ofInnerClass + 1); + } + } + + /** + * Determine the simple (unqualified) name of a class. + *

+     *   in      out
+     *   ------- -------
+     *   [blank] [blank]
+     *   a       a
+     *   .a      [blank]
+     *   a.      [blank]
+     *   .a.     [blank]
+     *   a.b     b
+     *   .a.b    b
+     *   a.b.    [blank]
+     *   .a.b.   [blank]
+     * 
+ * + * @param sName the simple or qualified name of the class (or package) + * @return the simple name of the class + */ + static public String getSimpleName(String sName) + { + int ofLastDot = sName.lastIndexOf('.'); + + // no dot means no package name + if (ofLastDot < 0) + { + return sName; + } + + // trailing dot means entire string is package name + if (ofLastDot == sName.length() - 1) + { + return ""; + } + + // dot in middle means qualified name + if (ofLastDot > 0) + { + return sName.substring(ofLastDot + 1); + } + + // otherwise, whole name is the package name + return ""; + } + + /** + * Instantiate the specified class using the specified parameters. + * + * @param clz the class to instantiate + * @param aoParam the constructor parameters + * + * @return a new instance of the specified class + * + * @throws InstantiationException if an exception is raised trying + * to instantiate the object, whether the exception is a + * security, method access, no such method, or instantiation + * exception + * @throws InvocationTargetException if the constructor of the new + * object instance raises an exception + */ + static public Object newInstance(Class clz, Object[] aoParam) + throws InstantiationException, InvocationTargetException + { + if (clz == null) + { + throw new InstantiationException("Required class object is null"); + } + + if (aoParam == null) + { + aoParam = VOID; + } + + int cParams = aoParam.length; + + // simple instantiation: no parameters + if (cParams == 0) + { + try + { + return clz.newInstance(); + } + catch (InstantiationException e) + { + // InstantiationException could be thrown with an empty message. + if (e.getMessage() == null) + { + throw new InstantiationException(clz.getName()); + } + else + { + throw e; + } + } + catch (IllegalAccessException e) + { + throw new InstantiationException(e.toString()); + } + } + + // determine required constructor parameter types + Class[] aclzParam = new Class[cParams]; + boolean fExactMatch = true; + for (int i = 0; i < cParams; ++i) + { + if (aoParam[i] == null) + { + fExactMatch = false; + } + else + { + aclzParam[i] = aoParam[i].getClass(); + } + } + + // try to find a matching constructor + Constructor constrMatch = null; + + // check for exact constructor match if all parameters were non-null + if (fExactMatch) + { + try + { + constrMatch = clz.getConstructor(aclzParam); + } + catch (Exception e) + { + } + } + + if (constrMatch == null) + { + // search for first constructor that matches the parameters + Constructor[] aconstr = clz.getConstructors(); + int cconstr = aconstr.length; + for (int iconstr = 0; iconstr < cconstr; ++iconstr) + { + Constructor constr = aconstr[iconstr]; + Class[] aclzActual = constr.getParameterTypes(); + if (aclzActual.length == cParams) + { + boolean fMatch = true; + for (int i = 0; i < cParams; ++i) + { + // matchable possibilities: + // 1) null parameter with any reference type + // 2) non-null parameter matching primitive type + // 3) non-null parameter assignable to reference type + if (aoParam[i] == null) + { + // null cannot be assigned to a primitive param + fMatch = !aclzActual[i].isPrimitive(); + } + else if (aclzActual[i].isPrimitive()) + { + fMatch = (aclzActual[i] == (Class) tblPrimitives.get(aclzParam[i])); + } + else + { + fMatch = aclzActual[i].isAssignableFrom(aclzParam[i]); + } + + // no match try the next constructor + if (!fMatch) + { + break; + } + } + + // found valid constructor. + if (fMatch) + { + constrMatch = constr; + break; + } + } + } + } + + if (constrMatch != null) + { + try + { + return constrMatch.newInstance(aoParam); + } + catch (InstantiationException e) + { + // InstantiationException could be thrown with a null message. + if (e.getMessage() == null) + { + throw new InstantiationException(clz.getName()); + } + else + { + throw e; + } + } + catch (IllegalAccessException e) + { + throw new InstantiationException(e.toString()); + } + catch (SecurityException e) + { + throw new InstantiationException(e.toString()); + } + } + + // did not find a matching constructor + StringBuilder sb = new StringBuilder(); + sb.append("Could not find a constructor for "); + sb.append(clz.getName()); + sb.append("("); + + for (int i = 0; i < cParams; ++i) + { + if (i > 0) + { + sb.append(", "); + } + + sb.append(aoParam[i] == null ? "null" : aoParam[i].getClass().getName()); + } + sb.append(")"); + + throw new InstantiationException(sb.toString()); + } + + /** + * Invoke the specified static method using the passed arguments. + * + * @param clz the class to invoke the static method of + * @param sName the method name + * @param aoParam the method arguments + * + * @return the return value (if any) from the method + */ + static public Object invokeStatic(Class clz, String sName, Object[] aoParam) + throws NoSuchMethodException, + IllegalAccessException, + InvocationTargetException + { + return invoke(clz, null, sName, aoParam); + } + + /** + * Invoke the specified instance method using the passed arguments. + * + * @param obj the object to invoke the instance method of + * @param sName the method name + * @param aoParam the method arguments + * + * @return the return value (if any) from the method + */ + static public Object invoke(Object obj, String sName, Object[] aoParam) + throws NoSuchMethodException, + IllegalAccessException, + InvocationTargetException + { + return invoke(obj.getClass(), obj, sName, aoParam); + } + + /** + * Invoke the specified method using the passed arguments. + * + * @param clz the class to invoke the method on + * @param obj the object to invoke the method on + * @param sName the method name + * @param aoParam the method arguments + * + * @return the return value (if any) from the method invocation + */ + static public Object invoke(Class clz, Object obj, String sName, Object[] aoParam) + throws NoSuchMethodException, + IllegalAccessException, + InvocationTargetException + { + if (aoParam == null) + { + aoParam = VOID; + } + + // determine method parameter types + int cParams = aoParam.length; + Class[] aclzParam = cParams == 0 ? VOID_PARAMS : new Class[cParams]; + for (int i = 0; i < cParams; ++i) + { + Object oParam = aoParam[i]; + if (oParam != null) + { + aclzParam[i] = oParam.getClass(); + } + } + + // the outermost IllegalAccessException to rethrow if nothing else works + IllegalAccessException iae = null; + + // search for the first matching method + boolean fStatic = (obj == null); + Method method = findMethod(clz, sName, aclzParam, fStatic); + while (method != null) + { + try + { + return method.invoke(obj, aoParam); + } + catch (IllegalAccessException e) + { + if (iae == null) + { + iae = e; + } + } + + // there is a matching method, but it cannot be called as a class + // method; this could happen for an interface method implemented + // by a non-public class; let's look for a matching interface + Class[] aclzIface = clz.getInterfaces(); + for (int i = 0, c = aclzIface.length; i < c; i++) + { + method = com.oracle.coherence.common.base.Classes.findMethod(aclzIface[i], sName, + aclzParam, fStatic); + if (method != null) + { + try + { + return method.invoke(obj, aoParam); + } + catch (IllegalAccessException e) {} + } + } + + clz = clz.getSuperclass(); + if (clz == null) + { + throw iae; + } + + // repeat the entire sequence for a super class + method = findMethod(clz, sName, aclzParam, fStatic); + } + + if (iae == null) + { + // no matching method was found + throw new NoSuchMethodException(clz.getName() + '.' + sName); + } + else + { + // the method is there, but is not callable + throw iae; + } + } + + /** + * Find a Method that matches the specified name and parameter types. + * If there are more than one matching methods, the first one will be + * returned. + * + * @param clz the class reference + * @param sName the method name + * @param aclzParam the parameter types (some array elements could be null) + * @param fStatic the method scope flag + * + * @return the matching Method object or null if no match could be found + */ + public static Method findMethod(Class clz, String sName, Class[] aclzParam, boolean fStatic) + { + if (aclzParam == null) + { + aclzParam = VOID_PARAMS; + } + + int cParams = aclzParam.length; + boolean fExactMatch = true; + for (int i = 0; i < cParams; ++i) + { + if (aclzParam[i] == null) + { + fExactMatch = false; + break; + } + } + + if (fExactMatch && !fStatic) + { + // check for an exact instance method match + try + { + return clz.getMethod(sName, aclzParam); + } + catch (NoSuchMethodException e) {} + } + + Method[] aMethod = clz.getMethods(); + int cMethods = aMethod.length; + + NextMethod: + for (int iMethod = 0; iMethod < cMethods; ++iMethod) + { + Method method = aMethod[iMethod]; + if (method.getName().equals(sName) && + Modifier.isStatic(method.getModifiers()) == fStatic) + { + Class[] aclzActual = method.getParameterTypes(); + if (aclzActual.length == cParams) + { + for (int i = 0; i < cParams; ++i) + { + Class clzParam = aclzParam[i]; + Class clzActual = aclzActual[i]; + + // matchable possibilities: + // 1) null parameter with any reference type + // 2) non-null parameter matching primitive type + // 3) non-null parameter assignable to reference type + boolean fMatch; + if (clzParam == null) + { + // null cannot be assigned to a primitive param + fMatch = !clzActual.isPrimitive(); + } + else if (clzActual.isPrimitive()) + { + fMatch = (clzActual == tblPrimitives.get(clzParam)); + } + else + { + fMatch = clzActual.isAssignableFrom(clzParam); + } + + if (!fMatch) + { + continue NextMethod; + } + } + return method; + } + } + } + return null; + } + + /** + * Calculate the class array based on the parameter array. + * + * @return the class array based on the parameter array + */ + public static Class[] getClassArray(Object[] aoParam) + { + if (aoParam != null) + { + int cParams = aoParam.length; + if (cParams > 0) + { + Class[] aClass = new Class[cParams]; + for (int i = 0; i < cParams; i++) + { + Object oParam = aoParam[i]; + if (oParam != null) + { + aClass[i] = oParam.getClass(); + } + } + return aClass; + } + } + + return VOID_PARAMS; + } + + /** + * Replace wrapper types with appropriate primitive types. + * + * @return the class array with primitive instead of wrapper types + */ + public static Class[] unwrap(Class[] aClasses) + { + for (int i = 0; i < aClasses.length; i++) + { + Class clz = aClasses[i]; + if (clz == null) aClasses[i] = Object.class; + if (clz == Boolean.class) aClasses[i] = Integer.TYPE; + if (clz == Byte.class) aClasses[i] = Byte.TYPE; + if (clz == Character.class) aClasses[i] = Character.TYPE; + if (clz == Short.class) aClasses[i] = Short.TYPE; + if (clz == Integer.class) aClasses[i] = Integer.TYPE; + if (clz == Long.class) aClasses[i] = Long.TYPE; + if (clz == Float.class) aClasses[i] = Float.TYPE; + if (clz == Double.class) aClasses[i] = Double.TYPE; + } + + return aClasses; + } + + /** + * Parse the method signature into discrete return type and parameter + * signatures as they appear in Java .class structures. + * + * @param sSig the JVM method signature + * + * @return an array of JVM type signatures, where [0] is the return + * type and [1]..[c] are the parameter types. + */ + public static String[] toTypes(String sSig) + { + // check for start of signature + char[] ach = sSig.toCharArray(); + if (ach[0] != '(') + { + throw new IllegalArgumentException("JVM Method Signature must start with '('"); + } + + // reserve the first element for the return value + Vector vect = new Vector(); + vect.addElement(null); + + // parse parameter signatures + int of = 1; + while (ach[of] != ')') + { + int cch = getTypeLength(ach, of); + vect.addElement(new String(ach, of, cch)); + of += cch; + } + + // return value starts after the parameter-stop character + // and runs to the end of the method signature + ++of; + vect.setElementAt(new String(ach, of, ach.length - of), 0); + + String[] asSig = new String[vect.size()]; + vect.copyInto(asSig); + + return asSig; + } + + private static int getTypeLength(char[] ach, int of) + { + switch (ach[of]) + { + case 'V': + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + return 1; + + case '[': + { + int cch = 1; + while (isDecimal(ach[++of])) + { + ++cch; + } + return cch + getTypeLength(ach, of); + } + + case 'L': + { + int cch = 2; + while (ach[++of] != ';') + { + ++cch; + } + return cch; + } + + default: + throw new IllegalArgumentException("JVM Type Signature cannot start with '" + ach[of] + "'"); + } + } + + // ----- Field operations ----------------------------------------------- + + /** + * Provide a Java source representation of a JVM type signature. + * + * @param sSig the JVM type signature + * + * @return the Java type name as found in Java source code + */ + public static String toTypeStringField(String sSig) + { + switch (sSig.charAt(0)) + { + case 'V': + return "void"; + case 'Z': + return "boolean"; + case 'B': + return "byte"; + case 'C': + return "char"; + case 'S': + return "short"; + case 'I': + return "int"; + case 'J': + return "long"; + case 'F': + return "float"; + case 'D': + return "double"; + + case 'L': + return sSig.substring(1, sSig.indexOf(';')).replace('/', '.'); + + case '[': + int of = 0; + while (isDecimal(sSig.charAt(++of))) + {} + return toTypeStringField(sSig.substring(of)) + '[' + sSig.substring(1, of) + ']'; + + default: + throw new IllegalArgumentException("JVM Type Signature cannot start with '" + sSig.charAt(0) + "'"); + } + } + + /** + * Provide a boxed version of the given primitive in binary format. + * + * @param sSig the JVM type signature + * + * @return the boxed version of the given primitive in binary format + */ + public static String toBoxedTypeField(String sSig) + { + String sBoxedType = "java/lang/"; + switch (sSig.charAt(0)) + { + case 'V': + sBoxedType += "Void"; + break; + case 'Z': + sBoxedType += "Boolean"; + break; + case 'B': + sBoxedType += "Byte"; + break; + case 'C': + sBoxedType += "Character"; + break; + case 'S': + sBoxedType += "Short"; + break; + case 'I': + sBoxedType += "Integer"; + break; + case 'J': + sBoxedType += "Long"; + break; + case 'F': + sBoxedType += "Float"; + break; + case 'D': + sBoxedType += "Double"; + break; + case 'L': + if (sSig.startsWith("Ljava/lang/")) + { + return sSig.substring(1, sSig.length() - 1); + } + case '[': + // reference and array types are quietly unsupported + sBoxedType = null; + break; + default: + throw new IllegalArgumentException("JVM Type Signature cannot start with '" + sSig.charAt(0) + "'"); + } + return sBoxedType; + } + + /** + * Provide a boxed version of the given primitive in binary format. + * + * @param sSig the JVM type signature + * + * @return the boxed version of the given primitive in binary format + */ + public static char fromBoxedTypeField(String sSig) + { + if (sSig.startsWith("java/lang/")) + { + switch (sSig.substring(10)) + { + case "Void": + return 'V'; + case "Boolean": + return 'Z'; + case "Byte": + return 'B'; + case "Character": + return 'C'; + case "Short": + return 'S'; + case "Integer": + return 'I'; + case "Long": + return 'J'; + case "Float": + return 'F'; + case "Double": + return 'D'; + } + } + return 0; // ascii null + } + + + // ----- class loader support ---------------------------------------------- + + /** + * Obtain a non-null ClassLoader. + * + * @param loader a ClassLoader (may be null) + * @return the passed ClassLoader (if not null), or the ContextClassLoader + */ + public static ClassLoader ensureClassLoader(ClassLoader loader) + { + return loader == null ? getContextClassLoader() : loader; + } + + /** + * Try to determine the ClassLoader that supports the current context. + * + * @return a ClassLoader to use for the current context + */ + public static ClassLoader getContextClassLoader() + { + return getContextClassLoader(null); + } + + /** + * Try to determine the ClassLoader that supports the current context. + * + * @param o the calling object, or any object out of the application + * that is requesting the class loader + * @return a ClassLoader to use for the current context + */ + public static ClassLoader getContextClassLoader(Object o) + { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + if (loader == null) + { + if (o != null) + { + loader = o.getClass().getClassLoader(); + } + if (loader == null) + { + loader = ClassLoader.class.getClassLoader(); + if (loader == null) + { + loader = ClassLoader.getSystemClassLoader(); + } + } + } + return loader; + } + + /** + * Attempt to load the specified class using sequentionally all of the + * specified loaders, the ContextClassLoader and the current class loader. + * + * @param sClass the class name + * @param loader1 first ClassLoader to try + * @param loader2 second ClassLoader to try + * + * @return the Class for the specified name + * + * @throws ClassNotFoundException if all the attempts fail + */ + public static Class loadClass(String sClass, ClassLoader loader1, ClassLoader loader2) + throws ClassNotFoundException + { + for (int i = 1; i <= 3; i++) + { + ClassLoader loader; + switch (i) + { + case 1: + loader = loader1; + break; + + case 2: + loader = loader2; + break; + + case 3: + loader = getContextClassLoader(); + if (loader == loader1 || loader == loader2) + { + loader = null; + } + break; + + default: + throw new IllegalStateException(); + } + + try + { + if (loader != null) + { + return Class.forName(sClass, false, loader); + } + } + catch (ClassNotFoundException e) {} + } + + // nothing worked; try the current class loader as a last chance + return Class.forName(sClass); + } + + + // ----- class formatting support ---------------------------------------------- + + /** + * Formats Class information for debug output purposes. + * + * @param clz the Class to print information for + * @return a String describing the Class in detail + */ + public static String toString(Class clz) + { + if (clz.isPrimitive()) + { + return clz.toString(); + } + else if (clz.isArray()) + { + return "Array of " + toString(clz.getComponentType()); + } + else if (clz.isInterface()) + { + return toInterfaceString(clz, ""); + } + else + { + return toClassString(clz, ""); + } + } + + /** + * Formats Class information for debug output purposes. + * + * @param clz the Class to print information for + * @param sIndent the indentation to precede each line of output + * @return a String describing the Class in detail + */ + private static String toClassString(Class clz, String sIndent) + { + StringBuilder sb = new StringBuilder(); + sb.append(sIndent) + .append("Class ") + .append(clz.getName()) + .append(" (") + .append(toString(clz.getClassLoader())) + .append(')'); + + sIndent += " "; + + Class[] aclz = clz.getInterfaces(); + for (int i = 0, c = aclz.length; i < c; ++i) + { + sb.append('\n') + .append(toInterfaceString(aclz[i], sIndent)); + } + + clz = clz.getSuperclass(); + if (clz != null) + { + sb.append('\n') + .append(toClassString(clz, sIndent)); + } + + return sb.toString(); + } + + /** + * Formats interface information for debug output purposes. + * + * @param clz the interface Class to print information for + * @param sIndent the indentation to precede each line of output + * @return a String describing the interface Class in detail + */ + private static String toInterfaceString(Class clz, String sIndent) + { + StringBuilder sb = new StringBuilder(); + sb.append(sIndent) + .append("Interface ") + .append(clz.getName()) + .append(" (") + .append(toString(clz.getClassLoader())) + .append(')'); + + Class[] aclz = clz.getInterfaces(); + for (int i = 0, c = aclz.length; i < c; ++i) + { + clz = aclz[i]; + + sb.append('\n') + .append(toInterfaceString(clz, sIndent + " ")); + } + + return sb.toString(); + } + + /** + * Format a description for the specified ClassLoader object. + * + * @param loader the ClassLoader instance (or null) + * @return a String description of the ClassLoader + */ + private static String toString(ClassLoader loader) + { + if (loader == null) + { + return "System ClassLoader"; + } + + return "ClassLoader class=" + loader.getClass().getName() + + ", hashCode=" + loader.hashCode(); + } + + /** + * Maps specific classes to their primitive "alter egos". These classes + * exist independently as instantiable classes but are also used by the + * JVM reflection to pass primitive values as objects. The JVM creates + * java.lang.Class instances for each of the primitive types in order to + * describe parameters and return values of constructors and methods. + * For example, a parameter described as class java.lang.Boolean.TYPE + * requires an instance of the class java.lang.Boolean as an argument. + * (Note that a parameter described as class java.lang.Boolean also + * requires an instance of the class java.lang.Boolean as an argument. + * This parameter/argument asymmetry only occurs with primitive types.) + */ + static private Hashtable tblPrimitives; + static + { + tblPrimitives = new Hashtable(10, 1.0F); + tblPrimitives.put(Boolean .class, Boolean.TYPE ); + tblPrimitives.put(Character.class, Character.TYPE); + tblPrimitives.put(Byte .class, Byte.TYPE ); + tblPrimitives.put(Short .class, Short.TYPE ); + tblPrimitives.put(Integer .class, Integer.TYPE ); + tblPrimitives.put(Long .class, Long.TYPE ); + tblPrimitives.put(Float .class, Float.TYPE ); + tblPrimitives.put(Double .class, Double.TYPE ); + tblPrimitives.put(Void .class, Void.TYPE ); + } + + // ---- constants ------------------------------------------------------- + + /** + * Useful constant for methods with no parameters. + */ + public final static Class[] VOID_PARAMS = new Class[0]; + + /** + * Useful constant for methods with no arguments. + */ + public final static Object[] VOID = new Object[0]; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Cloneable.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Cloneable.java new file mode 100644 index 0000000000000..85f807f3da09e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Cloneable.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * This interface is meant to "fix" the usability issues related to the + * {@link java.lang.Cloneable} method-less interface. + * + * @author gg/mf 2012.07.12 + */ +public interface Cloneable + extends java.lang.Cloneable + { + /** + * Create and return a copy of this object as described by the + * {@link Object#clone()} contract. + * + * @return a copy of this object + */ + public Object clone(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Cloner.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Cloner.java new file mode 100644 index 0000000000000..773586294a09b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Cloner.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * A Cloner provides an external means for producing copies of objects as + * prescribed by the {@link Object#clone()} contract. + * + * @author gg/mf 2012.07.12 + */ +public interface Cloner + { + /** + * Return a copy of the specified object. + * + * @param o the object to clone + * @param the type of the object + * + * @return the new object + */ + public T clone(T o); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Collector.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Collector.java new file mode 100644 index 0000000000000..1473b34fb1453 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Collector.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * A Collector is mechanism for receiving items. + * + * @param the collected type + * + * @author mf 2010.10.06 + */ +public interface Collector + { + /** + * Notify the collector of a item of interest. + * + * @param value the item to collect + */ + public void add(V value); + + /** + * Request processing of any added values. + *

+ * This method should be called after a call or series of calls to {@link #add}. + */ + public default void flush() {}; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/ConcurrentNotifier.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/ConcurrentNotifier.java new file mode 100644 index 0000000000000..1779c072b67fa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/ConcurrentNotifier.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.concurrent.locks.LockSupport; + + +/** + * A Condition-like object, usable by multiple threads to both wait and signal. + *

+ * Note that no synchronization is needed to use this class; i.e. clients + * must not synchronize on this class prior to calling await() or + * signal(), nor should the use any of the primitive wait() + * or notify() methods. + *

+ * Unlike the {@link SingleWaiterMultiNotifier}, this notifier implementation requires + * the notion of a {@link #isReady ready} check. When the notifier is ready then a call + * to await because a no-op. An example ready check for a notifier based queue removal + * would be !queue.isEmpty(); + * + * @author mf 2018.04.13 + */ +public abstract class ConcurrentNotifier + implements Notifier + { + @Override + public void await(long cMillis) + throws InterruptedException + { + Thread threadThis = Thread.currentThread(); + Link linkThis = null; + long lBitThis = -1L; // will be reset when we make our link + + while (!isReady()) + { + Object oHead = m_oWaitHead; + Object oNew; + if (oHead == null) + { + // try to be the initial waiting thread; avoid creating a Link + oNew = threadThis; + } + else if (linkThis == null) + { + // other threads are waiting; create a Link for myself + lBitThis = 1L << (threadThis.hashCode() % 61); + oNew = linkThis = makeSelfLink(threadThis, lBitThis, oHead); + // if linkThis is non-null linkThis.next has also been set to oHead + // if null we are already in the stack and can avoid cas'ing and just park (see below) + } + else + { + // other threads are waiting; we've already created a Link for ourselves; just assign .next/lFilterThreads + // no-need to recheck if we're in the stack + Link linkNext = linkThis.next = oHead instanceof Link + ? (Link) oHead + : new Link((Thread) oHead); + linkThis.lFilterThreads = lBitThis | linkNext.lFilterThreads; + oNew = linkThis; + } + + if (oNew == null || s_fuHead.compareAndSet(this, oHead, oNew)) + { + park(cMillis); + return; + } + // else; retry + } + } + + @Override + public void signal() + { + Object oWaitHead = m_oWaitHead; + if (oWaitHead == null) + { + // nobody waiting; nothing to do + } + else if (oWaitHead instanceof Thread && s_fuHead.compareAndSet(this, oWaitHead, null)) + { + // common case, just one thread waiting, and we win the CAS on first attempt + LockSupport.unpark((Thread) oWaitHead); + } + else + { + // take the slow path + signalInternal(); + } + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Full version of signal. + */ + protected void signalInternal() + { + for (Object oWaitHead = m_oWaitHead; oWaitHead != null; oWaitHead = m_oWaitHead) + { + if (s_fuHead.compareAndSet(this, oWaitHead, null)) + { + // we've signaled with waiting thread(s) + if (oWaitHead instanceof Link) + { + for (Link link = (Link) oWaitHead; link != null; ) + { + LockSupport.unpark(link.thread); + + Link linkLast = link; + + link = link.next; + linkLast.next = null; // helps avoid pulling new stuff into old-gen + } + } + else // single waiting thread + { + LockSupport.unpark((Thread) oWaitHead); + } + + // we've unparked all threads we are responsible for; if another cas'd in after our cas + // they will rely on {@link #isReady} or appropriately wait for the next call to signal + return; + } + } + } + + + /** + * Block the calling thread if the notifier is not ready. + * + * @param cMillis the time to block for + * + * @throws InterruptedException if the calling thread is interrupted + */ + protected void park(long cMillis) + throws InterruptedException + { + if (!isReady()) + { + if (cMillis == 0) + { + Blocking.park(/*blocker*/ this); + } + else + { + Blocking.parkNanos(/*blocker*/ this, cMillis * 1000000); + } + } + + if (m_oWaitHead != null && Blocking.interrupted()) // only pay the cost of interrupt check if we may not have been signaled + { + throw new InterruptedException(); + } + } + + /** + * Make a link for the calling thread, checking if one already exists for this notifier. + * + * @param threadThis the calling thread + * @param lBitThis this thread's contribution to the bloom filter + * @param oHead the current head + * + * @return this thread's link, or null if we should not block + */ + protected Link makeSelfLink(Thread threadThis, long lBitThis, Object oHead) + { + // It's possible this thread is already in the chain, but this can only happen if we had a induced + // wakeup (ready-check, timeout, spurious) while waiting on this notifier previously. + // We could try to move this check to after we wakeup, but this will be more harder + // to do, and we're about to block anyway so we might as well do it now. We make use of a bloom + // filter to avoid scanning any deeper into the chain then is absolutely necessary, which in many + // cases may eliminate the scan entirely. + + Link linkHead; + if (oHead == threadThis) + { + return null; // we're already there + } + else if (oHead instanceof Link) + { + linkHead = (Link) oHead; + for (Link link = linkHead; link != null && (link.lFilterThreads & lBitThis) != 0L; link = link.next) + { + if (link.thread == threadThis) + { + return null; // we're already there + } + } + } + else + { + linkHead = new Link((Thread) oHead); + linkHead.lFilterThreads = 1L << (oHead.hashCode() % 61); + } + + Link linkThis = new Link(threadThis); + + linkThis.next = linkHead; + linkThis.lFilterThreads = lBitThis | linkHead.lFilterThreads; + return linkThis; + } + + /** + * Return true if the notifier is ready, i.e. threads entering await cant return without blocking. + * + * @return true if the notifier is ready + */ + abstract protected boolean isReady(); + + + // ----- inner class: Link ---------------------------------------------- + + /** + * A link in a stack of waiting threads. + */ + protected static final class Link + { + /** + * Construct a new Link for a given thread. + * + * @param thread the thread + */ + Link(Thread thread) + { + this.thread = thread; + } + + /** + * This waiting thread. + */ + final Thread thread; + + /** + * A bloom filter of the waiting threads. + */ + long lFilterThreads; + + /** + * The next waiting thread. + */ + Link next; + } + + // ----- data members --------------------------------------------------- + + /** + * The head of a stack of waiting threads. The head can be either a Thread, or a Link. + */ + protected volatile Object m_oWaitHead; + + /** + * The atomic field updater for {@link #m_oWaitHead}. + */ + private static final AtomicReferenceFieldUpdater s_fuHead = + AtomicReferenceFieldUpdater.newUpdater(ConcurrentNotifier.class, Object.class, "m_oWaitHead"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Continuation.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Continuation.java new file mode 100644 index 0000000000000..80eb776d0d8dc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Continuation.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * Continuation interface is used to implement asynchronous post-processing, + * the pattern that is also known as the + * + * "Continuation-passing style". + *

+ * Most commonly, a continuation can be used to encode a single program control + * mechanism (i.e. a logical "return"). Advanced usages may also need to encode + * multiple control mechanisms (e.g. an exceptional execution path). For such + * usages, each control path could be explicitly represented as a separate + * Continuation, e.g.: + *

+ * void doAsync(Continuation<Result> contNormal, Continuation<Exception> contExceptional);
+ * 
+ * + * @param the result type + * + * @author gg 02.17.2011 + */ +public interface Continuation + { + /** + * Resume the execution after the completion of an asynchronous call. + * + * @param r the result of the execution preceding this continuation + */ + public void proceed(R r); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Converter.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Converter.java new file mode 100644 index 0000000000000..55d325e7e6904 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Converter.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +import java.util.function.Function; + + +/** + * Provide for "pluggable" object conversions. + * + * @param the from type + * @param the to type + * + * @author pm 2000.04.25 + */ +@FunctionalInterface +public interface Converter + extends Function + { + /** + * Convert the passed object to another object. + * + * @param value the object to convert + * + * @return the converted form of the passed object + * + * @throws IllegalArgumentException describes a conversion error + */ + public T convert(F value); + + @Override + public default T apply(F value) + { + return convert(value); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Disposable.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Disposable.java new file mode 100644 index 0000000000000..575c66101a4d3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Disposable.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * The Disposable interface is used for life-cycle management of resources. + * + * Disposable is also AutoCloseable and thus is compatible with the try-with-resources pattern. + * + * @author ch 2010.01.11 + */ +public interface Disposable + extends AutoCloseable + { + /** + * Invoked when all resources owned by the implementer can safely be + * released. + *

+ * Once disposed of the object should no longer be considered to be + * usable. + *

+ * Note the Disposable interface is compatible with try-with-resources which will automatically + * invoke this method. + */ + public void dispose(); + + /** + * Default implementation invokes {@link #dispose}, it is not recommended that this be overridden. + */ + @Override + public default void close() + { + dispose(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Exceptions.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Exceptions.java new file mode 100644 index 0000000000000..11433e3cfac32 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Exceptions.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + +import java.rmi.RemoteException; + + +/** + * Class for providing exception support. + * + * @author cp 2000.08.02 + * @since Coherence 12.4.1 + */ + +public abstract class Exceptions + { + // ----- exception support ---------------------------------------------- + + /** + * Convert the passed exception to a RuntimeException if necessary. + * + * @param e any Throwable object + * + * @return a RuntimeException + */ + public static RuntimeException ensureRuntimeException(Throwable e) + { + if (e instanceof RuntimeException) + { + return (RuntimeException) e; + } + else + { + return ensureRuntimeException(e, e.getMessage()); + } + } + + /** + * Convert the passed exception to a RuntimeException if necessary. + * + * @param e any Throwable object + * @param s an additional description + * + * @return a RuntimeException + */ + public static RuntimeException ensureRuntimeException(Throwable e, String s) + { + if (e instanceof RuntimeException && s == null) + { + return (RuntimeException) e; + } + else + { + return new RuntimeException(s, e); + } + } + + /** + * Unwind the wrapper (runtime) exception to extract the original + * + * @param e Runtime exception (wrapper) + * + * @return an original wrapped exception + */ + public static Throwable getOriginalException(RuntimeException e) + { + Throwable t = e; + + while (true) + { + if (t instanceof RuntimeException) + { + t = t.getCause(); + } + else if (t instanceof RemoteException) + { + t = ((RemoteException) t).detail; + } + // we do not want to have runtime dependency on j2ee classes + else if (t.getClass().getName().equals("javax.ejb.EJBException")) + { + try + { + t = (Throwable) Classes.invoke(t, + "getCausedByException", Classes.VOID); + } + catch (Exception x) + { + return t; + } + } + else + { + return t; + } + } + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Factory.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Factory.java new file mode 100644 index 0000000000000..c855dedd18ee6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Factory.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * The Factory interface provides a means of producing objects of a given + * type. + * + * @param the type of the created object + * + * @author mf 2010.11.23 + */ +public interface Factory + { + /** + * Create a new instance. + *

+ * If the produced object requires constructor parameters, they must be + * externally communicated to the factory. + * + * @return a new instance + */ + public T create(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Formatting.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Formatting.java new file mode 100644 index 0000000000000..59f45630e1699 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Formatting.java @@ -0,0 +1,2013 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +import com.tangosol.util.ByteSequence; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + + +/** + * Class for providing formatting functionality for various types. + * + * @author cp 2000.08.02 + * @since Coherence 12.4.1 + */ + +public abstract class Formatting + { + // ----- formatting support: character/String --------------------------- + + /** + * Format a double value as a String. + * + * @param dfl a double value + * @param cMinDigits the minimum number of digits of precision to display + * + * @return the double value formatted as a String + */ + public static String toString(double dfl, int cMinDigits) + { + BigDecimal decVal = new BigDecimal(dfl); + BigInteger intVal = decVal.toBigInteger(); + String sIntVal = intVal.toString(); + int cIntDigits = sIntVal.length() - (intVal.signum() <= 0 ? 1 : 0); + int cDecDigits = decVal.scale(); + if (cIntDigits >= cMinDigits || cDecDigits == 0) + { + return sIntVal; + } + + int cRemDigits = cMinDigits - cIntDigits; + if (cDecDigits > cRemDigits) + { + decVal = decVal.setScale(cRemDigits, BigDecimal.ROUND_HALF_UP); + } + + String sDecVal = decVal.toString(); + int of = sDecVal.length() - 1; + if (sDecVal.length() > 1 && sDecVal.charAt(of) == '0') + { + do + { + --of; + } + while (sDecVal.charAt(of) == '0'); + if (sDecVal.charAt(of) == '.') + { + --of; + } + sDecVal = sDecVal.substring(0, of + 1); + } + + return sDecVal; + } + + /** + * Format a Unicode character to the Unicode escape sequence of '\' + * + 'u' + 4 hex digits. + * + * @param ch the character + * @return the Unicode escape sequence + */ + public static String toUnicodeEscape(char ch) + { + int n = ch; + char[] ach = new char[6]; + + ach[0] = '\\'; + ach[1] = 'u'; + ach[2] = HEX[n >> 12]; + ach[3] = HEX[n >> 8 & 0x0F]; + ach[4] = HEX[n >> 4 & 0x0F]; + ach[5] = HEX[n & 0x0F]; + + return new String(ach); + } + + /** + * Format a char to a printable escape if necessary. + * + * @param ch the char + * @return a printable String representing the passed char + */ + public static String toCharEscape(char ch) + { + char[] ach = new char[6]; + int cch = escape(ch, ach, 0); + return new String(ach, 0, cch); + } + + /** + * Format a char to a printable escape if necessary as it would + * appear (quoted) in Java source code. + *

+ * This is a replacement for Text.printableChar(). + * + * @param ch the character + * @return a printable String in single quotes representing the + * passed char + */ + public static String toQuotedCharEscape(char ch) + { + char[] ach = new char[8]; + ach[0] = '\''; + int cch = escape(ch, ach, 1); + ach[cch + 1] = '\''; + return new String(ach, 0, cch + 2); + } + + /** + * Format a String escaping characters as necessary. + * + * @param s the String + * @return a printable String representing the passed String + */ + public static String toStringEscape(String s) + { + char[] achSrc = s.toCharArray(); + int cchSrc = achSrc.length; + int ofSrc = 0; + + int cchDest = cchSrc * 6; // 100% safe + char[] achDest = new char[cchDest]; + int ofDest = 0; + + while (ofSrc < cchSrc) + { + ofDest += escape(achSrc[ofSrc++], achDest, ofDest); + } + + return new String(achDest, 0, ofDest); + } + + /** + * Format a String as it would appear (quoted) in Java source code, + * escaping characters as necessary. + *

+ * This is a replacement for Text.printableString(). + * + * @param s the String + * @return a printable String in double quotes representing the + * passed String + */ + public static String toQuotedStringEscape(String s) + { + char[] achSrc = s.toCharArray(); + int cchSrc = achSrc.length; + int ofSrc = 0; + + int cchDest = cchSrc * 6 + 2; // 100% safe + char[] achDest = new char[cchDest]; + int ofDest = 0; + + achDest[ofDest++] = '\"'; + while (ofSrc < cchSrc) + { + ofDest += escape(achSrc[ofSrc++], achDest, ofDest); + } + achDest[ofDest++] = '\"'; + + return new String(achDest, 0, ofDest); + } + + /** + * Format a char to a printable escape if necessary, putting the result + * into the passed array. The array must be large enough to accept six + * characters. + * + * @param ch the character to format + * @param ach the array of characters to format into + * @param of the offset in the array to format at + * @return the number of characters used to format the char + */ + public static int escape(char ch, char[] ach, int of) + { + switch (ch) + { + case '\b': + ach[of++] = '\\'; + ach[of] = 'b'; + return 2; + case '\t': + ach[of++] = '\\'; + ach[of] = 't'; + return 2; + case '\n': + ach[of++] = '\\'; + ach[of] = 'n'; + return 2; + case '\f': + ach[of++] = '\\'; + ach[of] = 'f'; + return 2; + case '\r': + ach[of++] = '\\'; + ach[of] = 'r'; + return 2; + case '\"': + ach[of++] = '\\'; + ach[of] = '\"'; + return 2; + case '\'': + ach[of++] = '\\'; + ach[of] = '\''; + return 2; + case '\\': + ach[of++] = '\\'; + ach[of] = '\\'; + return 2; + + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x0B: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + ach[of++] = '\\'; + ach[of++] = '0'; + ach[of++] = (char) (ch / 8 + '0'); + ach[of] = (char) (ch % 8 + '0'); + return 4; + + default: + switch (Character.getType(ch)) + { + case Character.CONTROL: + case Character.PRIVATE_USE: + case Character.UNASSIGNED: + { + int n = ch; + ach[of++] = '\\'; + ach[of++] = 'u'; + ach[of++] = HEX[n >> 12]; + ach[of++] = HEX[n >> 8 & 0x0F]; + ach[of++] = HEX[n >> 4 & 0x0F]; + ach[of] = HEX[n & 0x0F]; + } + return 6; + } + } + + // character does not need to be escaped + ach[of] = ch; + return 1; + } + + /** + * Escapes the string for SQL. + * + * @return the string quoted for SQL and escaped as necessary + */ + public static String toSqlString(String s) + { + if (s == null) + { + return "NULL"; + } + + if (s.length() == 0) + { + return "''"; + } + + if (s.indexOf('\'') < 0) + { + return '\'' + s + '\''; + } + + char[] ach = s.toCharArray(); + int cch = ach.length; + + StringBuilder sb = new StringBuilder(cch + 16); + + // open quotes + sb.append('\''); + + // scan for characters to escape + int ofPrev = 0; + for (int ofCur = 0; ofCur < cch; ++ofCur) + { + char ch = ach[ofCur]; + + switch (ch) + { + case '\n': + case '\'': + { + // copy up to this point + if (ofCur > ofPrev) + { + sb.append(ach, ofPrev, ofCur - ofPrev); + } + + // process escape + switch (ch) + { + case '\n': + // close quote, new line, re-open quote + sb.append("\'\n\'"); + break; + case '\'': + // escape single quote with a second single quote + sb.append("\'\'"); + break; + } + + // processed up to the following character + ofPrev = ofCur + 1; + } + } + } + + // copy the remainder of the string + if (ofPrev < cch) + { + sb.append(ach, ofPrev, cch - ofPrev); + } + + // close quotes + sb.append('\''); + + return sb.toString(); + } + + /** + * Indent the passed multi-line string. + * + * @param sText the string to indent + * @param sIndent a string used to indent each line + * @return the string, indented + */ + public static String indentString(String sText, String sIndent) + { + return indentString(sText, sIndent, true); + } + + /** + * Textually indent the passed multi-line string. + * + * @param sText the string to indent + * @param sIndent a string used to indent each line + * @param fFirstLine true indents all lines; + * false indents all but the first + * @return the string, indented + */ + public static String indentString(String sText, String sIndent, boolean fFirstLine) + { + char[] ach = sText.toCharArray(); + int cch = ach.length; + + StringBuilder sb = new StringBuilder(); + + int iLine = 0; + int of = 0; + int ofPrev = 0; + while (of < cch) + { + if (ach[of++] == '\n' || of == cch) + { + if (iLine++ > 0 || fFirstLine) + { + sb.append(sIndent); + } + + sb.append(sText.substring(ofPrev, of)); + ofPrev = of; + } + } + + return sb.toString(); + } + + /** + * Breaks the specified string into a multi-line string. + * + * @param sText the string to break + * @param nWidth the max width of resulting lines (including the indent) + * @param sIndent a string used to indent each line + * @return the string, broken and indented + */ + public static String breakLines(String sText, int nWidth, String sIndent) + { + return breakLines(sText, nWidth, sIndent, true); + } + + /** + * Breaks the specified string into a multi-line string. + * + * @param sText the string to break + * @param nWidth the max width of resulting lines (including the + * indent) + * @param sIndent a string used to indent each line + * @param fFirstLine if true indents all lines; + * otherwise indents all but the first + * @return the string, broken and indented + */ + public static String breakLines(String sText, int nWidth, String sIndent, boolean fFirstLine) + { + if (sIndent == null) + { + sIndent = ""; + } + + nWidth -= sIndent.length(); + if (nWidth <= 0) + { + throw new IllegalArgumentException("The width and indent are incompatible"); + } + + char[] ach = sText.toCharArray(); + int cch = ach.length; + + StringBuilder sb = new StringBuilder(cch); + + int ofPrev = 0; + int of = 0; + + while (of < cch) + { + char c = ach[of++]; + + boolean fBreak = false; + int ofBreak = of; + int ofNext = of; + + if (c == '\n') + { + fBreak = true; + ofBreak--; + } + else if (of == cch) + { + fBreak = true; + } + else if (of == ofPrev + nWidth) + { + fBreak = true; + + while (!Character.isWhitespace(ach[--ofBreak]) && ofBreak > ofPrev) + { + } + if (ofBreak == ofPrev) + { + ofBreak = of; // no spaces -- force the break + } + else + { + ofNext = ofBreak + 1; + } + } + + if (fBreak) + { + if (ofPrev > 0) + { + sb.append('\n') + .append(sIndent); + } + else if (fFirstLine) + { + sb.append(sIndent); + } + + sb.append(sText.substring(ofPrev, ofBreak)); + + ofPrev = ofNext; + } + + } + + return sb.toString(); + } + + /** + * Create a String of the specified length containing the specified + * character. + * + * @param ch the character to fill the String with + * @param cch the length of the String + * @return a String containing the character repeated times + */ + public static String dup(char ch, int cch) + { + char[] ach = new char[cch]; + for (int of = 0; of < cch; ++of) + { + ach[of] = ch; + } + return new String(ach); + } + + /** + * Create a String which is a duplicate of the specified number of the + * passed String. + * + * @param s the String to fill the new String with + * @param c the number of duplicates to put into the new String + * @return a String containing the String s repeated c + * times + */ + public static String dup(String s, int c) + { + if (c < 1) + { + return ""; + } + if (c == 1) + { + return s; + } + + char[] achPat = s.toCharArray(); + int cchPat = achPat.length; + int cchBuf = cchPat * c; + char[] achBuf = new char[cchBuf]; + for (int i = 0, of = 0; i < c; ++i, of += cchPat) + { + System.arraycopy(achPat, 0, achBuf, of, cchPat); + } + return new String(achBuf); + } + + /** + * Replace all occurrences of the specified substring in the specified + * string. + * + * @param sText string to change + * @param sFrom pattern to change from + * @param sTo pattern to change to + * @return modified string + */ + public static String replace(String sText, String sFrom, String sTo) + { + if (sFrom.length() == 0) + { + return sText; + } + + StringBuilder sbTextNew = new StringBuilder(); + int iTextLen = sText.length(); + int iStart = 0; + + while (iStart < iTextLen) + { + int iPos = sText.indexOf(sFrom, iStart); + if (iPos != -1) + { + sbTextNew.append(sText.substring(iStart, iPos)); + sbTextNew.append(sTo); + iStart = iPos + sFrom.length(); + } + else + { + sbTextNew.append(sText.substring(iStart)); + break; + } + } + + return sbTextNew.toString(); + } + + /** + * Parse a character-delimited String into an array of Strings. + * + * @param s character-delimited String to parse + * @param chDelim character delimiter + * @return an array of String objects parsed from the passed String + */ + public static String[] parseDelimitedString(String s, char chDelim) + { + if (s == null) + { + return null; + } + + List list = new ArrayList(); + int ofPrev = -1; + while (true) + { + int ofNext = s.indexOf(chDelim, ofPrev + 1); + if (ofNext < 0) + { + list.add(s.substring(ofPrev + 1)); + break; + } + else + { + list.add(s.substring(ofPrev + 1, ofNext)); + } + + ofPrev = ofNext; + } + + return (String[]) list.toArray(new String[list.size()]); + } + + /** + * Format the content of the passed integer array as a delimited string. + * + * @param an the array + * @param sDelim the delimiter + * @return the formatted string + */ + public static String toDelimitedString(int[] an, String sDelim) + { + int c = an.length; + if (c > 0) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < c; i++) + { + sb.append(sDelim).append(an[i]); + } + return sb.substring(sDelim.length()); + } + else + { + return ""; + } + } + + /** + * Format the content of the passed long array as a delimited string. + * + * @param al the array + * @param sDelim the delimiter + * @return the formatted string + */ + public static String toDelimitedString(long[] al, String sDelim) + { + int c = al.length; + if (c > 0) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < c; i++) + { + sb.append(sDelim).append(al[i]); + } + return sb.substring(sDelim.length()); + } + else + { + return ""; + } + } + + /** + * Format the content of the passed Object array as a delimited string. + * + * @param ao the array + * @param sDelim the delimiter + * @return the formatted string + */ + public static String toDelimitedString(Object[] ao, String sDelim) + { + int c = ao.length; + if (c > 0) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < c; i++) + { + sb.append(sDelim).append(ao[i]); + } + return sb.substring(sDelim.length()); + } + else + { + return ""; + } + } + + /** + * Format the content of the passed Iterator as a delimited string. + * + * @param iter the Iterator + * @param sDelim the delimiter + * @return the formatted string + */ + public static String toDelimitedString(Iterator iter, String sDelim) + { + StringBuilder sb = new StringBuilder(); + while (iter.hasNext()) + { + sb.append(sDelim).append(iter.next()); + } + return sb.length() == 0 ? "" : sb.substring(sDelim.length()); + } + + /** + * Capitalize a string. + * + * @param s the string to capitalize + * @return the capitalized string + */ + public static String capitalize(String s) + { + return s.length() > 1 + ? s.substring(0, 1).toUpperCase() + s.substring(1) + : s.toUpperCase(); + } + + /** + * Truncate a string to the specified character count. + * + * @param s the String to be truncated + * @param cLimit expected character count + * @return the truncated String + */ + public static String truncateString(String s, int cLimit) + { + int cChar = s.length(); + return cChar > cLimit + ? s.substring(0, cLimit) + "...(" + (cChar - cLimit) + " more)" + : s; + } + + /** + * Provide a string representation of elements within the collection until + * the concatenated string length exceeds {@code cLimit}. + * + * @param coll the collection of elements to describe + * @param cLimit expected character count + * @return the truncated string representation of the provided collection + */ + public static String truncateString(Collection coll, int cLimit) + { + StringBuilder sb = new StringBuilder(Classes.getSimpleName(coll.getClass())) + .append('['); + + cLimit += sb.length() + 1; + + int c = 1; + for (Iterator iter = coll.iterator(); iter.hasNext() && sb.length() < cLimit; ++c) + { + if (c > 1) + { + sb.append(", "); + } + sb.append(iter.next()); + } + + if (c < coll.size() && sb.length() >= cLimit) + { + sb.append(", ..."); + } + + return sb.append(']').toString(); + } + + + // ----- formatting support: hex values --------------------------------- + + /** + * Returns true if the passed character is a hexadecimal digit. + * + * @param ch The character to check + */ + public static boolean isHex(char ch) + { + return ((ch >= '0') && (ch <= '9')) + || ((ch >= 'A') && (ch <= 'F')) + || ((ch >= 'a') && (ch <= 'f')); + } + + /** + * Returns the integer value of a hexadecimal digit. + * + * @param ch The character to convert + */ + public static int hexValue(char ch) + { + if ((ch >= '0') && (ch <= '9')) + { + return ch - '0'; + } + else if ((ch >= 'A') && (ch <= 'F')) + { + return ch - 'A' + 10; + } + else if ((ch >= 'a') && (ch <= 'f')) + { + return ch - 'a' + 10; + } + else + { + throw new IllegalArgumentException("Character \"" + ch + "\" is not a valid hexadecimal digit."); + } + } + + /** + * Calculate the number of hex digits needed to display the passed value. + * + * @param n the value + * @return the number of hex digits needed to display the value + */ + public static int getMaxHexDigits(int n) + { + int cDigits = 0; + do + { + cDigits++; + n >>>= 4; + } + while (n != 0); + + return cDigits; + } + + /** + * Format the passed integer as a fixed-length hex string. + * + * @param n the value + * @param cDigits the length of the resulting hex string + * @return the hex value formatted to the specified length string + */ + public static String toHexString(int n, int cDigits) + { + char[] ach = new char[cDigits]; + while (cDigits > 0) + { + ach[--cDigits] = HEX[n & 0x0F]; + n >>>= 4; + } + + return new String(ach); + } + + /** + * Convert a byte to the hex sequence of 2 hex digits. + * + * @param b the byte + * @return the hex sequence + */ + public static String toHex(int b) + { + int n = b & 0xFF; + char[] ach = new char[2]; + + ach[0] = HEX[n >> 4]; + ach[1] = HEX[n & 0x0F]; + + return new String(ach); + } + + /** + * Convert a byte array to the hex sequence of 2 hex digits per byte. + *

+ * This is a replacement for Text.toString(char[]). + * + * @param ab the byte array + * @return the hex sequence + */ + public static String toHex(byte[] ab) + { + int cb = ab.length; + char[] ach = new char[cb * 2]; + + for (int ofb = 0, ofch = 0; ofb < cb; ++ofb) + { + int n = ab[ofb] & 0xFF; + ach[ofch++] = HEX[n >> 4]; + ach[ofch++] = HEX[n & 0x0F]; + } + + return new String(ach); + } + + /** + * Convert a byte to a hex sequence of '0' + 'x' + 2 hex digits. + * + * @param b the byte + * @return the hex sequence + */ + public static String toHexEscape(byte b) + { + int n = b & 0xFF; + char[] ach = new char[4]; + + ach[0] = '0'; + ach[1] = 'x'; + ach[2] = HEX[n >> 4]; + ach[3] = HEX[n & 0x0F]; + + return new String(ach); + } + + /** + * Convert a byte array to a hex sequence of '0' + 'x' + 2 hex digits + * per byte. + * + * @param ab the byte array + * @return the hex sequence + */ + public static String toHexEscape(byte[] ab) + { + return toHexEscape(ab, 0, ab.length); + } + + /** + * Convert a byte array to a hex sequence of '0' + 'x' + 2 hex digits + * per byte. + * + * @param ab the byte array + * @param of the offset into array + * @param cb the number of bytes to convert + * @return the hex sequence + */ + public static String toHexEscape(byte[] ab, int of, int cb) + { + char[] ach = new char[2 + cb * 2]; + + ach[0] = '0'; + ach[1] = 'x'; + + for (int ofb = of, ofch = 2, ofStop = of + cb; ofb < ofStop; ++ofb) + { + int n = ab[ofb] & 0xFF; + ach[ofch++] = HEX[n >> 4]; + ach[ofch++] = HEX[n & 0x0F]; + } + + return new String(ach); + } + + /** + * Convert a ByteSequence to a hex sequence of '0' + 'x' + 2 hex digits + * per byte. + * + * @param seq the ByteSequence + * @param of the offset into the byte sequence + * @param cb the number of bytes to convert + * + * @return the hex sequence + * + * @since Coherence 3.7 + */ + public static String toHexEscape(ByteSequence seq, int of, int cb) + { + if (cb > 0) + { + char[] ach = new char[2 + cb * 2]; + + ach[0] = '0'; + ach[1] = 'x'; + + for (int ofb = of, ofch = 2, ofStop = of + cb; ofb < ofStop; ++ofb) + { + int n = seq.byteAt(ofb) & 0xFF; + ach[ofch++] = HEX[n >> 4]; + ach[ofch++] = HEX[n & 0x0F]; + } + + return new String(ach); + } + else + { + return ""; + } + } + + /** + * Convert a byte array to a hex dump. + *

+ * This is a replacement for Text.toString(byte[] ab, int cBytesPerLine). + * + * @param ab the byte array to format as a hex string + * @param cBytesPerLine the number of bytes to display on a line + * @return a multi-line hex dump + */ + public static String toHexDump(byte[] ab, int cBytesPerLine) + { + int cb = ab.length; + if (cb == 0) + { + return ""; + } + + // calculate number of digits required to show offset + int cDigits = 0; + int cbTemp = cb - 1; + do + { + cDigits += 2; + cbTemp /= 0x100; + } + while (cbTemp > 0); + + // calculate number and size of lines + int cLines = (cb + cBytesPerLine - 1) / cBytesPerLine; + int cCharsPerLine = cDigits + 4 * cBytesPerLine + 5; + + // pre-allocate buffer to build hex dump + int cch = cLines * cCharsPerLine; + char[] ach = new char[cch]; + + // offsets within each line for formatting stuff + int ofColon = cDigits; + int ofLF = cCharsPerLine - 1; + + // offsets within each line for data + int ofHexInLine = ofColon + 3; + int ofCharInLine = ofLF - cBytesPerLine; + + int ofByte = 0; + int ofLine = 0; + for (int iLine = 0; iLine < cLines; ++iLine) + { + // format offset + int nOffset = ofByte; + int ofDigit = ofLine + cDigits; + for (int i = 0; i < cDigits; ++i) + { + ach[--ofDigit] = HEX[nOffset & 0x0F]; + nOffset >>= 4; + } + + // formatting + int ofFmt = ofLine + cDigits; + ach[ofFmt++] = ':'; + ach[ofFmt++] = ' '; + ach[ofFmt] = ' '; + + // format data + int ofHex = ofLine + ofHexInLine; + int ofChar = ofLine + ofCharInLine; + for (int i = 0; i < cBytesPerLine; ++i) + { + try + { + int n = ab[ofByte++] & 0xFF; + + ach[ofHex++] = HEX[(n & 0xF0) >> 4]; + ach[ofHex++] = HEX[(n & 0x0F)]; + ach[ofHex++] = ' '; + ach[ofChar++] = (n < 32 ? '.' : (char) n); + } + catch (ArrayIndexOutOfBoundsException e) + { + ach[ofHex++] = ' '; + ach[ofHex++] = ' '; + ach[ofHex++] = ' '; + ach[ofChar++] = ' '; + } + } + + // spacing and newline + ach[ofHex] = ' '; + ach[ofChar] = '\n'; + + ofLine += cCharsPerLine; + } + + return new String(ach, 0, cch - 1); + } + + /** + * Parse the passed String of hexadecimal characters into a binary + * value. This implementation allows the passed String to be prefixed + * with "0x". + * + * @param s the hex String to evaluate + * @return the byte array value of the passed hex String + */ + public static byte[] parseHex(String s) + { + char[] ach = s.toCharArray(); + int cch = ach.length; + if (cch == 0) + { + return new byte[0]; + } + + if ((cch & 0x1) != 0) + { + throw new IllegalArgumentException("invalid length hex string"); + } + + int ofch = 0; + if (ach[1] == 'x' || ach[1] == 'X') + { + ofch = 2; + } + + int cb = (cch - ofch) / 2; + byte[] ab = new byte[cb]; + for (int ofb = 0; ofb < cb; ++ofb) + { + ab[ofb] = (byte) (parseHex(ach[ofch++]) << 4 | parseHex(ach[ofch++])); + } + + return ab; + } + + /** + * Return the integer value of a hexadecimal digit. + * + * @param ch the hex character to evaluate + * @return the integer value of the passed hex character + */ + public static int parseHex(char ch) + { + switch (ch) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return ch - '0'; + + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return ch - 'A' + 0x0A; + + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return ch - 'a' + 0x0A; + + default: + throw new IllegalArgumentException("illegal hex char: " + ch); + } + } + + // ----- formatting support: decimal values ----------------------------- + + /** + * Returns true if the passed character is a decimal digit. + * + * @param ch The character to check + */ + public static boolean isDecimal(char ch) + { + return (ch >= '0') && (ch <= '9'); + } + + /** + * Returns the integer value of a decimal digit. + * + * @param ch The character to convert + */ + public static int decimalValue(char ch) + { + if ((ch >= '0') && (ch <= '9')) + { + return ch - '0'; + } + else + { + throw new IllegalArgumentException("Character \"" + ch + + "\" is not a valid decimal digit."); + } + } + + /** + * Calculate the number of decimal digits needed to display the passed + * value. + * + * @param n the value + * @return the number of decimal digits needed to display the value + */ + public static int getMaxDecDigits(int n) + { + int cDigits = 0; + do + { + cDigits += 1; + n /= 10; + } + while (n != 0); + + return cDigits; + } + + /** + * Format the passed non-negative integer as a fixed-length decimal string. + * + * @param n the value + * @param cDigits the length of the resulting decimal string + * @return the decimal value formatted to the specified length string + */ + public static String toDecString(int n, int cDigits) + { + char[] ach = new char[cDigits]; + while (cDigits > 0) + { + ach[--cDigits] = (char) ('0' + n % 10); + n /= 10; + } + + return new String(ach); + } + + /** + * Return the smallest value that is not less than the first argument and + * is a multiple of the second argument. Effectively rounds the first + * argument up to a multiple of the second. + * + * @param lMin the smallest value to return + * @param lMultiple the return value will be a multiple of this argument + * @return the smallest multiple of the second argument that is not less + * than the first + */ + public static long pad(long lMin, long lMultiple) + { + return ((lMin + lMultiple - 1) / lMultiple) * lMultiple; + } + + + // ----- formatting support: octal values ------------------------------- + + /** + * Returns true if the passed character is an octal digit. + * + * @param ch The character to check + */ + public static boolean isOctal(char ch) + { + return (ch >= '0') && (ch <= '7'); + } + + /** + * Returns the integer value of an octal digit. + * + * @param ch The character to convert + */ + public static int octalValue(char ch) + { + if ((ch >= '0') && (ch <= '7')) + { + return ch - '0'; + } + else + { + throw new IllegalArgumentException("Character \"" + ch + "\" is not a valid octal digit."); + } + } + + + // ----- formatting support: bandwidth ---------------------------------- + + /** + * Parse the given string representation of a number of bytes per second. + * The supplied string must be in the format: + *

+ * [\d]+[[.][\d]+]?[K|k|M|m|G|g|T|t]?[[B|b][P|p][S|s]]? + *

+ * where the first non-digit (from left to right) indicates the factor + * with which the preceding decimal value should be multiplied: + *

    + *
  • K or k (kilo, 210)
  • + *
  • M or m (mega, 220)
  • + *
  • G or g (giga, 230)
  • + *
  • T or t (tera, 240)
  • + *
+ *

+ * If the string value does not contain a factor, a factor of one is + * assumed. + *

+ * The optional last three characters indicate the unit of measure, + * [b][P|p][S|s] in the case of bits per second and + * [B][P|p][S|s] in the case of bytes per second. If the string + * value does not contain a unit, a unit of bits per second is assumed. + * + * @param s a string with the format: + * [\d]+[[.][\d]+]?[K|k|M|m|G|g|T|t]?[[B|b][P|p][S|s]]? + * + * @return the number of bytes per second represented by the given string + */ + public static long parseBandwidth(String s) + { + return parseBandwidth(s, POWER_0); + } + + /** + * Parse the given string representation of a number of bytes per second. + * The supplied string must be in the format: + *

+ * [\d]+[[.][\d]+]?[K|k|M|m|G|g|T|t]?[[B|b][P|p][S|s]]? + *

+ * where the first non-digit (from left to right) indicates the factor + * with which the preceding decimal value should be multiplied: + *

    + *
  • K or k (kilo, 210)
  • + *
  • M or m (mega, 220)
  • + *
  • G or g (giga, 230)
  • + *
  • T or t (tera, 240)
  • + *
+ *

+ * If the string value does not contain an explicit or implicit factor, a + * factor calculated by raising 2 to the given default power is used. The + * default power can be one of: + *

    + *
  • {@link #POWER_0}
  • + *
  • {@link #POWER_K}
  • + *
  • {@link #POWER_M}
  • + *
  • {@link #POWER_G}
  • + *
  • {@link #POWER_T}
  • + *
+ *

+ * The optional last three characters indicate the unit of measure, + * [b][P|p][S|s] in the case of bits per second and + * [B][P|p][S|s] in the case of bytes per second. If the string + * value does not contain a unit, a unit of bits per second is assumed. + * + * @param s a string with the format: + * [\d]+[[.][\d]+]?[K|k|M|m|G|g|T|t]?[[B|b][P|p][S|s]]? + * @param nDefaultPower the exponent used to calculate the factor used in + * the conversion if one is not implied by the given + * string + * + * @return the number of bytes per second represented by the given string + */ + public static long parseBandwidth(String s, int nDefaultPower) + { + if (s == null) + { + throw new IllegalArgumentException("passed String must not be null"); + } + + switch (nDefaultPower) + { + case POWER_0: + case POWER_K: + case POWER_M: + case POWER_G: + case POWER_T: + break; + default: + throw new IllegalArgumentException("illegal default power: " + + nDefaultPower); + } + + // remove trailing "[[P|p][S|s]]?" + int cch = s.length(); + if (cch >= 2) + { + char ch = s.charAt(cch - 1); + if (ch == 'S' || ch == 's') + { + ch = s.charAt(cch - 2); + if (ch == 'P' || ch == 'p') + { + cch -= 2; + } + else + { + throw new IllegalArgumentException("invalid bandwidth: \"" + + s + "\" (illegal bandwidth unit)"); + } + } + } + + // remove trailing "[B|b]?" and store it as a factor + // (default is "bps" i.e. baud) + int cBitShift = -3; + boolean fDefault = true; + if (cch >= 1) + { + switch (s.charAt(cch - 1)) + { + case 'B': + cBitShift = 0; + // no break; + case 'b': + --cch; + fDefault = false; + break; + } + } + + // remove trailing "[K|k|M|m|G|g|T|t]?[B|b]?" and update the factor + if (cch >= 1) + { + switch (s.charAt(--cch)) + { + case 'K': case 'k': + cBitShift += POWER_K; + break; + + case 'M': case 'm': + cBitShift += POWER_M; + break; + + case 'G': case 'g': + cBitShift += POWER_G; + break; + + case 'T': case 't': + cBitShift += POWER_T; + break; + + default: + if (fDefault) + { + cBitShift += nDefaultPower; + } + ++cch; // oops: shouldn't have chopped off the last char + break; + } + } + + // make sure that the string contains some digits + if (cch == 0) + { + throw new NumberFormatException("passed String (\"" + s + + "\") must contain a number"); + } + + // extract the digits (decimal form) to assemble the base number + long cb = 0; + boolean fDecimal = false; + int nDivisor = 1; + for (int of = 0; of < cch; ++of) + { + char ch = s.charAt(of); + switch (ch) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + cb = (cb * 10) + (ch - '0'); + if (fDecimal) + { + nDivisor *= 10; + } + break; + + case '.': + if (fDecimal) + { + throw new NumberFormatException("invalid bandwidth: \"" + + s + "\" (illegal second decimal point)"); + } + fDecimal = true; + break; + + default: + throw new NumberFormatException("invalid bandwidth: \"" + + s + "\" (illegal digit: \"" + ch + "\")"); + } + } + + if (cBitShift < 0) + { + cb >>>= -cBitShift; + } + else + { + cb <<= cBitShift; + } + + if (fDecimal) + { + if (nDivisor == 1) + { + throw new NumberFormatException("invalid bandwidth: \"" + + s + "\" (illegal trailing decimal point)"); + } + else + { + cb /= nDivisor; + } + } + + return cb; + } + + /** + * Format the passed bandwidth (in bytes per second) as a String that can + * be parsed by {@link #parseBandwidth} such that + * cb==parseBandwidth(toBandwidthString(cb)) holds true for + * all legal values of cbps. + * + * @param cbps the number of bytes per second + * + * @return a String representation of the given bandwidth + */ + public static String toBandwidthString(long cbps) + { + return toBandwidthString(cbps, true); + } + + /** + * Format the passed bandwidth (in bytes per second) as a String. This + * method will possibly round the memory size for purposes of producing a + * more-easily read String value unless the fExact parameters is + * passed as true; if fExact is true, then + * cb==parseBandwidth(toBandwidthString(cb, true)) holds true + * for all legal values of cbps. + * + * @param cbps the number of bytes per second + * @param fExact true if the String representation must be exact, or + * false if it can be an approximation + * + * @return a String representation of the given bandwidth + */ + public static String toBandwidthString(long cbps, boolean fExact) + { + boolean fBits = (cbps & 0xF00000000000000L) == 0L; + + if (fBits) + { + cbps <<= 3; + } + + StringBuilder sb = new StringBuilder(toMemorySizeString(cbps, fExact)); + int ofLast = sb.length() - 1; + if (sb.charAt(ofLast) == 'B') + { + if (fBits) + { + sb.setCharAt(ofLast, 'b'); + } + } + else + { + sb.append(fBits ? 'b' : 'B'); + } + sb.append("ps"); + + return sb.toString(); + } + + + // ----- formatting support: memory size -------------------------------- + + /** + * Parse the given string representation of a number of bytes. The + * supplied string must be in the format: + *

+ * [\d]+[[.][\d]+]?[K|k|M|m|G|g|T|t]?[B|b]? + *

+ * where the first non-digit (from left to right) indicates the factor + * with which the preceding decimal value should be multiplied: + *

    + *
  • K or k (kilo, 210)
  • + *
  • M or m (mega, 220)
  • + *
  • G or g (giga, 230)
  • + *
  • T or t (tera, 240)
  • + *
+ *

+ * If the string value does not contain a factor, a factor of one is + * assumed. + *

+ * The optional last character B or b indicates a unit + * of bytes. + * + * @param s a string with the format + * [\d]+[[.][\d]+]?[K|k|M|m|G|g|T|t]?[B|b]? + * + * @return the number of bytes represented by the given string + */ + public static long parseMemorySize(String s) + { + return parseMemorySize(s, POWER_0); + } + + /** + * Parse the given string representation of a number of bytes. The + * supplied string must be in the format: + *

+ * [\d]+[[.][\d]+]?[K|k|M|m|G|g|T|t]?[B|b]? + *

+ * where the first non-digit (from left to right) indicates the factor + * with which the preceding decimal value should be multiplied: + *

    + *
  • K or k (kilo, 210)
  • + *
  • M or m (mega, 220)
  • + *
  • G or g (giga, 230)
  • + *
  • T or t (tera, 240)
  • + *
+ *

+ * If the string value does not contain an explicit or implicit factor, a + * factor calculated by raising 2 to the given default power is used. The + * default power can be one of: + *

    + *
  • {@link #POWER_0}
  • + *
  • {@link #POWER_K}
  • + *
  • {@link #POWER_M}
  • + *
  • {@link #POWER_G}
  • + *
  • {@link #POWER_T}
  • + *
+ *

+ * The optional last character B or b indicates a unit + * of bytes. + * + * @param s a string with the format + * [\d]+[[.][\d]+]?[K|k|M|m|G|g|T|t]?[B|b]? + * @param nDefaultPower the exponent used to calculate the factor used in + * the conversion if one is not implied by the given + * string + * + * @return the number of bytes represented by the given string + */ + public static long parseMemorySize(String s, int nDefaultPower) + { + if (s == null) + { + throw new IllegalArgumentException("passed String must not be null"); + } + + switch (nDefaultPower) + { + case POWER_0: + case POWER_K: + case POWER_M: + case POWER_G: + case POWER_T: + break; + default: + throw new IllegalArgumentException("illegal default power: " + + nDefaultPower); + } + + // remove trailing "[K|k|M|m|G|g|T|t]?[B|b]?" and store it as a factor + int cBitShift = POWER_0; + int cch = s.length(); + if (cch > 0) + { + boolean fDefault; + char ch = s.charAt(cch - 1); + if (ch == 'B' || ch == 'b') + { + // bytes are implicit + --cch; + fDefault = false; + } + else + { + fDefault = true; + } + + if (cch > 0) + { + switch (s.charAt(--cch)) + { + case 'K': case 'k': + cBitShift = POWER_K; + break; + + case 'M': case 'm': + cBitShift = POWER_M; + break; + + case 'G': case 'g': + cBitShift = POWER_G; + break; + + case 'T': case 't': + cBitShift = POWER_T; + break; + + default: + if (fDefault) + { + cBitShift = nDefaultPower; + } + ++cch; // oops: shouldn't have chopped off the last char + break; + } + } + } + + // make sure that the string contains some digits + if (cch == 0) + { + throw new NumberFormatException("passed String (\"" + s + + "\") must contain a number"); + } + + // extract the digits (decimal form) to assemble the base number + long cb = 0; + boolean fDecimal = false; + int nDivisor = 1; + for (int of = 0; of < cch; ++of) + { + char ch = s.charAt(of); + switch (ch) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + cb = (cb * 10) + (ch - '0'); + if (fDecimal) + { + nDivisor *= 10; + } + break; + + case '.': + if (fDecimal) + { + throw new NumberFormatException("invalid memory size: \"" + + s + "\" (illegal second decimal point)"); + } + fDecimal = true; + break; + + default: + throw new NumberFormatException("invalid memory size: \"" + + s + "\" (illegal digit: \"" + ch + "\")"); + } + } + + cb <<= cBitShift; + if (fDecimal) + { + if (nDivisor == 1) + { + throw new NumberFormatException("invalid memory size: \"" + + s + "\" (illegal trailing decimal point)"); + } + else + { + cb /= nDivisor; + } + } + return cb; + } + + /** + * Format the passed memory size (in bytes) as a String that can be + * parsed by {@link #parseMemorySize} such that + * cb==parseMemorySize(toMemorySizeString(cb)) holds true for + * all legal values of cb. + * + * @param cb the number of bytes of memory + * + * @return a String representation of the given memory size + */ + public static String toMemorySizeString(long cb) + { + return toMemorySizeString(cb, true); + } + + /** + * Format the passed memory size (in bytes) as a String. This method will + * possibly round the memory size for purposes of producing a more-easily + * read String value unless the fExact parameters is passed as + * true; if fExact is true, then + * cb==parseMemorySize(toMemorySizeString(cb, true)) holds true + * for all legal values of cb. + * + * @param cb the number of bytes of memory + * @param fExact true if the String representation must be exact, or + * false if it can be an approximation + * + * @return a String representation of the given memory size + */ + public static String toMemorySizeString(long cb, boolean fExact) + { + if (cb < 0) + { + throw new IllegalArgumentException("negative quantity: " + cb); + } + + if (cb < 1024) + { + return String.valueOf(cb); + } + + int cDivs = 0; + int cMaxDivs = MEM_SUFFIX.length - 1; + + if (fExact) + { + // kilobytes? megabytes? gigabytes? terabytes? + while (((((int) cb) & KB_MASK) == 0) && cDivs < cMaxDivs) + { + cb >>>= 10; + ++cDivs; + } + return cb + MEM_SUFFIX[cDivs]; + } + + // need roughly the 3 most significant decimal digits + int cbRem = 0; + while (cb >= KB && cDivs < cMaxDivs) + { + cbRem = ((int) cb) & KB_MASK; + cb >>>= 10; + ++cDivs; + } + + StringBuilder sb = new StringBuilder(); + sb.append(String.valueOf(cb)); + int cch = sb.length(); + if (cch < 3 && cbRem != 0) + { + // need the first digit or two of string value of cbRem / 1024; + // format the most significant two digits ".xx" as a string "1xx" + String sDec = String.valueOf((int) (cbRem / 10.24 + 100)); + sb.append('.') + .append(sDec.substring(1, 1 + 3 - cch)); + } + sb.append(MEM_SUFFIX[cDivs]); + + return sb.toString(); + } + + // ----- CRC32 ---------------------------------------------------------- + + /** + * Calculate a CRC32 value from a byte array. + * + * @param ab an array of bytes + * + * @return the 32-bit CRC value + */ + public static int toCrc(byte[] ab) + { + return toCrc(ab, 0, ab.length); + } + + /** + * Calculate a CRC32 value from a portion of a byte array. + * + * @param ab an array of bytes + * @param of the offset into the array + * @param cb the number of bytes to evaluate + * + * @return the 32-bit CRC value + */ + public static int toCrc(byte[] ab, int of, int cb) + { + return toCrc(ab, of, cb, 0xFFFFFFFF); + } + + /** + * Continue to calculate a CRC32 value from a portion of a byte array. + * + * @param ab an array of bytes + * @param of the offset into the array + * @param cb the number of bytes to evaluate + * @param nCrc the previous CRC value + * + * @return the 32-bit CRC value + */ + public static int toCrc(byte[] ab, int of, int cb, int nCrc) + { + while (cb > 0) + { + nCrc = (nCrc >>> 8) ^ CRC32_TABLE[(nCrc ^ ab[of++]) & 0xFF]; + --cb; + } + return nCrc; + } + + /** + * Calculate a CRC32 value from a ByteSequence. + * + * @param seq a ByteSequence + * + * @return the 32-bit CRC value + */ + public static int toCrc(ByteSequence seq) + { + return toCrc(seq, 0, seq.length(), 0xFFFFFFFF); + } + + /** + * Continue to calculate a CRC32 value from a portion of a ByteSequence . + * + * @param seq a ByteSequence + * @param of the offset into the ByteSequence + * @param cb the number of bytes to evaluate + * @param nCrc the previous CRC value + * + * @return the 32-bit CRC value + */ + public static int toCrc(ByteSequence seq, int of, int cb, int nCrc) + { + while (cb > 0) + { + nCrc = (nCrc >>> 8) ^ CRC32_TABLE[(nCrc ^ seq.byteAt(of++)) & 0xFF]; + --cb; + } + return nCrc; + } + + + // ----- constants ------------------------------------------------------ + + /** + * Hex digits. + */ + public static final char[] HEX = "0123456789ABCDEF".toCharArray(); + + /** + * Memory size constants. + */ + private static final int KB = 1 << 10; + private static final int KB_MASK = KB - 1; + private static final String[] MEM_SUFFIX = {"", "KB", "MB", "GB", "TB"}; + + /** + * CRC32 constants. + */ + private static final int CRC32_BASE = 0xEDB88320; + public static final int[] CRC32_TABLE = new int[256]; + static + { + for (int i = 0, c = CRC32_TABLE.length; i < c; ++i) + { + int nCrc = i; + for (int n = 0; n < 8; ++n) + { + if ((nCrc & 1) == 1) + { + nCrc = (nCrc >>> 1) ^ CRC32_BASE; + } + else + { + nCrc >>>= 1; + } + } + CRC32_TABLE[i] = nCrc; + } + } + + /** + * Integer constant representing an exponent of zero. + * + * @see #parseBandwidth(String, int) + * @see #parseMemorySize(String, int) + */ + public static final int POWER_0 = 0; + + /** + * Integer constant representing an exponent of 10. + * + * @see #parseBandwidth(String, int) + * @see #parseMemorySize(String, int) + */ + public static final int POWER_K = 10; + + /** + * Integer constant representing an exponent of 20. + * + * @see #parseBandwidth(String, int) + * @see #parseMemorySize(String, int) + */ + public static final int POWER_M = 20; + + /** + * Integer constant representing an exponent of 30. + * + * @see #parseBandwidth(String, int) + * @see #parseMemorySize(String, int) + */ + public static final int POWER_G = 30; + + /** + * Integer constant representing an exponent of 40. + * + * @see #parseBandwidth(String, int) + * @see #parseMemorySize(String, int) + */ + public static final int POWER_T = 40; + + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/HashHelper.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/HashHelper.java new file mode 100644 index 0000000000000..ebd2d63c03f0d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/HashHelper.java @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + +import java.util.Collection; +import java.util.Iterator; + + +/** + * This abstract class contains helper functions for + * calculating hash code values for any group of + * java intrinsics. + * + * @author pm + * @version 1.00, 04/25/00 + */ +public abstract class HashHelper + { + /** + * Calculate a running hash using the boolean value. + * + * @param value the boolean value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(boolean value, int hash) + { + // This mimics Boolean.hashCode + return swizzle(hash) ^ (value ? 1231 : 1237); + } + + /** + * Calculate a running hash using the byte value. + * + * @param value the byte value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(byte value, int hash) + { + // This mimics Byte.hashCode + return swizzle(hash) ^ value; + } + + /** + * Calculate a running hash using the char value. + * + * @param value the char value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(char value, int hash) + { + // Character.hashCode uses Object.hashCode, so + // we are instead using this. + return swizzle(hash) ^ value; + } + + /** + * Calculate a running hash using the double value. + * + * @param value the double value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(double value, int hash) + { + // This mimics Double.hashCode + long bits = Double.doubleToLongBits(value); + return swizzle(hash) ^ (int) (bits ^ (bits >> 32)); + } + + /** + * Calculate a running hash using the float value. + * + * @param value the float value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(float value, int hash) + { + // This mimics Float.hashCode + return swizzle(hash) ^ Float.floatToIntBits(value); + } + + /** + * Calculate a running hash using the int value. + * + * @param value the int value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(int value, int hash) + { + // This mimics Integer.hashCode + return swizzle(hash) ^ value; + } + + /** + * Calculate a running hash using the long value. + * + * @param value the long value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(long value, int hash) + { + // This mimics Long.hashCode + return swizzle(hash) ^ (int) (value ^ (value >> 32)); + } + + /** + * Calculate a running hash using the short value. + * + * @param value the short value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(short value, int hash) + { + // This mimics Short.hashCode + return swizzle(hash) ^ value; + } + + /** + * Calculate a running hash using the Object value. + * + * @param value the Object value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(Object value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + if (value instanceof boolean[]) + { + return hash ^ hash((boolean[]) value, hash); + } + if (value instanceof byte[]) + { + return hash ^ hash((byte[]) value, hash); + } + if (value instanceof char[]) + { + return hash ^ hash((char[]) value, hash); + } + if (value instanceof double[]) + { + return hash ^ hash((double[]) value, hash); + } + if (value instanceof float[]) + { + return hash ^ hash((float[]) value, hash); + } + if (value instanceof int[]) + { + return hash ^ hash((int[]) value, hash); + } + if (value instanceof long[]) + { + return hash ^ hash((long[]) value, hash); + } + if (value instanceof short[]) + { + return hash ^ hash((short[]) value, hash); + } + if (value instanceof Object[]) + { + return hash ^ hash((Object[]) value, hash); + } + if (value instanceof Collection) + { + return hash ^ hash((Collection) value, hash); + } + + return hash ^ value.hashCode(); + } + + /** + * Calculate a running hash using the boolean array value. + * + * @param value the boolean array value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(boolean[] value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + for (int i = 0; i < value.length; ++i) + { + hash = hash(value[i], hash); + } + return hash; + } + + /** + * Calculate a running hash using the byte array value. + * + * @param value the byte array value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(byte[] value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + for (int i = 0; i < value.length; ++i) + { + hash = hash(value[i], hash); + } + return hash; + } + + /** + * Calculate a running hash using the char array value. + * + * @param value the char array value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(char[] value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + for (int i = 0; i < value.length; ++i) + { + hash = hash(value[i], hash); + } + return hash; + } + + /** + * Calculate a running hash using the double array value. + * + * @param value the double array value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(double[] value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + for (int i = 0; i < value.length; ++i) + { + hash = hash(value[i], hash); + } + return hash; + } + + /** + * Calculate a running hash using the float array value. + * + * @param value the float array value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(float[] value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + for (int i = 0; i < value.length; ++i) + { + hash = hash(value[i], hash); + } + return hash; + } + + /** + * Calculate a running hash using the int array value. + * + * @param value the int array value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(int[] value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + for (int i = 0; i < value.length; ++i) + { + hash = hash(value[i], hash); + } + return hash; + } + + /** + * Calculate a running hash using the long array value. + * + * @param value the long array value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(long[] value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + for (int i = 0; i < value.length; ++i) + { + hash = hash(value[i], hash); + } + return hash; + } + + /** + * Calculate a running hash using the short array value. + * + * @param value the short array value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(short[] value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + for (int i = 0; i < value.length; ++i) + { + hash = hash(value[i], hash); + } + return hash; + } + + /** + * Calculate a running hash using the Object array value. + * + * @param value the Object array value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(Object[] value, int hash) + { + hash = swizzle(hash); + if (value == null) + { + return hash; + } + for (int i = 0; i < value.length; ++i) + { + hash = hash(value[i], hash); + } + return hash; + } + + /** + * Calculate a running hash using the Collection value. The hash + * computed over the Collection's entries is order-independent. + * + * @param col the Collection value for use in the hash + * @param hash the running hash value + * @return the resulting running hash value + */ + public static int hash(Collection col, int hash) + { + hash = swizzle(hash); + for (Iterator iter = col.iterator(); iter.hasNext(); ) + { + hash ^= iter.next().hashCode(); + } + return hash; + } + + /** + * Shift the running hash value to try and help with + * generating unique values given the same input, but + * in a different order. + * + * @param hash the running hash value + * @return the resulting running hash value + */ + private static int swizzle(int hash) + { + // rotate the current hash value 4 bits to the left + return (hash << 4) | ((hash >> 28) & 0xf); + } + + /** + * Shift the value into a different charset order. + * + * @param s a String + * @return a String with a different charset ordering + */ + public static String hash(String s) + { + if (s == null || s.length() == 0) + { + return s; + } + + char[] ach = s.toCharArray(); + for (int of = 0, cch = ach.length; of < cch; ++of) + { + char ch = ach[of]; + if (ch >= 32 && ch <= 127) + { + ach[of] = (char) (32 + (127 - ch)); + } + } + return new String(ach); + } + + // ----- hashing -------------------------------------------------------- + + /** + * Return the hash code for the supplied object or 0 for null. + * + * @param o the object to hash + * + * @return the hash code for the supplied object + */ + public static int hashCode(Object o) + { + return o == null ? 0 : o.hashCode(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Hasher.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Hasher.java new file mode 100644 index 0000000000000..8ff3a8bf3a085 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Hasher.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * A Hasher provides an external means for producing hash codes and comparing + * objects for equality. + * + * @author mf 2011.01.07 + */ +public interface Hasher + { + /** + * Return a hash for the specified object. + * + * @param v the object to hash + * + * @return the hash + */ + public int hashCode(V v); + + /** + * Compare two objects for equality. + * + * @param va the first object to compare + * @param vb the second object to compare + * + * @return true iff the object are equal + */ + public boolean equals(V va, V vb); + + + // ---- static methods --------------------------------------------------- + + /** + * Calculate a modulo of two integer numbers. For a positive dividend the + * result is the same as the Java remainder operation (n % m). + * For a negative dividend the result is still positive and equals to + * (n % m + m). + * + * @param n the dividend + * @param m the divisor (must be positive) + * + * @return the modulo + */ + public static int mod(int n, int m) + { + int k = n % m; + return k >= 0 ? k : k + m; + } + + /** + * Calculate a modulo of two long numbers. For a positive dividend the + * result is the same as the Java remainder operation (n % m). + * For a negative dividend the result is still positive and equals to + * (n % m + m). + * + * @param n the dividend + * @param m the divisor (must be positive) + * + * @return the modulo + */ + public static long mod(long n, long m) + { + long k = n % m; + return k >= 0 ? k : k + m; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Holder.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Holder.java new file mode 100644 index 0000000000000..7078c61d2f6fd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Holder.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * A Holder is a reference like object, i.e. it simply holds another object. + * + * @param the value type + * + * @author mf 2010.12.2 + */ +public interface Holder + { + /** + * Specify the held object. + * + * @param value the object to hold + */ + public void set(V value); + + /** + * Return the held object. + * + * @return the held object + */ + public V get(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/IdentityHasher.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/IdentityHasher.java new file mode 100644 index 0000000000000..a32a54b822a85 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/IdentityHasher.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * IdentityHasher provides a Hasher implementation based upon an object's + * {@link System#identityHashCode(Object) identity hashCode} and reference + * equality. + * + * @param the value type + * + * @author mf 2011.01.07 + */ +public class IdentityHasher + implements Hasher + { + /** + * {@inheritDoc} + */ + @Override + public int hashCode(V o) + { + return System.identityHashCode(o); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(V va, V vb) + { + return va == vb; + } + + + // ----- constants ------------------------------------------------------ + + /** + * A singleton instance of the IdentityHasher. + */ + public static final IdentityHasher INSTANCE = new IdentityHasher(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/IdentityHolder.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/IdentityHolder.java new file mode 100644 index 0000000000000..65f49ea00642b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/IdentityHolder.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +/** + * A Holder implementation which additionally provides an equals/hashCode implementation based on the held + * object's identity. + * + * @author mf 2017.03.27 + */ +public class IdentityHolder + extends SimpleHolder + { + /** + * Construct an IdentityHolder holding nothing. + */ + public IdentityHolder() + { + } + + /** + * Construct an IdentityHolder holding the specified object. + * + * @param v the object to hold + */ + public IdentityHolder(V v) + { + super(v); + } + + @Override + public int hashCode() + { + return System.identityHashCode(get()); + } + + @Override + public boolean equals(Object oThat) + { + return oThat instanceof IdentityHolder && get() == ((IdentityHolder) oThat).get(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/InverseComparator.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/InverseComparator.java new file mode 100644 index 0000000000000..cc50b2010df41 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/InverseComparator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +import java.util.Comparator; + + +/** + * InverseComparator is a wrapper comparator which simply inverses the comparison result. + * + * @author mf 2014.08.28 + */ +public class InverseComparator + implements Comparator + { + /** + * Construct an InverseComparator which inverts the specified comparator. + * + * @param comparator the comparator to invert + */ + public InverseComparator(Comparator comparator) + { + f_comparator = comparator; + } + + @Override + public int compare(T o1, T o2) + { + return -f_comparator.compare(o1, o2); + } + + + // ----- data members --------------------------------------------------- + + /** + * The comparator to invert. + */ + protected final Comparator f_comparator; + + + // ----- constants ------------------------------------------------------ + + /** + * InverseComparator singleton which inverts a Comparable objects natural comparison order. + */ + public static final InverseComparator INSTANCE = new InverseComparator(NaturalComparator.INSTANCE); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Loggers.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Loggers.java new file mode 100644 index 0000000000000..f694bff1978f0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Loggers.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +import com.tangosol.util.NullImplementation; +import java.io.PrintWriter; + +import static com.oracle.coherence.common.base.Formatting.toHexEscape; +import static com.oracle.coherence.common.base.Formatting.toQuotedStringEscape; +import static com.oracle.coherence.common.base.StackTrace.getExpression; +import static com.oracle.coherence.common.base.StackTrace.printStackTrace; + + +/** + * Class for providing printed output functionality. + * + * @author cp 2000.08.02 + * @since Coherence 12.4.1 + */ + +public abstract class Loggers + { + // ----- printed output support ---------------------------------------------- + + /** + * Prints a blank line. + */ + public static void out() + { + s_out.println(); + } + + /** + * Prints the passed Object. + * + * @param o the Object to print. + */ + public static void out(Object o) + { + s_out.println(o); + } + + /** + * Prints the passed String value. + * + * @param s the String to print. + */ + public static void out(String s) + { + s_out.println(s); + } + + /** + * Prints the passed class information. + * + * @param clz the class object to print. + */ + public static void out(Class clz) + { + s_out.println(Classes.toString(clz)); + } + + /** + * Prints the passed exception information. + * + * @param e the Throwable object to print. + */ + public static void out(Throwable e) + { + s_out.println(printStackTrace(e)); + } + + /** + * Prints a blank line to the trace Writer. + */ + public static void err() + { + s_err.println(); + } + + /** + * Prints the passed Object to the trace Writer. + * + * @param o the Object to print. + */ + public static void err(Object o) + { + s_err.println(o); + } + + /** + * Prints the passed String value to the trace Writer. + * + * @param s the String to print. + */ + public static void err(String s) + { + s_err.println(s); + } + + /** + * Prints the passed class information to the trace Writer. + * + * @param clz the class object to print. + */ + public static void err(Class clz) + { + s_err.println(Classes.toString(clz)); + } + + /** + * Prints the passed exception information to the trace Writer. + * + * @param e the Throwable object to print. + */ + public static void err(Throwable e) + { + s_err.println(printStackTrace(e)); + } + + /** + * Prints a blank line to the log. + */ + public static void log() + { + s_log.println(); + if (s_fEchoLog) + { + s_out.println(); + } + } + + /** + * Prints the passed Object to the log. + * + * @param o the Object to print. + */ + public static void log(Object o) + { + log(String.valueOf(o)); + } + + /** + * Prints the passed String value to the log. + * + * @param s the String to print. + */ + public static void log(String s) + { + s_log.println(s); + if (s_fEchoLog) + { + s_out.println(s); + } + } + + /** + * Prints the passed class information to the log. + * + * @param clz the class object to print. + */ + public static void log(Class clz) + { + log(Classes.toString(clz)); + } + + /** + * Prints the passed exception information to the log. + * + * @param e the Throwable object to print. + */ + public static void log(Throwable e) + { + String s = printStackTrace(e); + s_log.println(s); + if (s_fEchoLog) + { + s_out.println(s); + } + } + + /** + * Obtains the current writer used for printing. + * + * @return the current writer used for printing; never null + */ + public static PrintWriter getOut() + { + return s_out; + } + + /** + * Sets the current writer used for printing. + * + * @param writer the java.io.PrintWriter instance to use for printing; + * may be null + */ + public static void setOut(PrintWriter writer) + { + s_out = writer == null + ? new PrintWriter(NullImplementation.getWriter(), true) + : writer; + } + + /** + * Obtains the current writer used for tracing. + * + * @return the current writer used for tracing; never null + */ + public static PrintWriter getErr() + { + return s_err; + } + + /** + * Sets the current writer used for tracing. + * + * @param writer the java.io.PrintWriter instance to use for tracing; may + * be null + */ + public static void setErr(PrintWriter writer) + { + s_err = writer == null + ? new PrintWriter(NullImplementation.getWriter(), true) + : writer; + } + + /** + * Obtains the current writer used for logging. + * + * @return the current writer used for logging; never null + */ + public static PrintWriter getLog() + { + return s_log; + } + + /** + * Sets the current writer used for logging. + * + * @param writer the java.io.PrintWriter instance to use for logging; may + * be null + */ + public static void setLog(PrintWriter writer) + { + s_log = writer == null + ? new PrintWriter(NullImplementation.getWriter(), true) + : writer; + } + + + // ----- debugging support: expression evaluation ---------------------- + + /** + * Display the value of a boolean expression. + */ + public static void trace(boolean fVal) + { + traceImpl(String.valueOf(fVal)); + } + + /** + * Display the value of a char expression. + */ + public static void trace(char chVal) + { + traceImpl(String.valueOf(chVal)); + } + + /** + * Display the value of an int expression. + */ + public static void trace(int nVal) + { + traceImpl(String.valueOf(nVal)); + } + + /** + * Display the value of a long expression. + */ + public static void trace(long lVal) + { + traceImpl(String.valueOf(lVal)); + } + + /** + * Display the value of a float expression. + */ + public static void trace(float flVal) + { + traceImpl(String.valueOf(flVal)); + } + + /** + * Display the value of a double expression. + */ + public static void trace(double dflVal) + { + traceImpl(String.valueOf(dflVal)); + } + + /** + * Display the value of a byte array expression. + */ + public static void trace(byte[] ab) + { + if (ab == null) + { + traceImpl(null); + } + else + { + traceImpl("length=" + ab.length + ", binary=" + toHexEscape(ab)); + } + } + + /** + * Display the value of a String expression. + */ + public static void trace(String sVal) + { + traceImpl(sVal == null ? "null" : toQuotedStringEscape(sVal)); + } + + /** + * Display the value of an Object expression. + */ + public static void trace(Object oVal) + { + traceImpl(String.valueOf(oVal)); + } + + + /** + * Internal implementation for trace methods. + */ + private static void traceImpl(String sVal) + { + String sExpr = getExpression("trace"); + out((sExpr == null ? "?" : sExpr) + '=' + (sVal == null ? "null" : sVal)); + } + + + // ----- data members --------------------------------------------------- + + /** + * The writer to use for print output. + */ + private static PrintWriter s_out = new PrintWriter(System.out, true); + + /** + * The writer to use for trace output. + */ + private static PrintWriter s_err = new PrintWriter(System.err, true); + + /** + * The writer to use for logging. By default, there is no persistent + * log. + */ + private static PrintWriter s_log = new PrintWriter(NullImplementation.getWriter(), true); + + /** + * Option to log to the console in addition to the logging writer. By + * default, all logged messages are echoed to the console. + */ + private static boolean s_fEchoLog = true; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/MutableLong.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/MutableLong.java new file mode 100644 index 0000000000000..e6dec534df2ff --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/MutableLong.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +/** + * A Long like class which supports mutation. + * + * @author mf 2014.03.20 + */ +public class MutableLong + extends Number + implements Comparable + { + /** + * Construct a MutableLong with a zero initial value. + */ + public MutableLong() + { + } + + /** + * Construct a MutableLong with the specified value. + * + * @param lValue the initial value + */ + public MutableLong(long lValue) + { + m_lValue = lValue; + } + + + // ----- MutableLong interface ------------------------------------------ + + /** + * Update the value + * + * @param lValue the new value + * + * @return this object + */ + public MutableLong set(long lValue) + { + m_lValue = lValue; + return this; + } + + /** + * Return the current value. + * + * @return the value + */ + public long get() + { + return m_lValue; + } + + /** + * Increment the long and return the new value. + * + * @return the new value + */ + public long incrementAndGet() + { + return ++m_lValue; + } + + /** + * Decrement the long and return the new value. + * + * @return the new value + */ + public long decrementAndGet() + { + return --m_lValue; + } + + /** + * Return a ThreadLocal of MutableLong. + * + * @return a ThreadLocal of MutableLong + */ + public static ThreadLocal createThreadLocal() + { + return ThreadLocal.withInitial(MutableLong::new); + } + + // ----- Number interface ----------------------------------------------- + + @Override + public int intValue() + { + return (int) m_lValue; + } + + @Override + public long longValue() + { + return m_lValue; + } + + @Override + public float floatValue() + { + return m_lValue; + } + + @Override + public double doubleValue() + { + return m_lValue; + } + + + // ----- Comparable interface ------------------------------------------- + + @Override + public int compareTo(MutableLong that) + { + long lDelta = this.m_lValue - that.m_lValue; + return lDelta < 0 ? -1 : lDelta == 0 ? 0 : 1; + } + + + // ----- Object interface ----------------------------------------------- + + @Override + public int hashCode() + { + return (int)(m_lValue ^ (m_lValue >>> 32)); // same as Long.hashCode + } + + @Override + public boolean equals(Object that) + { + return that == this || that instanceof MutableLong && ((MutableLong) that).m_lValue == m_lValue; + } + + @Override + public String toString() + { + return String.valueOf(m_lValue); + } + + + // ----- data members --------------------------------------------------- + + /** + * The value. + */ + protected long m_lValue; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalAssociator.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalAssociator.java new file mode 100644 index 0000000000000..47a4a31ea56ed --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalAssociator.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +import java.util.LinkedList; +import java.util.List; + + +/** + * NaturalAssociator provides an Associator implementation for objects that + * implement the {@link Associated} interface. + * + * @author gg 2012.03.11 + */ +public class NaturalAssociator + implements Associator + { + /** + * {@inheritDoc} + */ + @Override + public Object getAssociatedKey(Object o) + { + if (o instanceof Associated) + { + for (int i = 0; i < 7; i++) + { + o = ((Associated) o).getAssociatedKey(); + + if (!(o instanceof Associated)) + { + return o; + } + } + + // we should never get here, but since we did, + // it is almost definitely a circular association case + return validateAssociated((Associated) o); + } + else + { + return null; + } + } + + + // ----- internal helpers ----------------------------------------------- + + /** + * Check if given Associated object generates a circular association. + * + * @param assoc an Associated object to check + * + * @return the key object that is associated with the specified object, + * or null if there is no association + * + * @throws RuntimeException if there is a circular key association chain, + * or if the maximum association depth has been reached + */ + protected Object validateAssociated(Associated assoc) + { + List listKeys = new LinkedList(); + int cDepth = 0; + + while (true) + { + if (listKeys.contains(assoc)) + { + throw new RuntimeException(cDepth == 1 ? + "self-associated object: " + assoc : + "circular association chain: " + listKeys); + } + if (++cDepth > 777) + { + throw new RuntimeException("maximum association depth reached"); + } + listKeys.add(assoc); + + Object oAssocNext = assoc.getAssociatedKey(); + if (oAssocNext instanceof Associated) + { + assoc = (Associated) oAssocNext; + } + else + { + return oAssocNext; + } + } + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalCloner.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalCloner.java new file mode 100644 index 0000000000000..ca8f6d235b6e3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalCloner.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * A Cloner that clones {@link Cloneable} objects. + * + * @author gg/mf 2012.07.12 + */ +public class NaturalCloner + implements Cloner + { + @Override + public T clone(T o) + { + return o == null ? null : (T) ((Cloneable) o).clone(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalComparator.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalComparator.java new file mode 100644 index 0000000000000..122a1ce46a356 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalComparator.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +import java.util.Comparator; + + +/** + * NaturalComparator is a comparator which simply delegates to a Comparable object's compare implementation. + * + * @author mf 2014.08.28 + */ +public class NaturalComparator> + implements Comparator + { + @Override + public int compare(T o1, T o2) + { + return o1.compareTo(o2); + } + + + // ----- constants ------------------------------------------------------ + + /** + * NaturalComparator singleton. + */ + public static final NaturalComparator INSTANCE = new NaturalComparator(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalHasher.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalHasher.java new file mode 100644 index 0000000000000..fa2ce46681444 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NaturalHasher.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * NaturalHasher provides a Hasher implementation based upon an object's + * internal {@link Object#hashCode hashCode} and {@link Object#equals equals} + * implementation. + * + * @param the value type + * + * @author mf 2011.01.07 + */ +public class NaturalHasher + implements Hasher + { + /** + * {@inheritDoc} + */ + @Override + public int hashCode(V o) + { + return o == null ? 0 : o.hashCode(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(V va, V vb) + { + return va == vb || (va != null && va.equals(vb)); + } + + + // ----- constants ------------------------------------------------------ + + /** + * A singleton instance of the NaturalHasher. + */ + public static final NaturalHasher INSTANCE = new NaturalHasher(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NonBlocking.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NonBlocking.java new file mode 100644 index 0000000000000..838b6db093fd4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/NonBlocking.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +/** + * NonBlocking allows a thread to mark itself as non-blocking and should be exempt from things such as + * flow-control pauses using a try-with-resource pattern. + * + *

+ * try (NonBlocking e = new NonBlocking())
+ *     {
+ *     // NonBlocking.isNonBlockingCaller() will now be true, and FlowControlled elements should respect this and not block
+ *     }
+ * // NonBlocking.isNonBlockingCaller() will have been restored to its former value
+ * 
+ */ +public class NonBlocking + implements AutoCloseable + { + public NonBlocking() + { + if (!f_fPriorValue) + { + s_fNonBlocking.set(Boolean.TRUE); + } + // else; prior is true, and we can only set to true no action is necessary + } + + @Override + public void close() + { + if (!f_fPriorValue) + { + s_fNonBlocking.remove(); + } + // else; prior is true, and we can only set to true no action is necessary + } + + /** + * Return true if the the calling thread has been marked as non-blocking + * + * @return true iff the calling thread is marked as non-blocking + */ + public static boolean isNonBlockingCaller() + { + return s_fNonBlocking.get() == Boolean.TRUE; + } + + /** + * The prior value. + */ + private final boolean f_fPriorValue = isNonBlockingCaller(); + + /** + * ThreadLocal tracking the calling thread's non-blocking status. + */ + private static final ThreadLocal s_fNonBlocking = new ThreadLocal<>(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Notifier.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Notifier.java new file mode 100644 index 0000000000000..db79f35ade3df --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Notifier.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +/** + * A Condition-like object, used to block thread(s) for a notification. + *

+ * Unlike {@link java.util.concurrent.locks.Condition Condition} no external locking or synchronization + * is needed with Notifiers; i.e. clients need not synchronize on this class prior to + * calling await() or signal(), nor should they use any of the + * primitive wait() or notify() methods. + * + * Note: the Notifiers are expected to be {@link Timeout} compatible.* + * + * @author cp/mf 2010-06-15 + */ +public interface Notifier + { + /** + * Wait for a notification. Note that spurious wake-ups are possible. + * + * @throws InterruptedException if the calling thread is interrupted + * while it is waiting + */ + public default void await() + throws InterruptedException + { + await(0); + } + + /** + * Wait for a notification. Note that spurious wake-ups are possible. + * + * @param cMillis the maximum wait time in milliseconds, or zero for indefinite + * + * @throws InterruptedException if the calling thread is interrupted + * while it is waiting + */ + public void await(long cMillis) + throws InterruptedException; + + /** + * Notifies the waiting thread(s), waking them up if awaiting. + */ + public void signal(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Objects.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Objects.java new file mode 100644 index 0000000000000..830295807cded --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Objects.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + +import java.util.Arrays; + + +/** + * Class for providing comparison functionality. + * + * @author cp 2000.08.02 + * @since Coherence 12.4.1 + */ + +public abstract class Objects + { + // ----- comparison support ---------------------------------------------- + + /** + * Compare two references for equality. + * + * @param o1 + * @param o2 + * @return true if equal, false otherwise + */ + public static boolean equals(Object o1, Object o2) + { + if (o1 == o2) + { + return true; + } + + if (o1 == null || o2 == null) + { + return false; + } + + try + { + return o1.equals(o2); + } + catch (RuntimeException e) + { + return false; + } + } + + /** + * Deeply compare two references for equality. This dives down into + * arrays, including nested arrays. + * + * @param o1 + * @param o2 + * @return true if deeply equal, false otherwise + */ + public static boolean equalsDeep(Object o1, Object o2) + { + if (o1 == o2) + { + return true; + } + + if (o1 == null || o2 == null) + { + return false; + } + + if (o1.getClass().isArray()) + { + // the following are somewhat in order of likelihood + + if (o1 instanceof byte[]) + { + return o2 instanceof byte[] + && Arrays.equals((byte[]) o1, (byte[]) o2); + } + + if (o1 instanceof Object[]) + { + if (o2 instanceof Object[]) + { + Object[] ao1 = (Object[]) o1; + Object[] ao2 = (Object[]) o2; + int c = ao1.length; + if (c == ao2.length) + { + for (int i = 0; i < c; ++i) + { + if (!equalsDeep(ao1[i], ao2[i])) + { + return false; + } + } + return true; + } + } + + return false; + } + + if (o1 instanceof int[]) + { + return o2 instanceof int[] + && Arrays.equals((int[]) o1, (int[]) o2); + } + + if (o1 instanceof char[]) + { + return o2 instanceof char[] + && Arrays.equals((char[]) o1, (char[]) o2); + } + + if (o1 instanceof long[]) + { + return o2 instanceof long[] + && Arrays.equals((long[]) o1, (long[]) o2); + } + + if (o1 instanceof double[]) + { + return o2 instanceof double[] + && Arrays.equals((double[]) o1, (double[]) o2); + } + + if (o1 instanceof boolean[]) + { + return o2 instanceof boolean[] + && Arrays.equals((boolean[]) o1, (boolean[]) o2); + } + + if (o1 instanceof short[]) + { + return o2 instanceof short[] + && Arrays.equals((short[]) o1, (short[]) o2); + } + + if (o1 instanceof float[]) + { + return o2 instanceof float[] + && Arrays.equals((float[]) o1, (float[]) o2); + } + } + + try + { + return o1.equals(o2); + } + catch (RuntimeException e) + { + return false; + } + } + + + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Pollable.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Pollable.java new file mode 100644 index 0000000000000..47111d31283b7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Pollable.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +import java.util.concurrent.TimeUnit; + +/** + * The Pollable interface describes a component which supports element removal. + * + * @author mf 2013.02.19 + */ +public interface Pollable + { + /** + * Removes and returns the next element, or returns null if none is present. + * + * @return the next element or null + */ + E poll(); + + /** + * Removes and returns the next element, or returns null upon timeout. + * + * @param timeout how long to wait before giving up, in units of + * unit + * @param unit a TimeUnit determining how to interpret the + * timeout parameter + * + * @return the next element or null + * @throws InterruptedException if interrupted while waiting + */ + E poll(long timeout, TimeUnit unit) + throws InterruptedException; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Predicate.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Predicate.java new file mode 100644 index 0000000000000..fc64100a80ba2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Predicate.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * Predicate represents a boolean test of an object. + * + * @param the type of the value to evaluate + * + * @author rhl 2011.11.14 + */ +public interface Predicate + { + /** + * Return true iff the specified object satisfies the predicate. + * + * @param t the object to evaluate + * + * @return true iff the specified object satisfies the predicate + */ + public boolean evaluate(T t); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Reads.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Reads.java new file mode 100644 index 0000000000000..5f8ffc9a5eddd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Reads.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; +import java.net.URLConnection; + +import static com.oracle.coherence.common.base.Assertions.azzert; + + +/** + * Class for providing read functionality. + * + * @author cp 2000.08.02 + * @since Coherence 12.4.1 + */ + +public abstract class Reads + { + // ----- read support ---------------------------------------------- + + /** + * Read the contents out of the passed stream into the passed byte array + * and return the length read. This method will read up to the number of + * bytes that can fit into the passed array. + * + * @param stream a java.io.InputStream object to read from + * @param ab a byte array to read into + * + * @return the number of bytes read from the InputStream and stored into + * the passed byte array + * + * @throws IOException + */ + public static int read(InputStream stream, byte[] ab) + throws IOException + { + final int MAX = ab.length; + int cb = 0; + boolean fEOF = false; + while (!fEOF && cb < MAX) + { + int cbBlock = stream.read(ab, cb, MAX - cb); + if (cbBlock < 0) + { + fEOF = true; + } + else + { + cb += cbBlock; + } + + } + return cb; + } + + /** + * Read the contents out of the passed stream and return the result as a + * byte array. + * + * @param stream a java.io.InputStream object to read from + * + * @return a byte array containing the contents of the passed InputStream + * + * @throws IOException + */ + public static byte[] read(InputStream stream) + throws IOException + { + final int BLOCK = 1024; + byte[] ab = new byte[BLOCK]; + ByteArrayOutputStream streamBuf = new ByteArrayOutputStream(BLOCK); + while (true) + { + try + { + int cb = stream.read(ab, 0, BLOCK); + if (cb < 0) + { + break; + } + else if (cb > 0) + { + streamBuf.write(ab, 0, cb); + } + } + catch (EOFException e) + { + break; + } + } + stream.close(); + return streamBuf.toByteArray(); + } + + /** + * Read the contents out of the passed stream and return the result as a + * byte array. + * + * @param stream a java.io.DataInput object to read from + * + * @return a byte array containing the contents of the passed stream + * + * @throws IOException + */ + public static byte[] read(DataInput stream) + throws IOException + { + if (stream instanceof InputStream) + { + return read((InputStream) stream); + } + + final int BLOCK = 1024; + int cb = 0; + byte[] ab = new byte[BLOCK]; + ByteArrayOutputStream streamBuf = null; + try + { + while (true) + { + ab[cb++] = stream.readByte(); + if (cb == BLOCK) + { + if (streamBuf == null) + { + streamBuf = new ByteArrayOutputStream(BLOCK); + } + streamBuf.write(ab, 0, cb); + cb = 0; + } + } + } + catch (EOFException e) + { + // end of input reached; eat it + } + + if (streamBuf == null) + { + // contents fit in first block + if (cb == BLOCK) + { + // perfect fit + return ab; + } + // shrink block and return + byte[] abNew = new byte[cb]; + System.arraycopy(ab, 0, abNew, 0, cb); + return abNew; + } + + // contents span multiple blocks + if (cb != 0) + { + // copy remainder into streamBuf + streamBuf.write(ab, 0, cb); + } + return streamBuf.toByteArray(); + } + + /** + * Read the contents out of the passed stream and return the result as a + * byte array. + * + * @param stream a java.io.DataInputStream object to read from + * + * @return a byte array containing the contents of the passed InputStream + * + * @throws IOException + */ + public static byte[] read(DataInputStream stream) + throws IOException + { + // this method resolves the ambiguity between the read(InputStream) + // and read(DataInput) methods for DataInputStreams and its derivatives + return read((InputStream) stream); + } + + /** + * Read the contents out of the passed Reader and return the result as a + * String. + * + * @param reader a java.io.Reader object to read from + * + * @return a String containing the contents of the passed Reader + * + * @throws IOException + */ + public static String read(Reader reader) + throws IOException + { + final int BLOCK = 1024; + char[] ach = new char[BLOCK]; + CharArrayWriter writer = new CharArrayWriter(BLOCK); + while (true) + { + try + { + int cch = reader.read(ach, 0, BLOCK); + if (cch < 0) + { + break; + } + else if (cch > 0) + { + writer.write(ach, 0, cch); + } + } + catch (EOFException e) + { + break; + } + } + reader.close(); + return writer.toString(); + } + + /** + * Read the contents out of the specified file and return the result as a + * byte array. + * + * @param file the java.io.File object to read the contents of + * + * @return the contents of the specified File as a byte array + * + * @throws IOException + */ + public static byte[] read(File file) + throws IOException + { + if (file == null || !file.exists() || !file.isFile()) + { + return null; + } + + long cbFile = file.length(); + azzert(cbFile < 0x7FFFFFFFL); + + int cb = (int) cbFile; + byte[] ab = new byte[cb]; + + InputStream in = new FileInputStream(file); + try + { + int cbRead = read(in, ab); + azzert(cb == cbRead); + } + finally + { + try + { + in.close(); + } + catch (Exception e) {} + } + + return ab; + } + + /** + * Read the contents of the specified URL and return the result as a + * byte array. + * + * @param url the java.net.URL object to read the contents of + * + * @return the contents of the specified URL as a byte array + * + * @throws IOException + */ + public static byte[] read(URL url) + throws IOException + { + if (url == null) + { + return null; + } + + URLConnection con = url.openConnection(); + int cb = con.getContentLength(); + byte[] ab = null; + InputStream in = con.getInputStream(); + try + { + if (cb == -1) + { + // unknown content length + ab = read(in); + } + else + { + // known content length (optimize by pre-allocating fully) + ab = new byte[cb]; + int cbRead = read(in, ab); + azzert(cb == cbRead); + } + } + finally + { + try + { + in.close(); + } + catch (Exception e) {} + } + + return ab; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Resources.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Resources.java new file mode 100644 index 0000000000000..dff7d3a8c35a2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Resources.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.text.MessageFormat; +import java.util.ListResourceBundle; + + +/** + * Implement simple basis for package (and other) resources. + * + * @author Cameron Purdy + * @version 1.00, 11/17/97 + */ +public abstract class Resources + extends ListResourceBundle + { + // the following two items should be implemented by derived classes + /* + public Object[][] getContents() + { + return resources; + } + + static final Object[][] resources = + { + {key, "text"}, + ... + } + */ + + /** + * Get the specified resource text. + * + * @param sKey the resource key + * @param sDefault returns this string if the resource cannot be found + * @return the requested string, formatted if specified + */ + public String getString(String sKey, String sDefault) + { + return getString(sKey, null, sDefault); + } + + /** + * Get the specified resource string. + * + * @param sKey the resource key + * @param asParam an array of arguments to fill in replaceable parameters + * @param sDefault returns this string if the resource cannot be found + * @return the requested string, formatted if specified + */ + public String getString(String sKey, String[] asParam, String sDefault) + { + String sBase; + try + { + sBase = getString(sKey); + } + catch (Exception e) + { + sBase = sDefault; + } + + if (asParam != null && asParam.length > 0) + { + try + { + return MessageFormat.format(sBase, (Object[]) asParam); + } + catch (Exception e) + { + int c = asParam.length; + for (int i = 0; i < c; ++i) + { + sBase += "\n[" + i + "]=\"" + + (asParam[i] == null ? "" : asParam[i]) + + '\"'; + } + } + } + + return sBase; + } + + /** + * Find the URL of the resource with the given name using the specified + * ClassLoader or the following Classes: + *

    + *
  • The Thread Context {@link ClassLoader}
  • + *
  • The {@link ClassLoader} used to load {@link ClassLoaderHelper}, which represents the Coherence Class Loader
  • + *
  • The System {@link ClassLoader}
  • + *
+ *

+ * If a resource with the given name is not found, this method attempts + * to find the resource using a fully-qualified or relative version of the + * specified name. As a last attempt, the name will be treated as a URL. + * + * @param sName the name of the resource + * @param loader the {@link ClassLoader} used to locate the resource; if null, + * or resource is not found, the list of {@link ClassLoader}s + * described above will be tried + * @return the URL of the resource or null if the resource could not be found + * and the resource name is not a valid URL specification + */ + public static URL findResource(String sName, ClassLoader loader) + { + URL url = findRelativeOrAbsoluteResource(sName, loader); + + if (url == null) + { + url = findRelativeOrAbsoluteResource(sName, Thread.currentThread().getContextClassLoader()); + } + + if (url == null) + { + url = findRelativeOrAbsoluteResource(sName, Classes.class.getClassLoader()); + } + + if (url == null) + { + url = findRelativeOrAbsoluteResource(sName, ClassLoader.getSystemClassLoader()); + } + + if (url == null) + { + try + { + url = new URL(sName); + } + catch (Exception e) + { + } + } + + return url; + } + + /** + * Find the URL of the resource with the given name using the specified + * {@link ClassLoader}. An attempt is made with both a relative URL + * and an absolute (fully-qualified) URL if required. This method will + * only search the provided ClassLoader; it is recommended to use + * {@link #findResource(String, ClassLoader)} for a more exhaustive search. + * + * @param sName the name of the resource + * @param loader the ClassLoader used to locate the resource; this method + * returns null if a null ClassLoader is provided + * @return the URL of the resource or null if the resource could not be found + * or if a null ClassLoader is provided + * @see #findResource(String, ClassLoader) + */ + public static URL findRelativeOrAbsoluteResource(String sName, ClassLoader loader) + { + if (loader == null) + { + return null; + } + + URL url = loader.getResource(sName); + if (url == null) + { + url = (sName.startsWith("/") + ? loader.getResource(sName.substring(1)) + : loader.getResource('/' + sName)); + } + + return url; + } + + /** + * Obtain a URL for an existing file with the specified name. + * + * @param sName the name of the file + * @return the file URL, or null if file does not exist + */ + public static URL getFileURL(String sName) + { + URL url = null; + try + { + File file = new File(sName); + if (file.exists()) + { + try + { + file = file.getCanonicalFile(); + } + catch (IOException ioex) + { + file = file.getAbsoluteFile(); + } + url = file.toURI().toURL(); + } + } + catch (Exception e) // MalformedURLException or SecurityException + { + } + + return url; + } + + /** + * Return a URL to the specified file or resource, using the specified class + * loader or a {@link Classes#getContextClassLoader() context ClassLoader}. + *

+ * This method attempts to locate a file with the specified name or path. If + * the file does not exist or cannot be read, this method attempts to locate + * a resource with the given name, using the specified class loader or + * context class loader. + *

+ * If a resource with the given name is not found, this method attempts + * to find the resource using a fully-qualified or relative version of the + * specified name. As a last attempt, the name will be treated as a URL. + * + * @param sName the name of the file or resource + * @param loader the ClassLoader used to locate the resource; if null, + * {@link Classes#getContextClassLoader()} is used + * @return the URL of the file or resource or null if the resource could not + * be found and the resource name is not a valid URL specification + */ + public static URL findFileOrResource(String sName, ClassLoader loader) + { + URL url = getFileURL(sName); + return url == null ? findResource(sName, loader) : url; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SafeClock.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SafeClock.java new file mode 100644 index 0000000000000..65ac76b1516f7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SafeClock.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.atomic.AtomicBoolean; + + +/** + * SafeClock maintains a "safe" time in milliseconds. + *

+ * Unlike the {@link System#currentTimeMillis()} this clock guarantees that + * the time never "goes back". More specifically, when queried twice on the + * same thread, the second query will never return a value that is less then + * the value returned by the first. + *

+ * If it is detected that the system clock moved backward, an attempt will be + * made to gradually compensate the safe clock (by slowing it down), so in the + * long run the safe time is the same as the system time. + *

+ * The SafeClock supports the concept of "clock jitter", which is a small + * time interval that the system clock could fluctuate by without a + * corresponding passage of wall time. + *

+ * In most cases the {@link SafeClock#INSTANCE} singleton should be used + * rather then creating new SafeClock instances. + * + * @author mf 2009.12.09 + */ +public class SafeClock + extends AtomicBoolean // embedded "lock" which will be on the same cache line as the clock's data members + { + /** + * Create a new SafeClock with the default maximum expected jitter as + * specified by the {@link #DEFAULT_JITTER_THRESHOLD} constant. + */ + public SafeClock() + { + this(System.currentTimeMillis()); + } + + /** + * Create a new SafeClock with the default maximum expected jitter as + * specified by the {@link #DEFAULT_JITTER_THRESHOLD} constant. + * + * @param ldtUnsafe the current unsafe time + */ + public SafeClock(long ldtUnsafe) + { + this(ldtUnsafe, DEFAULT_JITTER_THRESHOLD); + } + + /** + * Create a new SafeClock with the specified jitter threshold. + * + * @param ldtUnsafe the current unsafe time + * @param lJitter the maximum expected jitter in the underlying system + * clock + */ + public SafeClock(long ldtUnsafe, long lJitter) + { + m_ldtLastSafe = m_ldtLastUnsafe = ldtUnsafe; + m_lJitter = lJitter; + } + + /** + * Returns a "safe" current time in milliseconds. + * + * @return the difference, measured in milliseconds, between the + * corrected current time and midnight, January 1, 1970 UTC. + */ + public final long getSafeTimeMillis() + { + return getSafeTimeMillis(System.currentTimeMillis()); + } + + /** + * Returns a "safe" current time in milliseconds. + * + * @param ldtUnsafe the current unsafe time + * + * @return the difference, measured in milliseconds, between the + * corrected current time and midnight, January 1, 1970 UTC. + */ + public final long getSafeTimeMillis(long ldtUnsafe) + { + // optimization for heavy concurrent load: if no time has passed, or + // time jumped back within the expected jitter just return the last + // time and avoid CAS contention; keep short to encourage hot-spotting + long lDelta = ldtUnsafe - m_ldtLastUnsafe; + + return lDelta == 0 || (lDelta < 0 && lDelta >= -m_lJitter) + ? m_ldtLastSafe // common case during heavy load + : updateSafeTimeMillis(ldtUnsafe); + } + + /** + * Returns the last "safe" time as computed by a previous call to the + * {@link #getSafeTimeMillis} method. + *

+ * Note: Since the underlying field is non-volatile, the returned value + * is only guaranteed to be no less than the last value returned by + * getSafeTimeMillis() call on the same thread. + * + * @return the last "safe" time in milliseconds + */ + public final long getLastSafeTimeMillis() + { + return m_ldtLastSafe; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Updates and returns a "safe" current time in milliseconds based on the + * "unsafe" time. + * + * @param ldtUnsafe the unsafe current time in milliseconds + * + * @return the corrected safe time + */ + protected long updateSafeTimeMillis(long ldtUnsafe) + { + if (compareAndSet(false, true)) + { + try + { + long lJitter = m_lJitter; + long ldtLastSafe = m_ldtLastSafe; + long lDelta = ldtUnsafe - m_ldtLastUnsafe; + long ldtNewSafe = ldtLastSafe; + + if (lDelta > 0) + { + // unsafe progressed + if (ldtUnsafe >= ldtLastSafe) + { + // common case; unsafe is at or ahead of safe; sync clocks + ldtNewSafe = ldtUnsafe; + } + else if (lDelta > lJitter && ldtLastSafe - ldtUnsafe <= lJitter) + { + // unsafe is behind safe and jumped; the jump brought it + // very close (within jitter) to where it was before the + // corresponding regression; this appears to be jitter, hold + // safe and avoid recording anything about this bogus jump as + // that could artificially push safe into the future + return ldtLastSafe; + } + else + { + // unsafe is behind safe and progressed; progress safe slowly + // at half the measured delta or every other ms if delta is 1ms + // allowing unsafe to eventually catch up + ldtNewSafe += lDelta == 1 ? ldtUnsafe % 2 : lDelta / 2; + } + } + else if (lDelta >= -lJitter) + { + // unsafe made an insignificant (within jitter) regression; or + // didn't move at all; hold safe and avoid recording anything about + // this bogus jump as that could artificially push safe into the future + // Note: the same cases are handled in getSafeTimeMillis() but based + // on synchronization ordering it may not be detected until here + return ldtLastSafe; + } + + // except in the case of jitter we update our clocks + m_ldtLastUnsafe = ldtUnsafe; + return m_ldtLastSafe = ldtNewSafe; + } + finally + { + set(false); // unlock + } + } + else + { + // some other thread has locked the clock we have a few options + // - block until they complete, but who likes global contention + // - spin until they complete, but then we just waste CPU, and for what gain? + // - pretend like time has not advanced, no worse then the above and we get to do useful work + return m_ldtLastSafe; // note since we've attempted the CAS this is as good as a volatile read + } + } + + // ----- data members --------------------------------------------------- + + /** + * The last known safe time value. + */ + protected long m_ldtLastSafe; + + /** + * The last recorded unsafe time value. + */ + protected long m_ldtLastUnsafe; + + /** + * The maximum expected jitter exposed by the underlying unsafe clock. + */ + protected final long m_lJitter; + + // ----- constants ------------------------------------------------------ + + /** + * SafeClock singleton. + */ + public static final SafeClock INSTANCE = new SafeClock(); + + /** + * The default jitter threshold. + */ + public static final long DEFAULT_JITTER_THRESHOLD = Long.parseLong(AccessController.doPrivileged( + new PrivilegedAction() + { + public String run() + { + return System.getProperty(SafeClock.class.getName() + ".jitter", "16"); + } + })); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SimpleHolder.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SimpleHolder.java new file mode 100644 index 0000000000000..368d6ac84e56f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SimpleHolder.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +import javax.json.bind.annotation.JsonbProperty; +import java.io.Serializable; + +/** + * SimpleHolder is a basic implementation of the Holder interface. + *

+ * There value is simply held by a non-volatile reference, thus SimpleHolder + * does not provide any inter-thread visibility guarantees. + * + * @param the value type + * + * @author mf 2010.12.02 + */ +public class SimpleHolder + implements Holder, Serializable + { + /** + * Construct a SimpleHolder with no value. + */ + public SimpleHolder() + { + } + + /** + * Construct a SimpleHolder with an initial value. + * + * @param value the initial value + */ + public SimpleHolder(V value) + { + set(value); + } + + + // ----- Holder interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + public void set(V value) + { + m_value = value; + } + + /** + * {@inheritDoc} + */ + public V get() + { + return m_value; + } + + + // ----- data members --------------------------------------------------- + + /** + * The held value. + */ + @JsonbProperty("value") + protected V m_value; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SingleWaiterCooperativeNotifier.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SingleWaiterCooperativeNotifier.java new file mode 100644 index 0000000000000..bd29f95117b32 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SingleWaiterCooperativeNotifier.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +import com.oracle.coherence.common.collections.ConcurrentLinkedQueue; + +import java.util.Queue; +import java.util.concurrent.locks.LockSupport; + + +/** + * SingleWaiterCooperativeNotifier is an extension of the SingleWaiterMultiNotifier which attempts to offload + * potentially expensive "notification" work from signaling threads onto the waiting threads. This notifier + * is beneficial when there are few signaling threads, but potentially many waiting threads, each waiting on + * their own notifier. + *

+ * Unlike the standard Notifier usage, a signaling thread must at some point invoke the static {@link #flush} + * method, or {@link #await} on any SingleWaiterCooperativeNotifier to ensure that all deferred signals + * are processed. + * + * @author mf 2014.03.12 + */ +public class SingleWaiterCooperativeNotifier + extends SingleWaiterMultiNotifier + { + // ----- OffloadingMultiNotifier interface ------------------------------ + + /** + * Ensure that any deferred signals will be processed. + *

+ * Note it is more advantageous if the calling thread's natural idle point is + * to sit in {@link #await}, in which case calling this method is not required. + *

+ */ + public static void flush() + { + flush(FLUSH_AWAKE_COUNT, null); + } + + // ----- Notifier interface --------------------------------------------- + + @Override + public void await(long cMillis) + throws InterruptedException + { + flush(cMillis <= 0 || cMillis > Integer.MAX_VALUE // Note: using cMillis as a count just to limit iterations + ? Integer.MAX_VALUE : (int) cMillis, this); // relative to the allowable wait time + try + { + m_threadAwait = Thread.currentThread(); // done before await's cas to ensure visibility if we wait + super.await(cMillis); + } + finally + { + flush(2, /*self (not waiting)*/ null); // wakeup at least two other threads, forming a wakeup tree + } + } + + @Override + public void signal() + { + if (signalInternal() != null) + { + s_queueDeferred.add(this); + } + // else; no thread to wake; signaling is completed + + if (++s_cSignal == 0) // periodically ensure things are moving + { + flush(1, /*self (not waiting)*/ null); + } + } + + // ----- Object interface ----------------------------------------------- + + @Override + public String toString() + { + String s = super.toString(); + Thread thread = m_threadAwait; + + if (thread != null && !s.endsWith(")")) + { + // delayed signal + s += "(" + thread + ")"; + } + return s + "/" + s_queueDeferred.size(); + } + + // ----- helpers -------------------------------------------------------- + + /** + * Ensure that any deferred signals will be processed. + * + * @param cWake the number of deferrals to signal on the calling thread + * @param self the waiting notifier which is flushing, or null + */ + protected static void flush(int cWake, SingleWaiterCooperativeNotifier self) + { + for (int i = 0; i < cWake && (self == null || self.m_oState == null); ) + { + SingleWaiterCooperativeNotifier notifier = s_queueDeferred.poll(); + if (notifier == null) + { + break; + } + + Object oState = notifier.m_oState; // read to get clean view of m_threadAwait + Thread thread = notifier.m_threadAwait; + if (oState == notifier && thread != null) + { + LockSupport.unpark(thread); + ++i; + } + // else; we can't be sure the signaled thread will ever call flush(), don't rely on it + } + } + + @Override + protected void consumeSignal() + { + m_threadAwait = null; // ensure that null is written before we'd update m_oState to null in super + super.consumeSignal(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The waiting thread, or null. Visibility ensured via super.m_oState writes. + */ + protected Thread m_threadAwait; + + /** + * An intentionally non-volatile signal counter to ensure periodic flushes. + * + * byte is used to allow for auto-flush on every 256 signals (rollover) + */ + protected static byte s_cSignal; + + /** + * The deferred notifiers. + * + * Note: this doesn't have to be a queue (though that marginally improves fairness), it just needs to + * be some highly concurrent data set. Being a queue could result in this being a bottleneck. + */ + private static final Queue s_queueDeferred = new ConcurrentLinkedQueue<>(); + + /** + * The number of threads to awake upon a static flush call. + */ + private static final int FLUSH_AWAKE_COUNT = Integer.parseInt( + System.getProperty(SingleWaiterCooperativeNotifier.class.getName() + ".wakeOnFlush", "1")); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SingleWaiterMultiNotifier.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SingleWaiterMultiNotifier.java new file mode 100644 index 0000000000000..9a748e5755dac --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/SingleWaiterMultiNotifier.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.concurrent.locks.LockSupport; + + +/** + * A Condition-like object, used by a single thread to block for a + * notification, and optimized for many concurrent notifications by other + * threads. Basically, this is a blocking queue without any state to + * actually enqueue: the {@link SingleWaiterMultiNotifier#await()} method + * is analogous to an imaginary "take all" variant of the + * {@link java.util.concurrent.BlockingQueue#take() BlockingQueue.take()} + * method, and the + * {@link SingleWaiterMultiNotifier#signal()} method is analogous to + * {@link java.util.concurrent.BlockingQueue#put(Object) + * BlockingQueue.put()}. + *

+ * Note that no synchronization is needed to use this class; i.e. clients + * must not synchronize on this class prior to calling await() or + * signal(), nor should the use any of the primitive wait() + * or notify() methods. + *

+ * Since SingleWaiterMultiNotifier is only usable by a single waiting thread it is + * does not require an external readiness check, as signaling can record that state. + * + * @author cp/mf 2010-06-15 + */ +public class SingleWaiterMultiNotifier + implements Notifier + { + @Override + public void await(long cMillis) + throws InterruptedException + { + if (m_oState == this) + { + // pre-signaled; extra volatile read is "free" as compared to CAS + // volatile write is same roughly the same cost as CAS + // overall this path is about 2x faster then the equivalent pre-signaled CAS path + consumeSignal(); + } + else if (s_fuState.compareAndSet(this, null, Thread.currentThread())) + { + if (cMillis == 0) + { + Blocking.park(/*blocker*/ this); + } + else + { + Blocking.parkNanos(/*blocker*/ this, cMillis * 1000000); + } + + if (m_oState != this && Blocking.interrupted()) // only pay the cost of interrupt check if we were not signaled + { + consumeSignal(); + throw new InterruptedException(); + } + + consumeSignal(); + } + else if (m_oState == this) + { + // concurrently signaled + consumeSignal(); + } + else // m_oState references another thread (or null) + { + // note, even if m_oState is null we have an error as it could only happen because another + // thread had concurrently awaited, which is not safe and could leave one of the thread stuck + throw new IllegalStateException("unexpected thread '" + m_oState + "' is also awaiting"); + } + } + + @Override + public void signal() + { + LockSupport.unpark(signalInternal()); + } + + + // ----- Object interface ----------------------------------------------- + + @Override + public String toString() + { + Object oState = m_oState; + return super.toString() + (oState == this ? " signaled" : " unsignaled(" + /*Thread*/ oState + ")"); + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Return the thread which is waiting on this notifier. + * + * @return the waiting thread or null + */ + public Thread getWaitingThread() + { + Object oState = m_oState; + return oState == null || oState == this ? null : (Thread) oState; + } + + /** + * Signal the notifier returning any thread which needs to be unparked. + * + * @return a the thread if any which needs to be unparked. + */ + protected Thread signalInternal() + { + // from a performance perspective we want to minimize the number of CASs and thread wakeups + // we can optimize out repeated/concurrent signals as well as signals which occur concurrently + // with a thread wakeing up, we cannot optimize out signals which occur concurrentlly with a + // thread going into await + + Object oState = m_oState; + if (oState == null) + { + if (s_fuState.compareAndSet(this, null, /*signaled*/ this)) + { + // not yet signaled, no thread was waiting + return null; + } + // else new waiter or concurrent signal; fall through + oState = m_oState; + } + + if (oState != this && oState != null && s_fuState.compareAndSet(this, /*thread*/ oState, /*signaled*/ this)) + { + // we've signled a waiting thread + return (Thread) oState; + } + else + { + // already signaled, concurrently signal, or thread concurrently awoke + return null; + } + } + + /** + * Consume the signal. + */ + protected void consumeSignal() + { + m_oState = null; + } + + // ----- data members --------------------------------------------------- + + /** + * The signaling state, null for unsignaled, this for signaled, or a thread for the single awaiting thread + */ + protected volatile Object m_oState; + + /** + * The atomic field updater for {@link #m_oState}. + */ + private static final AtomicReferenceFieldUpdater s_fuState = + AtomicReferenceFieldUpdater.newUpdater(SingleWaiterMultiNotifier.class, Object.class, "m_oState"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/StackTrace.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/StackTrace.java new file mode 100644 index 0000000000000..3be6621870753 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/StackTrace.java @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + +import java.io.BufferedReader; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; + +import static com.oracle.coherence.common.base.Loggers.out; + + +/** + * Class for providing StackTrace functionality. + * + * @author cp 2000.08.02 + * @since Coherence 12.4.1 + */ + +public abstract class StackTrace + { + // ----- stack trace support ---------------------------------------------- + + /** + * Get the StackFrame information for the caller of the current method. + * + * @return the StackFrame information for the caller of the current method + */ + public static StackFrame getCallerStackFrame() + { + // display the code that caused the assertion + String sStack = getStackTrace(); + int of = sStack.indexOf('\n', sStack.lastIndexOf(".getCallerStackFrame(")) + 1; + of = sStack.indexOf('\n', of) + 1; + try + { + return new StackFrame(sStack.substring(of, sStack.indexOf('\n', of))); + } + catch (RuntimeException e) + { + return StackFrame.UNKNOWN; + } + } + + /** + * Get the StackFrame information for the current method. + * + * @return the StackFrame information for the current method + */ + public static StackFrame getStackFrame() + { + // display the code that caused the assertion + String sStack = getStackTrace(); + int of = sStack.indexOf('\n', sStack.lastIndexOf(".getStackFrame(")) + 1; + try + { + return new StackFrame(sStack.substring(of, sStack.indexOf('\n', of))); + } + catch (RuntimeException e) + { + return StackFrame.UNKNOWN; + } + } + + /** + * Iterate the StackFrame information for all callers, going from the + * inside outwards, and starting with the caller of this method. + * + * @return an Iterator of StackFrames + */ + public static StackFrame[] getStackFrames() + { + String sStack = getStackTrace(); + LineNumberReader reader = new LineNumberReader(new StringReader( + sStack.substring(sStack.indexOf('\n', sStack.lastIndexOf( + ".getStackFrames(")) + 1))); + + try + { + ArrayList list = new ArrayList(); + String sLine = reader.readLine(); + while (sLine != null && sLine.length() > 0) + { + StackFrame frame = StackFrame.UNKNOWN; + try + { + frame = new StackFrame(sLine); + } + catch (RuntimeException e) + { + } + + list.add(frame); + sLine = reader.readLine(); + } + return (StackFrame[]) list.toArray(new StackFrame[list.size()]); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + /** + * Build a stack trace for the current thread. + * + * @return a String containing a printable stack trace + */ + public static String getStackTrace() + { + String s = getStackTrace(new Exception()); + return s.substring(s.indexOf('\n', s.indexOf(".getStackTrace(")) + 1); + } + + /** + * Build a stack trace for the passed exception that does not include + * the exception description itself. + * + * @param e a Throwable object that contains stack trace information + * @return a String containing a printable stack trace + */ + public static String getStackTrace(Throwable e) + { + String s = printStackTrace(e); + if (s.startsWith(e.getClass().getName())) + { + s = s.substring(s.indexOf('\n') + 1); + } + return s; + } + + /** + * Extract a throwable's message, and all causal messages. + * + * @param t the throwable + * @param sDelim the delimiter to include between messages + * @return the concatenated messages + */ + public static String getDeepMessage(Throwable t, String sDelim) + { + StringBuilder sb = new StringBuilder(); + + String sMsgLast = null; + for (; t != null; t = t.getCause()) + { + String sMsg = t.getMessage(); + if (!Objects.equals(sMsgLast, sMsg)) + { + if (sMsgLast != null) + { + sb.append(sDelim); + } + sb.append(sMsg); + sMsgLast = sMsg; + } + } + return sb.toString(); + } + + /** + * Build a stack trace for the passed exception. + * + * @param e a Throwable object that contains stack trace information + * @return a String containing a printable stack trace + */ + public static String printStackTrace(Throwable e) + { + try + { + try (Writer writerRaw = new CharArrayWriter(1024)) + { + try (PrintWriter writerOut = new PrintWriter(writerRaw)) + { + e.printStackTrace(writerOut); + } + + writerRaw.flush(); + + return writerRaw.toString(); + } + } + catch (IOException eIO) + { + throw new RuntimeException(eIO); + } + } + + /** + * Ensure the specified Number is a BigDecimal value or convert it into a + * new BigDecimal object. + * + * @param num a Number object + * @return a BigDecimal object that is equal to the passed in Number + */ + public static BigDecimal ensureBigDecimal(Number num) + { + return num instanceof BigDecimal ? (BigDecimal) num : + num instanceof BigInteger ? new BigDecimal((BigInteger) num) : + new BigDecimal(num.doubleValue()); + } + + /** + * A class that provides "stack frame" information from a line of a stack + * trace. + */ + public static class StackFrame + { + /** + * Construct a StackFrame object from a line of a stack trace. + * + * @param sExcept a line of a stack trace in the format used by the + * reference implementation of the JVM + * @throws RuntimeException + */ + public StackFrame(String sExcept) + { + try + { + int of = sExcept.indexOf('('); + String sLeft = sExcept.substring(sExcept.lastIndexOf(' ', of) + 1, of); + String sRight = sExcept.substring(of + 1, sExcept.lastIndexOf(')')); + + of = sLeft.lastIndexOf('.'); + String sClass = sLeft.substring(0, of); + String sMethod = sLeft.substring(of + 1); + + String sFile = "unknown"; + int nLine = 0; + + of = sRight.lastIndexOf(':'); + if (of >= 0) + { + sFile = sRight.substring(0, of); + + try + { + nLine = Integer.parseInt(sRight.substring(of + 1)); + } + catch (RuntimeException e) + { + } + } + + init(sFile, sClass, sMethod, nLine); + } + catch (RuntimeException e) + { + out("Exception constructing StackFrame for \"" + sExcept + "\""); + throw e; + } + } + + /** + * Construct a StackFrame object from its constituent data members. + * + * @param sFile the source file name (e.g. Test.java) + * @param sClass the fully qualified class name (e.g. pkg.Test$1) + * @param sMethod the method name (e.g. main) + * @param nLine the line number (e.g. 17) or 0 if unknown + */ + public StackFrame(String sFile, String sClass, String sMethod, int nLine) + { + init(sFile, sClass, sMethod, nLine); + } + + /** + * Initialize the fields of the StackFrame object. + * + * @param sFile the source file name (e.g. Test.java) + * @param sClass the fully qualified class name (e.g. pkg.Test$1) + * @param sMethod the method name (e.g. main) + * @param nLine the line number (e.g. 17) or 0 if unknown + */ + protected void init(String sFile, String sClass, String sMethod, int nLine) + { + m_sFile = sFile; + m_sClass = sClass; + m_sMethod = sMethod; + m_nLine = nLine; + } + + /** + * @return the source file name (e.g. Test.java) + */ + public String getFileName() + { + return m_sFile; + } + + /** + * @return the fully qualified class name (e.g. pkg.Test$1) + */ + public String getClassName() + { + return m_sClass; + } + + /** + * @return the short class name (e.g. Test.1) + */ + public String getShortClassName() + { + String sClass = m_sClass; + sClass = sClass.substring(sClass.lastIndexOf('.') + 1); + return sClass.replace('$', '.'); + } + + /** + * @return the method name (e.g. main) + */ + public String getMethodName() + { + return m_sMethod; + } + + /** + * @return the line number (e.g. 17) or 0 if unknown + */ + public int getLineNumber() + { + return m_nLine; + } + + /** + * @return the line of source code if possible or null + */ + public String getLine() + { + int nLine = getLineNumber(); + if (nLine == 0) + { + return null; + } + + try + { + String sClass = getClassName(); + int of = sClass.indexOf('$'); + if (of >= 0) + { + sClass = sClass.substring(0, of); + } + + InputStream stream = Class.forName(sClass, false, + Classes.getContextClassLoader()) + .getClassLoader().getResourceAsStream( + sClass.replace('.', '/') + ".java"); + if (stream != null) + { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(stream))) + { + String sLine = null; + for (int i = 0; i < nLine; ++i) + { + sLine = reader.readLine(); + } + return sLine; + } + } + } + catch (Throwable t) + { + } + + return null; + } + + /** + * @return a String representation of the StackFrame + */ + public String toString() + { + int nLine = getLineNumber(); + String sLine = nLine == 0 ? "?" : "" + nLine; + + return getClassName() + '.' + getMethodName() + '(' + + getFileName() + ':' + sLine + ')'; + } + + /** + * @return a short String representation of the StackFrame + */ + public String toShortString() + { + int nLine = getLineNumber(); + String sLine = nLine == 0 ? "?" : "" + nLine; + + return getShortClassName() + '.' + getMethodName() + + " [" + sLine + ']'; + } + + public static final StackFrame UNKNOWN = new StackFrame( + "unknown", "unknown", "unknown", 0); + + private String m_sFile; + private String m_sClass; + private String m_sMethod; + private int m_nLine; + } + + /** + * Get the source code expression from the method that called the + * specified method. + * + * @param sMethod the first method on the stack after the method whose + * source code is desired + * + * @return null on any failure, otherwise the expression + */ + static String getExpression(String sMethod) + { + StackTrace.StackFrame[] aframe = getStackFrames(); + int i = 0; + + // find method in call stack + while (!aframe[i].getMethodName().equals(sMethod)) + { + ++i; + } + + // find calling method + while (aframe[i].getMethodName().equals(sMethod)) + { + ++i; + } + + // get source line + String sLine = aframe[i].getLine(); + if (sLine != null) + { + int of = sLine.indexOf(sMethod); + if (of >= 0) + { + // this is unavoidably imprecise but will usually work correctly + int ofLParen = sLine.indexOf('(', of); + int ofRParen = sLine.lastIndexOf(')'); + if (ofLParen > of && ofRParen > ofLParen) + { + return sLine.substring(ofLParen + 1, ofRParen); + } + } + } + + return null; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/TimeHelper.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/TimeHelper.java new file mode 100644 index 0000000000000..e67dcb719268b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/TimeHelper.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.base; + + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.ConcurrentHashMap; + +import static com.oracle.coherence.common.base.Exceptions.ensureRuntimeException; + + +/** + * Class for providing time functionality. + * + * @author cp 2000.08.02 + * @since Coherence 12.4.1 + */ + +public abstract class TimeHelper + { + // ----- time routines ---------------------------------------------- + + /** + * Return the number of milliseconds which have elapsed since the JVM was + * started. + * + * @return the number of milliseconds which have elapsed since the JVM was + * started + */ + public static long getUpTimeMillis() + { + Method methodUptime = s_methodUptime; + if (methodUptime == null) + { + return System.currentTimeMillis() - s_ldtStartTime; + } + + try + { + return ((Long) methodUptime.invoke(s_oRuntimeMXBean)).longValue(); + } + catch (Throwable e) + { + if (e instanceof InvocationTargetException) + { + e = ((InvocationTargetException) e).getTargetException(); + } + throw ensureRuntimeException(e); + } + } + + /** + * Returns a "safe" current time in milliseconds. + * + * @return the difference, measured in milliseconds, between + * the corrected current time and midnight, January 1, 1970 UTC. + * @see SafeClock + */ + public static long getSafeTimeMillis() + { + return s_safeClock.getSafeTimeMillis(System.currentTimeMillis()); + } + + /** + * Returns the last "safe" time as computed by a previous call to + * the {@link #getSafeTimeMillis} method. + * + * @return the last "safe" time in milliseconds + * @see SafeClock + */ + public static long getLastSafeTimeMillis() + { + return s_safeClock.getLastSafeTimeMillis(); + } + + /** + * compute the number of milliseconds until the specified time. + *

+ * Note: this method will only return zero if ldtTimeout == Long.MAX_VALUE. + * + * @param ldtTimeout the timeout as computed by {@link #getSafeTimeMillis} + * @return the number of milliseconds to wait, or negative if the timeout + * has expired + */ + public static long computeSafeWaitTime(long ldtTimeout) + { + if (ldtTimeout == Long.MAX_VALUE) + { + return 0; + } + + long ldtNow = getSafeTimeMillis(); + return ldtTimeout == ldtNow ? -1 : ldtTimeout - ldtNow; + } + + /** + * Gets the {@link TimeZone} for the given ID. + *

+ * This method will cache returned TimeZones to avoid the contention + * caused by the {@link TimeZone#getTimeZone} implementation. + * + * @param sId the ID for a {@link TimeZone} + * @return the specified {@link TimeZone}, or the GMT zone if the + * given ID cannot be understood. + * @see TimeZone#getTimeZone(String) + * @since Coherence 12.1.3 + */ + public static TimeZone getTimeZone(String sId) + { + TimeZone timeZone = (TimeZone) s_mapTimeZones.get(sId); + if (timeZone == null) + { + timeZone = TimeZone.getTimeZone(sId); + s_mapTimeZones.put(sId, timeZone); + } + return timeZone; + } + + + // ----- data members --------------------------------------------------- + + /** + * The map of cached {@link TimeZone}s keyed by ID. + */ + private static Map s_mapTimeZones = new ConcurrentHashMap(); + + /** + * The estimated JVM start time. + */ + private static final long s_ldtStartTime = System.currentTimeMillis(); + + /** + * The shared SafeClock. + */ + private static SafeClock s_safeClock = new SafeClock(s_ldtStartTime); + + /** + * The java.lang.RuntimeMXBean or null if not available. + */ + private static final Object s_oRuntimeMXBean; + + /** + * RuntimeMXBean.getUpTime() Method or null if not available. + */ + private static final Method s_methodUptime; + + static + { + Object oRuntimeMXBean = null; + Method methodUptime = null; + + try + { + oRuntimeMXBean = Class.forName( + "java.lang.management.ManagementFactory"). + getMethod("getRuntimeMXBean").invoke(null); + if (oRuntimeMXBean != null) + { + methodUptime = Class.forName( + "java.lang.management.RuntimeMXBean"). + getMethod("getUptime"); + } + } + catch (Throwable eIgnore) + { + } + + s_oRuntimeMXBean = oRuntimeMXBean; + s_methodUptime = methodUptime; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Timeout.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Timeout.java new file mode 100644 index 0000000000000..461b1b5b93325 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/Timeout.java @@ -0,0 +1,316 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + +import com.oracle.coherence.common.util.SafeClock; + +import java.util.concurrent.TimeUnit; + +/** + * Timeout provides a mechanism for allowing a thread to interrupt itself if it doesn't return + * to a specific call site within a given timeout. Timeout instances are intended to be + * used with the try-with-resource pattern. Once constructed a Timeout attempts to ensure that + * the corresponding try-with-resource block completes within the specified timeout and if it + * does not the thread will self-interrupt. Exiting the timeout block will automatically clear + * any interrupt present on the thread and in such a case an InterruptedException will be thrown. + * + *

+ * try (Timeout t = Timeout.after(5, TimeUnit.SECONDS))
+ *     {
+ *     doSomething();
+ *     } // this thread will self-interrupt if it doesn't reach this line within 5 seconds
+ * catch (InterruptedException e)
+ *     {
+ *     // thread timed out or was otherwise interrupted
+ *     }
+ * 
+ * + * In order for this to work any blocking code executed from within the context of the Timeout must use the + * {@link Blocking} static helper methods for blocking. An example of a compatible blocking call would be: + * + *
+ * void doSomething()
+ *     {
+ *     Object oField = m_oField;
+ *     synchronized (oField)
+ *         {
+ *         Blocking.wait(oField); // rather then oField.wait();
+ *         }
+ *     }
+ * 
+ * + * Note that Timeout can only self-interrupt at interruptible points, and does not defend against + * CPU bound loops for example. + * + * @author mf 2015.02.23 + */ +public class Timeout + implements AutoCloseable + { + // ----- constructors --------------------------------------------------- + + /** + * Specify a new timeout. + * + * This constructor variant allows the caller to override a parent timeout. This is + * rarely needed, and is roughly the equivalent of silently consuming a thread interrupt + * without rethrowing the InterruptedException. + * + * @param cMillis the new timeout. + * @param fForceOverride true if this timeout is allowed to extend a parent timeout. + */ + protected Timeout(long cMillis, boolean fForceOverride) + { + MutableLong mlTimeout = s_tloTimeout.get(); + if (mlTimeout == null) + { + s_tloTimeout.set(mlTimeout = new MutableLong(Long.MAX_VALUE)); + f_fTloCreator = true; + } + else + { + f_fTloCreator = false; + } + + f_mlTimeout = mlTimeout; + f_lTimeoutOrig = f_mlTimeout.get(); + + if (f_lTimeoutOrig == Long.MAX_VALUE) // orig is disabled (common) + { + f_cMillisTimeout = cMillis; + f_mlTimeout.set(-cMillis); + } + else if (f_lTimeoutOrig < 0) // orig is relative (common) + { + if (fForceOverride || cMillis < -f_lTimeoutOrig) + { + f_cMillisTimeout = cMillis; + f_mlTimeout.set(-cMillis); + } + else // we are not allowed to extend an existing timeout + { + f_cMillisTimeout = f_lTimeoutOrig; + } + } + else // orig is timestamp + { + // TODO: we could avoid pulling the time here if we retained a ref to the prior Timeout object + // rather then just it's timeout value. In this case we'd have the absolute timeout and it's + // relative value and could then compute our updated absolute from those. + long ldtTimeout = SafeClock.INSTANCE.getSafeTimeMillis() + cMillis; + if (fForceOverride || ldtTimeout < f_lTimeoutOrig) + { + f_cMillisTimeout = cMillis; + f_mlTimeout.set(ldtTimeout); + } + else // we are not allowed to extend an existing timeout + { + f_cMillisTimeout = f_lTimeoutOrig; + } + } + } + + + // ----- AutoCloseable interface ---------------------------------------- + + /** + * As part of closing the Timeout resource any former timeout will be restored. + * + * @throws InterruptedException if the calling thread is interrupted + */ + @Override + public void close() + throws InterruptedException + { + // we restore the former timeout, even if it is expired + + if (f_fTloCreator) + { + // cleanup the TLO when the Timeout stack has been fully popped + s_tloTimeout.set(null); + } + else if (f_lTimeoutOrig < 0) // orig was never realized + { + long lTimeoutCurr = f_mlTimeout.get(); + if (lTimeoutCurr < 0 || // we've yet to block + lTimeoutCurr == Long.MAX_VALUE) // timeout was disabled (note restore is suspect, but override has already violated orig) + { + // simply restore the orig value + f_mlTimeout.set(f_lTimeoutOrig); + } + else + { + // curr was realized, orig was not, adjust orig accordingly + // and set it as new timeout + f_mlTimeout.set(lTimeoutCurr + (-f_lTimeoutOrig - f_cMillisTimeout)); + } + } + else // orig is realized, simply restore it + { + f_mlTimeout.set(f_lTimeoutOrig); + } + + // checking to see if the thread is interrupted here ensures that if the nested code within the + // interrupt block were to suppress the InterruptedException (possibly from a timeout) that it + // gets recreated here so the application is forced to deal with it + // Note we don't just throw because of a timeout, as the general contract of a method which + // throws InterruptedException is that it throws if the thread in interrupted, period + // Note: we don't try to throw some derived exception such as InterruptedTimeoutException as + // we can't ensure that all timeout points would actually result in that exception. For instance + // a timeout in LockSupport.park() will interrupt the thread by throw nothing, and some other code + // could then detect the interrupt and throw a normal InterruptedException. Overall the intent + // here is to just make the timeout feature be indistinugisable from another thread interrupting + // this the thread. + if (Thread.interrupted()) + { + throw new InterruptedException(); + } + } + + // ----- static interface ----------------------------------------------- + + // Note: the use of static factory methods in addition to being more expressive + // then public constructors allows for the potential to pool Timeout objects + // in the future to further reduce the cost of creating a timeout block. + // It would seem likely that Timeout objects may live for a decent enough duration + // that they could become tenured, and thus pooling would be worth consideration. + // The pool could also be stored in a ThreadLocal and could simply be an array of + // Timeouts and an index into the next free slot. Considering that they are + // effectively bound to a callsite and the stack depth the expectation is that + // there would be a relatively small number of them per thread. If implemented + // this ThreadLocal could also hold the MutableLong timeout thus avoiding the + // need for multiple ThreadLocal lookups. + + /** + * Specify a new timeout. Note that the calling thread's timeout will only be + * changed if the specified timeout is less then any existing timeout already + * active on the thread. + * + * @param time the new timeout + * @param unit the unit the timeout is expressed in + * + * @return a Timeout instance to be used within a try-with-resource block + */ + public static Timeout after(long time, TimeUnit unit) + { + return after(Math.max(unit.toMillis(time), 1)); // ensure at least 1ms in case duration was expressed as sub-millisecond + } + + /** + * Specify a new timeout. Note that the calling thread's timeout will only be + * changed if the specified timeout is less then any existing timeout already + * active on the thread. + * + * @param cMillis the new timeout + * + * @return a Timeout instance to be used within a try-with-resource block + */ + public static Timeout after(long cMillis) + { + return new Timeout(cMillis, /*fForceOverride*/ false); + } + + /** + * Specify a new timeout, potentially extending an already active timeout. + *

+ * This variant allows the caller to extend a parent timeout. This is rarely + * needed, and is roughly the equivalent of silently consuming a thread interrupt + * without rethrowing the InterruptedException. Use of this method should + * be extremely limited. + * + * @param cMillis the new timeout + * + * @return a Timeout instance to be used within a try-with-resource block + */ + public static Timeout override(long cMillis) + { + return new Timeout(cMillis, /*fForceOverride*/ true); + } + + /** + * Return the number of milliseconds before this thread will timeout. + * + * Note if the current thread is timed out then invoking this method will + * also interrupt the thread. This method can be used to externally + * add Timeout support for other blocking APIs not covered by the existing + * Timeout helpers. + * + * @return the number of remaining milliseconds, 0 if timed out, or Long.MAX_VALUE if disabled. + */ + public static long remainingTimeoutMillis() + { + MutableLong mlTimeout = s_tloTimeout.get(); + if (mlTimeout == null) + { + // no timeout configured; avoid pulling local time + return Long.MAX_VALUE; + } + + long lTimeout = mlTimeout.get(); + long ldtNow = SafeClock.INSTANCE.getSafeTimeMillis(); + if (lTimeout < 0) + { + // timeout is still relative; actualize and store it + mlTimeout.set(ldtNow - lTimeout); // sets timeout as now + -lTimeout + return -lTimeout; // no need to compute relative + } + // else; timeout was already realized, compute remainder + + long cMillis = lTimeout - ldtNow; + if (cMillis <= 0) + { + Thread.currentThread().interrupt(); + return 0; + } + return cMillis; + } + + /** + * Return true if the calling thread is timed out. + * + * Note if the current thread is timed out then invoking this method will + * also interrupt the thread. This method can be used to externally + * add Timeout support for other blocking APIs not covered by the existing + * Timeout helpers. + * + * @return true if the calling thread is timed out + */ + public static boolean isTimedOut() + { + return remainingTimeoutMillis() == 0; + } + + // ----- data members --------------------------------------------------- + + /** + * True iff this Timeout created (and thus must ultimately destroy) the TLO. + */ + protected final boolean f_fTloCreator; + + /** + * Cached reference to the thread's MutableLong holding it's current timeout. + */ + protected final MutableLong f_mlTimeout; + + /** + * This Timeout's timeout. + */ + protected final long f_cMillisTimeout; + + /** + * The original timeout before this instance changed it. + */ + protected final long f_lTimeoutOrig; + + /** + * A thread-local containing the calling thread's timeout value. Value which are greater or equal to zero + * are used to indicate timeout timestamps. Negative values are relative timeouts which haven't yet been + * realized into a timestamp. This allows for an optimization where we can avoid obtaining + * the current time when "setting" the timeout, and defer it until we are about to block. + */ + protected static final ThreadLocal s_tloTimeout = new ThreadLocal<>(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/VolatileHolder.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/VolatileHolder.java new file mode 100644 index 0000000000000..01d3d5433dd95 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/VolatileHolder.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.base; + + +/** + * VolatileHolder is a basic implementation of the Holder interface where + * the held object is referenced from a volatile reference. + * + * @param the value type + * + * @author mf 2010.12.02 + */ +public class VolatileHolder + implements Holder + { + /** + * Construct a VolatileHolder with no value. + */ + public VolatileHolder() + { + } + + /** + * Construct a VolatileHolder with an initial value. + * + * @param value the initial value + */ + public VolatileHolder(V value) + { + set(value); + } + + + // ----- Holder interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + public void set(V value) + { + m_value = value; + } + + /** + * {@inheritDoc} + */ + public V get() + { + return m_value; + } + + + // ----- data members --------------------------------------------------- + + /** + * The held value. + */ + protected volatile V m_value; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/package.html new file mode 100644 index 0000000000000..8815b7837e917 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/base/package.html @@ -0,0 +1,5 @@ + +The base package provides a number of classes to complement those in the java.lang package. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/AbstractStableIterator.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/AbstractStableIterator.java new file mode 100644 index 0000000000000..f099f2864a5b3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/AbstractStableIterator.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.NoSuchElementException; + + +/** + * An abstract Iterator implementation that is stable between the + * {@link #hasNext} and {@link #next} methods, and between the {@link #next} + * and {@link #remove()} methods. + * + * @author cp 2003.05.24, 2010.12.09 + */ +public abstract class AbstractStableIterator + implements Iterator, Enumeration + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor. + */ + public AbstractStableIterator() + { + } + + + // ----- Iterator interface --------------------------------------------- + + /** + * {@inheritDoc} + */ + public boolean hasNext() + { + if (m_fNextReady) + { + return true; + } + + advance(); + return m_fNextReady; + } + + /** + * {@inheritDoc} + */ + public T next() + { + if (!m_fNextReady) + { + advance(); + if (!m_fNextReady) + { + throw new NoSuchElementException(); + } + } + + T oNext = m_oNext; + + m_fNextReady = false; + m_fCanDelete = true; + m_oPrev = oNext; + + return oNext; + } + + /** + * {@inheritDoc} + */ + public void remove() + { + if (m_fCanDelete) + { + m_fCanDelete = false; + remove(m_oPrev); + } + else + { + throw new IllegalStateException(); + } + } + + + // ----- Enumeration interface ------------------------------------------ + + /** + * {@inheritDoc} + */ + public boolean hasMoreElements() + { + return hasNext(); + } + + /** + * {@inheritDoc} + */ + public T nextElement() + { + return next(); + } + + + // ----- internal ------------------------------------------------------- + + /** + * Obtain the previous object provided by the Iterator. + * + * @return the object previously returned from a call to {@link #next} + */ + protected T getPrevious() + { + return m_oPrev; + } + + /** + * Specify the next object to provide from the Iterator. + * + * @param oNext the next object to provide from the Iterator + */ + protected void setNext(T oNext) + { + m_oNext = oNext; + m_fNextReady = true; + } + + /** + * Advance to the next object. + *

+ * This method must be implemented by the concrete sub-class by calling + * {@link #setNext} if there is a next object. + */ + protected abstract void advance(); + + /** + * Remove the specified item. + *

+ * This is an optional operation. If the Iterator supports element + * removal, then it should implement this method, which is delegated to by + * the {@link #remove()} method. + * + * @param oPrev the previously iterated object that should be removed + * + * @throws UnsupportedOperationException if removal is not supported + */ + protected void remove(T oPrev) + { + throw new UnsupportedOperationException(); + } + + + // ----- data members --------------------------------------------------- + + /** + * Set to true when m_oNext is the next object to return + * from the iterator. If there is no next object, or if the next object + * is not determined yet, then this will be false. Set up by + * {@link #setNext} and reset by {@link #next}. + */ + private boolean m_fNextReady; + + /** + * The next object to return from this iterator. Set up by + * {@link #setNext} and reset by {@link #next}. + */ + private T m_oNext; + + /** + * Set to true when the m_oPrev object has been returned + * but not yet removed. Set up by {@link #next} and reset by + * {@link #remove()}. + */ + private boolean m_fCanDelete; + + /** + * The object that can be deleted, if any. Set up by {@link #next}. + */ + private T m_oPrev; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/Arrays.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/Arrays.java new file mode 100644 index 0000000000000..84ca0e3dd4755 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/Arrays.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + +import java.util.concurrent.ThreadLocalRandom; + +import java.util.Random; + + +/** + * Arrays is a series of Array based helper methods. + * + * @author mf 2014.01.28 + */ +public final class Arrays + { + /** + * Shuffle a portion of the specified array's content. + * + * @param ao the array to shuffle + * @param of the start index + * @param c the number of elements to shuffle + */ + public static void shuffle(Object[] ao, int of, int c) + { + // from Collections.shuffle + Random rand = ThreadLocalRandom.current(); + for (int i = c; i > 1; i--) + { + int r = rand.nextInt(i); + Object o = ao[of + i - 1]; + + ao[of + i - 1] = ao[of + r]; + ao[of + r] = o; + } + } + + /** + * Shuffle the specified array's content. + * + * @param ao the array to shuffle + */ + public static void shuffle(Object[] ao) + { + shuffle(ao, 0, ao.length); + } + + /** + * Perform an insertion of the specified value into the sorted array. + * + * If the value is already present in the array then no insertion will be performed and the + * same array will be returned. If the value is not present then a new array will be created + * and will contain the specified value in the appropriate location. + * + * @param an the array to insert into, may be null + * @param n the value to insert + * + * @return the resulting array. + */ + public static int[] binaryInsert(int[] an, int n) + { + if (an == null) + { + return new int[]{n}; + } + + int of = java.util.Arrays.binarySearch(an, n); + if (of < 0) + { + // insertion required + of = -of; // correct offset to represent the insertion point, or rather point just after it + + int[] anNew = new int[an.length + 1]; + System.arraycopy(an, 0, anNew, 0, of - 1); + anNew[of - 1] = n; + System.arraycopy(an, of - 1, anNew, of, an.length - of + 1); + + return anNew; + } + // else; already present + + return an; + } + + // ----- data members --------------------------------------------------- + + /** + * Random number generator. + */ + private static final Random s_rand = new Random(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ChainedIterator.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ChainedIterator.java new file mode 100644 index 0000000000000..fbdd877e5847e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ChainedIterator.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + + +import java.util.Iterator; + + +/** + * Provide an Iterator which iterates over the contents of multiple Iterators. + * + * @author cp 1998.08.07, 2010.12.08 + */ +public class ChainedIterator + extends AbstractStableIterator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an enumerator that will first enumerate the first Iterator + * and then will enumerate the second Iterator as if they were together + * a single Iterator. + * + * @param iterFirst the first Iterator + * @param iterSecond the second Iterator + */ + public ChainedIterator(Iterator iterFirst, Iterator iterSecond) + { + this(new Iterator[] {iterFirst, iterSecond}); + } + + /** + * Construct an enumerator that will first enumerate the Iterators + * passed in the array as if they were together a single enumerator. + * + * @param aIter an array of Iterators + */ + public ChainedIterator(Iterator[] aIter) + { + m_aIter = aIter; + } + + + // ----- Iterator interface --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public T next() + { + T oNext = super.next(); + m_iterPrevious = m_iterCurrent; + return oNext; + } + + + // ----- internal ------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void advance() + { + Iterator iter = m_iterCurrent; + + if (iter == null || !iter.hasNext()) + { + Iterator[] aIter = m_aIter; + int cIters = aIter.length; + int iNext = m_iNextIter; + do + { + if (iNext >= cIters) + { + return; + } + + iter = aIter[iNext++]; + } + while (!iter.hasNext()); + + m_iNextIter = iNext; + m_iterCurrent = iter; + } + + setNext(iter.next()); + } + + /** + * {@inheritDoc} + */ + @Override + protected void remove(T oPrev) + { + Iterator iter = m_iterPrevious; + if (iter == null) + { + throw new IllegalStateException(); + } + iter.remove(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The Iterators. + */ + private final Iterator[] m_aIter; + + /** + * The next Iterator (index into m_aIter) to iterate. + */ + private int m_iNextIter; + + /** + * The current Iterator. + */ + private Iterator m_iterCurrent; + + /** + * The previous Iterator. + */ + private Iterator m_iterPrevious; + } + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ConcurrentHashMap.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ConcurrentHashMap.java new file mode 100644 index 0000000000000..8e0090953758a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ConcurrentHashMap.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + +import java.util.function.BiFunction; +import java.util.function.Function; + +import java.util.Map; + +/** + * An extension to the standard {@link java.util.concurrent.ConcurrentHashMap} that fixes some of its deficiencies. + * + * @author mf 2016.01.17 + */ +public class ConcurrentHashMap + extends java.util.concurrent.ConcurrentHashMap + { + /** + * Creates a new, empty map with the default initial table size (16). + */ + public ConcurrentHashMap() + { + super(); + } + + /** + * Creates a new, empty map with an initial table size + * accommodating the specified number of elements without the need + * to dynamically resize. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + */ + public ConcurrentHashMap(int initialCapacity) + { + super(initialCapacity); + } + + /** + * Creates a new map with the same mappings as the given map. + * + * @param m the map + */ + public ConcurrentHashMap(Map m) + { + super(m); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}) and + * initial table density ({@code loadFactor}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @throws IllegalArgumentException if the initial capacity of + * elements is negative or the load factor is nonpositive + */ + public ConcurrentHashMap(int initialCapacity, float loadFactor) + { + super(initialCapacity, loadFactor); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}), table + * density ({@code loadFactor}), and number of concurrently + * updating threads ({@code concurrencyLevel}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @param concurrencyLevel the estimated number of concurrently + * updating threads. The implementation may use this value as + * a sizing hint. + * @throws IllegalArgumentException if the initial capacity is + * negative or the load factor or concurrencyLevel are + * nonpositive + */ + public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) + { + super(initialCapacity, loadFactor, concurrencyLevel); + } + + /** + * As compared to {@link java.util.concurrent.ConcurrentHashMap#computeIfAbsent(Object, Function)} this + * implementation will not synchronize unless the mappingFunction will be run. + * + * {@inheritDoc} + */ + @Override + public V computeIfAbsent(K key, Function mappingFunction) + { + // j.u.c.CHM always syncs on the entry even if it won't update the value! + V v = get(key); + return v == null + ? super.computeIfAbsent(key, mappingFunction) + : v; + } + + /** + * As compared to {@link java.util.concurrent.ConcurrentHashMap#computeIfPresent(Object, BiFunction)} this + * implementation will not synchronize unless the mappingFunction will be run. + * + * {@inheritDoc} + */ + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) + { + // j.u.c.CHM always syncs on the entry even if it won't update the value! + return containsKey(key) + ? super.computeIfPresent(key, remappingFunction) + : null; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ConcurrentLinkedQueue.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ConcurrentLinkedQueue.java new file mode 100644 index 0000000000000..72760facadf02 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ConcurrentLinkedQueue.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + +/** + * ConcurrentLinkedQueue is a low-contention unbounded Queue implementation. + *

+ * Unlike the java.util.concurrent collections, the ConcurrentLinkedQueue supports null elements. + * Note however that this queue does not support out-of-order element removal, and as such + * {@link #remove(Object)}, {@link #removeAll(java.util.Collection)}, {@link #retainAll(java.util.Collection)}, + * and {@link java.util.Iterator#remove()} are not supported. + *

+ * + * @param the type of the values that will be stored in the queue + * + * @author mf 2014.01.09 + */ +public class ConcurrentLinkedQueue + extends java.util.concurrent.ConcurrentLinkedQueue + { + // Note: this class is purely retained to conveniently modify the hierarchy + // of some internal queue implementations, while currently j.u.c.CLQ + // has proven to have better performance + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ConcurrentLinkedStack.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ConcurrentLinkedStack.java new file mode 100644 index 0000000000000..1222c74bd6beb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/ConcurrentLinkedStack.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicReference; + + +/** + * ConcurrentLinkedStack is a low-contention unbounded Stack implementation. + *

+ * Unlike the java.util.concurrent collections, the ConcurrentLinkedStack supports null elements, and also + * has a constant time {@link #size} implementation. + *

+ * + * @param the type of the values that will be stored in the Stack + * + * @author mf/cp 2013.03.22 + */ +public class ConcurrentLinkedStack + implements Stack, Iterable + { + // ----- Stack interface ------------------------------------------------ + + @Override + public void push(V value) + { + Element next; + Element element = new Element(value); + do + { + element.m_next = next = f_refHead.get(); + element.m_cHeight = next.m_cHeight + 1; + } + while (!f_refHead.compareAndSet(next, element)); + } + + @Override + public V pop() + { + Element head; + do + { + head = f_refHead.get(); + } + while (head != TAIL && !f_refHead.compareAndSet(head, head.m_next)); + + head.m_next = null; // avoid infecting stack via tenured garbage + + return head.f_value; + } + + @Override + public V peek() + { + return f_refHead.get().f_value; + } + + @Override + public boolean isEmpty() + { + return f_refHead.get() == TAIL; + } + + @Override + public int size() + { + return f_refHead.get().m_cHeight; + } + + // ----- Iterable interface --------------------------------------------- + + @Override + /** + * Return a read-only iterator over the Stack. The iterator creates a + * "free" (no cost) snapshot of the stack. + *

+ * This iterator does not support {@link Iterator#remove}. + * + * @return an iterator over the stack + */ + public Iterator iterator() + { + return new Iterator() + { + @Override + public boolean hasNext() + { + return m_next != TAIL; + } + + @Override + public V next() + { + Element current = m_next; + if (current == TAIL) + { + throw new NoSuchElementException(); + } + + m_next = current.m_next; + return current.f_value; + } + + @Override + public void remove() + { + // despite being technically re-linkable, this would mean that Node.m_next needed to be CAS able and + // would open up a number of issues with the overall implementation + throw new UnsupportedOperationException(); + } + + private Element m_next = f_refHead.get(); + }; + } + + // ----- inner class: Node ---------------------------------------------- + + /** + * Node is a holder for an element within the stack. + * + * @param the type of the value + */ + static final class Element + { + /** + * Construct a Element to hold a value. + * + * @param value the value + */ + Element(V value) + { + f_value = value; + } + + /** + * Construct the "tail" Element, which is always the last Element in the stack. + */ + Element() + { + f_value = null; + m_next = this; + m_cHeight = 0; + } + + /** + * The value stored in this Element. + */ + protected final V f_value; + + /** + * The height of the stack from this Element down. + *

+ * This field is effectively final after being pushed into the stack. + */ + private int m_cHeight; + + /** + * The next Element down in the stack + *

+ * This field is effectively final after being pushed into the stack. + */ + private Element m_next; + } + + // ----- data members --------------------------------------------------- + + /** + * The bottom of all stacks. ("It's TAILs the whole way down.") + */ + protected static final Element TAIL = new Element(); + + /** + * Reference to the top of the stack. + */ + protected final AtomicReference> f_refHead = new AtomicReference>(TAIL); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/InflatableMap.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/InflatableMap.java new file mode 100644 index 0000000000000..fc916d677c895 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/InflatableMap.java @@ -0,0 +1,1062 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.NotActiveException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.Serializable; + +import java.lang.reflect.Array; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; + + +/** +* An implementation of java.util.Map that is optimal (in terms of both size +* and speed) for very small sets of data but still works excellently with +* large sets of data. This implementation is not thread-safe. +*

+* The InflatableMap implementation switches at runtime between several different +* sub-implementations for storing the Map of objects, described here: +* +*

    +*
  1. "empty map" - a map that contains no data; +*
  2. "single entry" - a reference directly to a single map entry +*
  3. "Object[]" - a reference to an array of entries; the item limit for +* this implementation is determined by the THRESHOLD constant; +*
  4. "delegation" - for more than THRESHOLD items, a map is created to +* delegate the map management to; sub-classes can override the default +* delegation class (java.util.HashMap) by overriding the factory method +* instantiateMap. +*
+*

+* The InflatableMap implementation supports the null key value. +* +* @author cp 06/29/99 +*/ +public class InflatableMap + extends AbstractMap + implements Cloneable, Externalizable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a InflatableMap. + */ + public InflatableMap() + { + } + + /** + * Construct a InflatableMap with the same mappings as the given map. + * + * @param map the map whose mappings are to be placed in this map. + */ + public InflatableMap(Map map) + { + putAll(map); + } + + + // ----- Map interface -------------------------------------------------- + + /** + * Returns true if this map contains no key-value mappings. + * + * @return true if this map contains no key-value mappings + */ + public boolean isEmpty() + { + return m_nImpl == I_EMPTY; + } + + /** + * Returns the number of key-value mappings in this map. + * + * @return the number of key-value mappings in this map + */ + public int size() + { + switch (m_nImpl) + { + case I_EMPTY: + return 0; + + case I_SINGLE: + return 1; + + case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: + case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: + return m_nImpl - I_ARRAY_1 + 1; + + case I_OTHER: + return ((Map) m_oContents).size(); + + default: + throw new IllegalStateException(); + } + } + + /** + * Returns true if this map contains a mapping for the specified + * key. + * + * @return true if this map contains a mapping for the specified + * key, false otherwise. + */ + public boolean containsKey(Object oKey) + { + switch (m_nImpl) + { + case I_EMPTY: + return false; + + case I_SINGLE: + { + Object oKeyEntry = ((Map.Entry) m_oContents).getKey(); + return Objects.equals(oKey, oKeyEntry); + } + + case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: + case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: + { + // "Entry[]" implementation + Map.Entry[] aEntry = (Map.Entry[]) m_oContents; + int c = m_nImpl - I_ARRAY_1 + 1; + return indexOf(aEntry, c, oKey) >= 0; + } + + case I_OTHER: + return ((Map) m_oContents).containsKey(oKey); + + default: + throw new IllegalStateException(); + } + } + + /** + * Returns the value to which this map maps the specified key. + * + * @param oKey the key object + * + * @return the value to which this map maps the specified key, + * or null if the map contains no mapping for this key + */ + public V get(Object oKey) + { + switch (m_nImpl) + { + case I_EMPTY: + return null; + + case I_SINGLE: + { + Map.Entry entry = (Map.Entry) m_oContents; + K entryKey = entry.getKey(); + return Objects.equals(oKey, entryKey) ? entry.getValue() : null; + } + + case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: + case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: + { + // "Entry[]" implementation + Map.Entry[] aEntry = (Map.Entry[]) m_oContents; + int c = m_nImpl - I_ARRAY_1 + 1; + int i = indexOf(aEntry, c, oKey); + return i < 0 ? null : aEntry[i].getValue(); + } + + case I_OTHER: + return ((Map) m_oContents).get(oKey); + + default: + throw new IllegalStateException(); + } + } + + /** + * Associates the specified value with the specified key in this map. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * + * @return previous value associated with specified key, or null + * if there was no mapping for key + */ + public V put(K key, V value) + { + switch (m_nImpl) + { + case I_EMPTY: + m_nImpl = I_SINGLE; + m_oContents = instantiateEntry(key, value); + return null; + + case I_SINGLE: + { + Map.Entry entry = (Map.Entry) m_oContents; + K entryKey = entry.getKey(); + V prevValue = null; + if (Objects.equals(key, entryKey)) + { + prevValue = entry.getValue(); + entry.setValue(value); + } + else + { + // grow to array implementation + Map.Entry[] aEntry = new Map.Entry[THRESHOLD]; + aEntry[0] = entry; + aEntry[1] = instantiateEntry(key, value); + + m_nImpl = I_ARRAY_2; + m_oContents = aEntry; + } + + return prevValue; + } + + case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: + case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: + { + // "Entry[]" implementation + int nImpl = m_nImpl; + Map.Entry[] aEntry = (Map.Entry[]) m_oContents; + int c = nImpl - I_ARRAY_1 + 1; + int i = indexOf(aEntry, c, key); + if (i >= 0) + { + Map.Entry entry = aEntry[i]; + V prevValue = entry.getValue(); + entry.setValue(value); + return prevValue; + } + + // check if adding the object exceeds the "lite" threshold + if (c >= THRESHOLD) + { + // time to switch to a different map implementation + Map map = instantiateMap(); + for (i = 0; i < c; ++i) + { + Map.Entry entry = aEntry[i]; + map.put(entry.getKey(), entry.getValue()); + } + map.put(key, value); + + m_nImpl = I_OTHER; + m_oContents = map; + } + else + { + // use the next available element in the array + aEntry[c] = instantiateEntry(key, value); + m_nImpl = (byte) (nImpl + 1); + } + + return null; + } + + case I_OTHER: + return ((Map) m_oContents).put(key, value); + + default: + throw new IllegalStateException(); + } + } + + /** + * Removes the mapping for this key from this map if present. + * Expensive: updates both the underlying cache and the local cache. + * + * @param oKey key whose mapping is to be removed from the map + * + * @return previous value associated with specified key, or null + * if there was no mapping for key. A null return can + * also indicate that the map previously associated null + * with the specified key, if the implementation supports + * null values. + */ + public V remove(Object oKey) + { + switch (m_nImpl) + { + case I_EMPTY: + return null; + + case I_SINGLE: + { + Map.Entry entry = (Map.Entry) m_oContents; + K entryKey = entry.getKey(); + V prevValue = null; + if (Objects.equals(oKey, entryKey)) + { + prevValue = entry.getValue(); + m_nImpl = I_EMPTY; + m_oContents = null; + } + return prevValue; + } + + case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: + case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: + { + // "Entry[]" implementation + int nImpl = m_nImpl; + Map.Entry[] aEntry = (Map.Entry[]) m_oContents; + int c = nImpl - I_ARRAY_1 + 1; + int i = indexOf(aEntry, c, oKey); + if (i < 0) + { + return null; + } + + V prevValue = aEntry[i].getValue(); + if (c == 1) + { + m_nImpl = I_EMPTY; + m_oContents = null; + } + else + { + System.arraycopy(aEntry, i + 1, aEntry, i, c - i - 1); + aEntry[c-1] = null; + m_nImpl = (byte) --nImpl; + } + return prevValue; + } + + case I_OTHER: + { + Map map = (Map) m_oContents; + V prevValue = map.remove(oKey); + checkShrinkFromOther(); + return prevValue; + } + + default: + throw new IllegalStateException(); + } + } + + /** + * Clear all key/value mappings. + */ + public void clear() + { + m_nImpl = I_EMPTY; + m_oContents = null; + } + + + // ----- Cloneable interface -------------------------------------------- + + /** + * Create a clone of the ImmutableArrayList. + * + * @return a clone of this list + */ + public Object clone() + { + InflatableMap that; + try + { + that = (InflatableMap) super.clone(); + } + catch (CloneNotSupportedException e) + { + throw ensureRuntimeException(e); + } + + switch (this.m_nImpl) + { + case I_EMPTY: + // nothing to do + break; + + case I_SINGLE: + { + Map.Entry entry = (Map.Entry) m_oContents; + that.m_oContents = that.instantiateEntry(entry.getKey(), entry.getValue()); + } + break; + + case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: + case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: + { + Map.Entry[] aEntryThis = (Map.Entry[]) this.m_oContents; + Map.Entry[] aEntryThat = new Map.Entry[THRESHOLD]; + for (int i = 0, c = m_nImpl - I_ARRAY_1 + 1; i < c; ++i) + { + Map.Entry entryThis = aEntryThis[i]; + aEntryThat[i] = that.instantiateEntry( + entryThis.getKey(), entryThis.getValue()); + } + that.m_oContents = aEntryThat; + } + break; + + case I_OTHER: + Map mapThis = (Map) this.m_oContents; + Map mapThat = that.instantiateMap(); + mapThat.putAll(mapThis); + that.m_oContents = mapThat; + break; + + default: + throw new IllegalStateException(); + } + + return that; + } + + + // ----- inner class: EntrySet ------------------------------------------ + + /** + * Returns a set view of the mappings contained in this map. Each element + * in the returned set is an {@link java.util.Map.Entry Map Entry}. The set is backed by the + * map, so changes to the map are reflected in the set, and vice-versa. + * If the map is modified while an iteration over the set is in progress + * (except by the iterator's own remove operation, or by the + * setValue operation on a map entry returned by the iterator) + * the results of the iteration are undefined. The set supports element + * removal, which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, removeAll, + * retainAll and clear operations. It is not expected to + * support the add or addAll operations. + * + * @return a set view of the mappings contained in this map + */ + public Set> entrySet() + { + return instantiateEntrySet(); + } + + /** + * A Set of entries backed by this Map. + */ + protected class EntrySet + extends AbstractSet> + implements Serializable + { + /** + * Returns an iterator over the elements contained in this collection. + * + * @return an iterator over the elements contained in this collection + */ + public Iterator> iterator() + { + InflatableMap map = InflatableMap.this; + int c = map.size(); + return c == 0 + ? NULL_ITERATOR + : new EntryIterator<>(map, (Map.Entry[]) toArray(new Map.Entry[c])); + } + + /** + * Returns true if this Set is empty. + * + * @return true if this Set is empty + */ + public boolean isEmpty() + { + return InflatableMap.this.isEmpty(); + } + + /** + * Returns the number of elements in this collection. + * + * @return the number of elements in this collection + */ + public int size() + { + return InflatableMap.this.size(); + } + + /** + * Returns true if this collection contains the specified + * element. More formally, returns true if and only if this + * collection contains at least one element e such that + * (o==null ? e==null : o.equals(e)).

+ * + * @param o object to be checked for containment in this collection + * + * @return true if this collection contains the specified + * element + */ + public boolean contains(Object o) + { + if (o instanceof Map.Entry) + { + Map.Entry entry = (Map.Entry) o; + Object oKey = entry.getKey(); + Object oValue = entry.getValue(); + InflatableMap map = InflatableMap.this; + Object oActual = map.get(oKey); + return oActual == null + ? oValue == null && map.containsKey(oKey) + : Objects.equals(oValue, oActual); + } + + return false; + } + + /** + * Returns an array containing all of the elements in this collection. + * + * @return an array containing all of the elements in this collection + */ + public Object[] toArray() + { + return toArray((Object[]) null); + } + + /** + * Returns an array with a runtime type is that of the specified array + * and that contains all of the elements in this collection. If the + * collection fits in the specified array, it is returned therein. + * Otherwise, a new array is allocated with the runtime type of the + * specified array and the size of this collection. + *

+ * If the collection fits in the specified array with room to spare + * (i.e. the array has more elements than the collection), the element + * in the array immediately following the end of the collection is set + * to null. This is useful in determining the length of the + * collection only if the caller knows that the collection does + * not contain any null elements.) + * + * @param ao the array into which the elements of the collection are + * to be stored, if it is big enough; otherwise, a new + * array of the same runtime type is allocated for this + * purpose + * + * @return an array containing the elements of the collection + * + * @throws ArrayStoreException if the runtime type of the specified + * array is not a supertype of the runtime type of every + * element in this collection + */ + public Object[] toArray(Object ao[]) + { + InflatableMap map = InflatableMap.this; + + // create the array to store the map contents + int c = map.size(); + if (ao == null) + { + ao = c == 0 ? NO_OBJECTS : new Object[c]; + } + else if (ao.length < c) + { + // if it is not big enough, a new array of the same runtime + // type is allocated + ao = (Object[]) Array.newInstance(ao.getClass().getComponentType(), c); + } + else if (ao.length > c) + { + // if the collection fits in the specified array with room to + // spare, the element in the array immediately following the + // end of the collection is set to null + ao[c] = null; + } + + switch (map.m_nImpl) + { + case I_EMPTY: + break; + + case I_SINGLE: + ao[0] = map.m_oContents; + break; + + case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: + case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: + System.arraycopy((Map.Entry[]) m_oContents, 0, ao, 0, c); + break; + + case I_OTHER: + ao = ((Map) m_oContents).entrySet().toArray(ao); + break; + + default: + throw new IllegalStateException(); + } + + return ao; + } + } + + + // ----- Externalizable interface --------------------------------------- + + /** + * Initialize this object from the data in the passed ObjectInput stream. + * + * @param in the stream to read data from in order to restore the object + * + * @exception IOException if an I/O exception occurs + */ + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException + { + if (!isEmpty()) + { + throw new NotActiveException(); + } + + int c = in.readInt(); + switch (c) + { + case 0: + break; + + case 1: + m_nImpl = I_SINGLE; + m_oContents = instantiateEntry((K) in.readObject(), (V) in.readObject()); + break; + + case 2: case 3: case 4: case 5: case 6: case 7: case 8: + { + Map.Entry[] aEntry = new Map.Entry[THRESHOLD]; + for (int i = 0; i < c; ++i) + { + aEntry[i] = instantiateEntry((K) in.readObject(), (V) in.readObject()); + } + m_nImpl = (byte) (I_ARRAY_1 + c - 1); + m_oContents = aEntry; + } + break; + + default: + { + Map map = instantiateMap(); + for (int i = 0; i < c; ++i) + { + map.put((K) in.readObject(), (V) in.readObject()); + } + m_nImpl = I_OTHER; + m_oContents = map; + } + break; + } + } + + /** + * Write this object's data to the passed ObjectOutput stream. + * + * @param out the stream to write the object to + * + * @exception IOException if an I/O exception occurs + */ + public synchronized void writeExternal(ObjectOutput out) + throws IOException + { + // format is int size followed by that many key/value pairs + int nImpl = m_nImpl; + switch (nImpl) + { + case I_EMPTY: + out.writeInt(0); + break; + + case I_SINGLE: + { + Map.Entry entry = (Map.Entry) m_oContents; + out.writeInt(1); + out.writeObject(entry.getKey()); + out.writeObject(entry.getValue()); + } + break; + + case I_ARRAY_1: case I_ARRAY_2: case I_ARRAY_3: case I_ARRAY_4: + case I_ARRAY_5: case I_ARRAY_6: case I_ARRAY_7: case I_ARRAY_8: + { + Map.Entry[] aEntry = (Map.Entry[]) m_oContents; + int c = nImpl - I_ARRAY_1 + 1; + out.writeInt(c); + for (int i = 0; i < c; ++i) + { + Map.Entry entry = aEntry[i]; + out.writeObject(entry.getKey()); + out.writeObject(entry.getValue()); + } + } + break; + + case I_OTHER: + { + Map map = ((Map) m_oContents); + int c = map.size(); + Map.Entry[] aEntry = (Map.Entry[]) map.entrySet().toArray(new Map.Entry[c]); + out.writeInt(c); + for (int i = 0; i < c; ++i) + { + Map.Entry entry = aEntry[i]; + out.writeObject(entry.getKey()); + out.writeObject(entry.getValue()); + } + } + break; + + default: + throw new IllegalStateException(); + } + } + + // ----- internal methods ----------------------------------------------- + + /** + * (Factory pattern) Instantiate a Map Entry. + * This method permits inheriting classes to easily override the + * implementation of the Entry object. + * + * @param key the key + * @param value the value + * + * @return an instance of a Map Entry + */ + protected Map.Entry instantiateEntry(K key, V value) + { + return new SimpleEntry(key, value); + } + + /** + * (Factory pattern) Instantiate an Entry Set. + * This method permits inheriting classes to easily override the + * implementation of the EntrySet object. + * + * @return an instance of Entry Set + */ + protected Set> instantiateEntrySet() + { + return new EntrySet(); + } + + /** + * (Factory pattern) Instantiate a Map object to store entries in once + * the "lite" threshold has been exceeded. This method permits inheriting + * classes to easily override the choice of the Map object. + * + * @return an instance of Map + */ + protected Map instantiateMap() + { + return new HashMap<>(); + } + + /** + * Scan up to the first c elements of the passed Entry array + * aEntry looking for the specified key key. If it is + * found, return its position i in the array such that + * (0 <= i < c). If it is not found, return -1. + * + * @param aEntry the array of objects to search + * @param c the number of Entry objects in the array to search + * @param oKey the key to look for + * + * @return the index of the object, if found; otherwise -1 + */ + private int indexOf(Map.Entry[] aEntry, int c, Object oKey) + { + // first quick-scan by reference + for (int i = 0; i < c; ++i) + { + if (oKey == aEntry[i].getKey()) + { + return i; + } + } + + // slow scan by equals() + if (oKey != null) + { + for (int i = 0; i < c; ++i) + { + if (oKey.equals(aEntry[i].getKey())) + { + return i; + } + } + } + + return -1; + } + + /** + * Return the specified exception as a RuntimeException, wrapping it if necessary. + * + * @param t the exception + * + * @return the RuntimeException + */ + protected RuntimeException ensureRuntimeException(Throwable t) + { + return t instanceof RuntimeException + ? (RuntimeException) t + : new RuntimeException(t); + } + + /** + * After a mutation operation has reduced the size of an underlying Map, + * check if the delegation model should be replaced with a more size- + * efficient storage approach, and switch accordingly. + */ + protected void checkShrinkFromOther() + { + assert m_nImpl == I_OTHER; + + // check if the Map is now significantly below the "lite" + // threshold + Map map = (Map) m_oContents; + int c = map.size(); + switch (c) + { + case 0: + m_nImpl = I_EMPTY; + m_oContents = null; + break; + + case 1: + { + Map.Entry entry = (Map.Entry) map.entrySet().toArray()[0]; + m_nImpl = I_SINGLE; + m_oContents = instantiateEntry(entry.getKey(), entry.getValue()); + } + break; + + case 2: case 3: case 4: + { + // shrink to "Entry[]" implementation + Map.Entry[] aEntry = new Map.Entry[THRESHOLD]; + int i = 0; + for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) + { + Map.Entry entry = (Map.Entry) iter.next(); + aEntry[i++] = instantiateEntry(entry.getKey(), entry.getValue()); + } + assert i == c; + + m_nImpl = (byte) (I_ARRAY_1 + i - 1); + m_oContents = aEntry; + } + break; + } + } + + + // ----- inner class: EntryIterator ------------------------------------- + + /** + * A simple Iterator for InflatableMap Entry objects. This class is static in + * order to allow the EntrySet to be quickly garbage-collected. + */ + public static class EntryIterator + implements Iterator> + { + // ----- constructors ------------------------------------------- + + /** + * Construct an EntryIterator. + * + * @param map the InflatableMap to delegate remove() calls to + * @param aEntry the array of Map Entry objects to iterate + */ + public EntryIterator(InflatableMap map, Map.Entry[] aEntry) + { + m_map = map; + m_aEntry = aEntry; + } + + // ----- Iterator interface ------------------------------------- + + /** + * Returns true if the iteration has more elements. + * (In other words, returns true if next + * would return an element rather than throwing an exception.) + * + * @return true if the iterator has more elements + */ + public boolean hasNext() + { + return m_iPrev + 1 < m_aEntry.length; + } + + /** + * Returns the next element in the iteration. + * + * @return the next element in the iteration + * + * @exception NoSuchElementException iteration has no more + * elements + */ + public Entry next() + { + int iNext = m_iPrev + 1; + if (iNext < m_aEntry.length) + { + m_iPrev = iNext; + m_fCanRemove = true; + return m_aEntry[iNext]; + } + else + { + throw new NoSuchElementException(); + } + } + + /** + * Removes from the underlying set the last element + * returned by the iterator. This method can be called only once + * per call to next. The behavior of an iterator is + * unspecified if the underlying set is modified while the + * iteration is in progress in any way other than by calling this + * method. + * + * @exception IllegalStateException if the next method + * has not yet been called, or the remove + * method has already been called after the last call + * to the next method + */ + public void remove() + { + if (m_fCanRemove) + { + m_fCanRemove = false; + m_map.remove(m_aEntry[m_iPrev].getKey()); + } + else + { + throw new IllegalStateException(); + } + } + + // ----- data members ------------------------------------------- + + /** + * The InflatableMap. Having this field allows the EntrySet to be + * quickly collected by a GC, because the EntryIterator does not + * have to maintain a reference to it in order to get to the InflatableMap. + */ + Map m_map; + + /** + * The entries to iterate. + */ + Map.Entry[] m_aEntry; + + /** + * The previous index iterated. + */ + int m_iPrev = -1; + + /** + * True if the previously iterated element can be removed. + */ + boolean m_fCanRemove = false; + } + + + // ----- constants ------------------------------------------------------ + + /** + * A constant array of zero size. (This saves having to allocate what + * should be a constant.) + */ + private static final Object[] NO_OBJECTS = new Object[0]; + + /** + * The default point above which the InflatableMap delegates to another Map + * implementation. + */ + protected static final int THRESHOLD = 8; + + /** + * Implementation: Empty Map. + */ + private static final int I_EMPTY = 0; + /** + * Implementation: Single-item Map. + */ + private static final int I_SINGLE = 1; + /** + * Implementation: Array Map of 1 item. + */ + private static final int I_ARRAY_1 = 2; + /** + * Implementation: Array Map of 2 items. + */ + private static final int I_ARRAY_2 = 3; + /** + * Implementation: Array Map of 3 items. + */ + private static final int I_ARRAY_3 = 4; + /** + * Implementation: Array Map of 4 items. + */ + private static final int I_ARRAY_4 = 5; + /** + * Implementation: Array Map of 5 items. + */ + private static final int I_ARRAY_5 = 6; + /** + * Implementation: Array Map of 6 items. + */ + private static final int I_ARRAY_6 = 7; + /** + * Implementation: Array Map of 7 items. + */ + private static final int I_ARRAY_7 = 8; + /** + * Implementation: Array Map of 8 items. + */ + private static final int I_ARRAY_8 = 9; + /** + * Implementation: Delegation. + */ + protected static final int I_OTHER = 10; + + /** + * An empty iterator. + */ + private static final Iterator NULL_ITERATOR = new Iterator() + { + @Override + public boolean hasNext() + { + return false; + } + + @Override + public Object next() + { + throw new NoSuchElementException(); + } + }; + + // ----- data members --------------------------------------------------- + + /** + * Implementation, one of I_EMPTY, I_SINGLE, I_ARRAY_* or I_OTHER. + */ + protected byte m_nImpl; + + /** + * The Map contents, based on the implementation being used. + */ + protected Object m_oContents; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/PredicateIterator.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/PredicateIterator.java new file mode 100644 index 0000000000000..ad16a152a3f1c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/PredicateIterator.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + + +import com.oracle.coherence.common.base.Predicate; + +import java.util.Iterator; +import java.util.NoSuchElementException; + + +/** + * A generic implementation of an Iterator which iterates items based on an + * inclusion test. + * + * @param the type of iterated items + * + * @author cp 1997.09.05 + */ +public class PredicateIterator + implements Iterator + { + /** + * Construct a {@link PredicateIterator} based on the specified Iterator + * and the Predicate. + * + * @param iter the Iterator of objects to filter + * @param test the inclusion test + */ + public PredicateIterator(Iterator iter, Predicate test) + { + m_iter = iter; + m_test = test; + m_fNext = false; + } + + + // ----- Iterator interface --------------------------------------------- + + /** + * {@inheritDoc} + */ + public boolean hasNext() + { + // check if we've already check for the "next one" + boolean fNext = m_fNext; + if (fNext) + { + return true; + } + + // find if there is a "next one" + Iterator iter = m_iter; + Predicate test = m_test; + + while (iter.hasNext()) + { + T next = iter.next(); + if (test.evaluate(next)) + { + m_next = next; + fNext = true; + break; + } + } + + // can't call remove now (because we'd end up potentially + // removing the wrong one + m_fPrev = false; + m_fNext = fNext; + + return fNext; + } + + /** + * {@inheritDoc} + */ + public T next() + { + if (hasNext()) + { + m_fNext = false; + m_fPrev = true; + return m_next; + } + else + { + throw new NoSuchElementException(); + } + } + + /** + * {@inheritDoc} + */ + public void remove() + { + if (m_fPrev) + { + m_fPrev = false; + m_iter.remove(); + } + else + { + throw new IllegalStateException(); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * Iterator to filter. + */ + protected Iterator m_iter; + + /** + * Test to perform on each item. + */ + protected Predicate m_test; + + /** + * Is there a next item which passed the test? + */ + protected boolean m_fNext; + + /** + * Is there a previous item which passed the test and can be removed? + */ + protected boolean m_fPrev; + + /** + * The next item which passed the test. + */ + protected T m_next; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/SingleConsumerBlockingQueue.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/SingleConsumerBlockingQueue.java new file mode 100644 index 0000000000000..5521364801327 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/SingleConsumerBlockingQueue.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + +import com.oracle.coherence.common.base.Collector; +import com.oracle.coherence.common.base.Notifier; +import com.oracle.coherence.common.base.SingleWaiterCooperativeNotifier; + +import java.util.Collection; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + + +/** + * SingleConsumerBlockingQueue is a BlockingQueue implementation which supports multiple producers but only a single + * blocking consumer. The restriction of only allowing a single blocking consumer allows for potentially significant + * performance optimizations. This queue does not support null elements. + * + * @param the element type + * + * @author mf 2014.01.09 + */ +public class SingleConsumerBlockingQueue + extends ConcurrentLinkedQueue + implements BlockingQueue + { + // ----- SingleConsumerBlockingQueue interface -------------------------- + + /** + * Return a Collector accessor for this BlockingQueue. This allows for + * insertion without notification. When using this style insertion the + * caller must ultimately {@link Collector#flush flush} the collector to + * ensure notification. + * + * @return the collector + */ + public Collector getCollector() + { + return f_collector; + } + + /** + * Return a cooperative Collector accessor for this BlockingQueue. This allows + * for insertion without notification. When using this style insertion the + * caller must ultimately {@link Collector#flush flush} the queue via this + * collector or via an explicit call to {@link SingleWaiterCooperativeNotifier#flush} + * to ensure notification. The primary benefit of using the cooperative collector + * approach versus the {@link #getCollector()} is the cooperative collector flushing + * can be done by a single call to the static {@link SingleWaiterCooperativeNotifier#flush} + * method. + * + * @return the collector + */ + public Collector getCooperativeCollector() + { + return f_collectorCooperative; + } + + // ----- BlockingQueue interface ---------------------------------------- + + @Override + public boolean add(V value) + { + if (value == null) + { + throw new NullPointerException(); + } + + super.add(value); + f_collector.flush(); + return true; + } + + @Override + public boolean offer(V value) + { + if (value == null) + { + throw new NullPointerException(); + } + + super.offer(value); + f_collector.flush(); + return true; + } + + @Override + public void put(V value) + throws InterruptedException + { + add(value); + } + + @Override + public boolean offer(V e, long timeout, TimeUnit unit) + throws InterruptedException + { + return add(e); // since we don't have a size constraint add cannot fail so we can ignore timeout + } + + @Override + public V take() + throws InterruptedException + { + V value; + while ((value = poll()) == null) + { + f_notifier.await(); + } + return value; + } + + @Override + public V poll(long timeout, TimeUnit unit) + throws InterruptedException + { + V e = poll(); + if (e == null) + { + f_notifier.await(unit.toMillis(timeout)); + e = poll(); + } + return e; + } + + @Override + public int remainingCapacity() + { + return Integer.MAX_VALUE; + } + + @Override + public int drainTo(Collection c) + { + if (c == this) + { + throw new IllegalArgumentException(); + } + + int cV = 0; + for (V value; (value = poll()) != null; ++cV) + { + c.add(value); + } + + return cV; + } + + @Override + public int drainTo(Collection c, int maxElements) + { + if (c == this) + { + throw new IllegalArgumentException(); + } + + int cV = 0; + for (V value; cV < maxElements && (value = poll()) != null; ++cV) + { + c.add(value); + } + + return cV; + } + + @Override + public boolean addAll(Collection c) + { + if (c.contains(null)) + { + throw new NullPointerException(); + } + + super.addAll(c); + f_collector.flush(); + return false; + } + + // ----- data members --------------------------------------------------- + + /** + * The notifier used to signal waiting threads when entries are added. + */ + protected final Notifier f_notifier = new SingleWaiterCooperativeNotifier(); + + /** + * The internal Collector for this Queue, allowing for add without signal. + */ + protected final Collector f_collector = new Collector() + { + @Override + public void add(V value) + { + SingleConsumerBlockingQueue.super.offer(value); + } + + @Override + public void flush() + { + f_notifier.signal(); + SingleWaiterCooperativeNotifier.flush(); + } + }; + + /** + * The internal Cooperative Collector for this Queue, allowing for external flushing via the SWCN. + */ + protected final Collector f_collectorCooperative = new Collector() + { + @Override + public void add(V value) + { + SingleConsumerBlockingQueue.super.offer(value); + f_notifier.signal(); + } + + @Override + public void flush() + { + // Note: as per the contract of getCooperativeCollector, this method cannot contain more + // then a call to SWCN.flush(). More specifically a call to SWCN.flush() needs to be + // sufficient to be called in place of this method. + SingleWaiterCooperativeNotifier.flush(); + } + }; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/Stack.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/Stack.java new file mode 100644 index 0000000000000..f8c195e44d9fe --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/Stack.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + +/** + * Stack describes a classic LIFO collection. + * + * @param the type of the values that will be stored in the Stack + * + * @author mf 2012.03.22 + */ +public interface Stack + { + /** + * Push an element onto the stack. + * + * @param element the element + */ + public void push(V element); + + /** + * Pop an element off of the stack. + * + * @return the element, or null if empty + */ + public V pop(); + + /** + * Return but don't remove the top element of the stack. + * + * @return the element, or null if empty + */ + public V peek(); + + /** + * Return true iff stack is empty. + * + * @return true iff stack is empty + */ + public boolean isEmpty(); + + /** + * Return the number of elements in the stack. + * + * @return the number of elements in the stack + */ + public int size(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/UnmodifiableSetCollection.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/UnmodifiableSetCollection.java new file mode 100644 index 0000000000000..4f6edb7ecf128 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/UnmodifiableSetCollection.java @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +/** + * UnmodifiableSetCollection is a wrapper set that provides a read-only view of + * the underlying Sets. The underlying sets are assumed to be disjoint. + * + * @author bb 2011.06.30 + */ +public class UnmodifiableSetCollection + implements Set + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a UnmodifiableSetCollection + * + * @param sets Array of sets to wrap + */ + public UnmodifiableSetCollection(Set... sets) + { + m_aSet = sets; + } + + // ----- Collection interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int size() + { + int i = 0; + for (Set set : m_aSet) + { + i += set.size(); + } + return i; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() + { + for (Set set : m_aSet) + { + if (!set.isEmpty()) + { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean contains(Object o) + { + for (Set set : m_aSet) + { + if (set.contains(o)) + { + return true; + } + } + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator iterator() + { + Iterator[] aItr = new Iterator[m_aSet.length]; + int i = 0; + for (Set set : m_aSet) + { + aItr[i++] = set.iterator(); + } + return new ChainedIterator(aItr); + } + + /** + * {@inheritDoc} + */ + @Override + public Object[] toArray() + { + ArrayList list = new ArrayList(this.size()); + for (Set set : m_aSet) + { + list.addAll(set); + } + return list.toArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public T[] toArray(T[] a) + { + ArrayList list = new ArrayList(this.size()); + for (Set set : m_aSet) + { + list.addAll(set); + } + return (T[]) list.toArray(a); + } + + // ----- Set interface -------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean add(E e) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean remove(Object o) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsAll(Collection c) + { + for (Object obj : c) + { + if (!contains(obj)) + { + return false; + } + } + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean addAll(Collection c) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean retainAll(Collection c) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean removeAll(Collection c) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public void clear() + { + throw new UnsupportedOperationException(); + } + + // ----- Object methods ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) + { + if (o == this) + { + return true; + } + + if (o instanceof Set) + { + Set set = (Set) o; + return set.size() == this.size() && this.containsAll(set); + } + return false; + } + + /** + * {@inheritDoc} + */ + public int hashCode() + { + int nHash = 0; + for (Set set : m_aSet) + { + nHash += set.hashCode(); + } + return nHash; + } + + // ----- data members ------------------------------------------- + + /** + * Wrapped sets + */ + protected Set[] m_aSet; + + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/WeakIdentityHashMap.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/WeakIdentityHashMap.java new file mode 100644 index 0000000000000..997b5cd437d5b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/WeakIdentityHashMap.java @@ -0,0 +1,917 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.collections; + +// Note: the following is a copy-and-paste of 1.7 java.util.WeakHashMap, modified only to change it to be identity based. +// this implementation has also been stripped of any Java internal class usage + +import java.util.*; +import java.lang.ref.WeakReference; +import java.lang.ref.ReferenceQueue; + + +/** + * Equivalent of {@link java.util.WeakHashMap} but where key equality is based solely on key identity. + */ +public class WeakIdentityHashMap + extends AbstractMap + implements Map { + + /** + * The default initial capacity -- MUST be a power of two. + */ + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<30. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The load factor used when none specified in constructor. + */ + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The table, resized as necessary. Length MUST Always be a power of two. + */ + Entry[] table; + + /** + * The number of key-value mappings contained in this weak hash map. + */ + private int size; + + /** + * The next size value at which to resize (capacity * load factor). + */ + private int threshold; + + /** + * The load factor for the hash table. + */ + private final float loadFactor; + + /** + * Reference queue for cleared WeakEntries + */ + private final ReferenceQueue queue = new ReferenceQueue<>(); + + /** + * The number of times this WeakIdentityHashMap has been structurally modified. + * Structural modifications are those that change the number of + * mappings in the map or otherwise modify its internal structure + * (e.g., rehash). This field is used to make iterators on + * Collection-views of the map fail-fast. + * + * @see ConcurrentModificationException + */ + int modCount; + + /** + * The default threshold of map capacity above which alternative hashing is + * used for String keys. Alternative hashing reduces the incidence of + * collisions due to weak hash code calculation for String keys. + *

+ * This value may be overridden by defining the system property + * {@code jdk.map.althashing.threshold}. A property value of {@code 1} + * forces alternative hashing to be used at all times whereas + * {@code -1} value ensures that alternative hashing is never used. + */ + static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE; + + /** + * holds values which can't be initialized until after VM is booted. + */ + private static class Holder { + + /** + * Table capacity above which to switch to use alternative hashing. + */ + static final int ALTERNATIVE_HASHING_THRESHOLD = ALTERNATIVE_HASHING_THRESHOLD_DEFAULT; + } + + /** + * If {@code true} then perform alternate hashing to reduce the incidence of + * collisions due to weak hash code calculation. + */ + transient boolean useAltHashing; + + @SuppressWarnings("unchecked") + private Entry[] newTable(int n) { + return (Entry[]) new Entry[n]; + } + + /** + * Constructs a new, empty WeakIdentityHashMap with the given initial + * capacity and the given load factor. + * + * @param initialCapacity The initial capacity of the WeakIdentityHashMap + * @param loadFactor The load factor of the WeakIdentityHashMap + * @throws IllegalArgumentException if the initial capacity is negative, + * or if the load factor is nonpositive. + */ + public WeakIdentityHashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal Initial Capacity: "+ + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal Load factor: "+ + loadFactor); + int capacity = 1; + while (capacity < initialCapacity) + capacity <<= 1; + table = newTable(capacity); + this.loadFactor = loadFactor; + threshold = (int)(capacity * loadFactor); + useAltHashing = capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD; + } + + /** + * Constructs a new, empty WeakIdentityHashMap with the given initial + * capacity and the default load factor (0.75). + * + * @param initialCapacity The initial capacity of the WeakIdentityHashMap + * @throws IllegalArgumentException if the initial capacity is negative + */ + public WeakIdentityHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs a new, empty WeakIdentityHashMap with the default initial + * capacity (16) and load factor (0.75). + */ + public WeakIdentityHashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs a new WeakIdentityHashMap with the same mappings as the + * specified map. The WeakIdentityHashMap is created with the default + * load factor (0.75) and an initial capacity sufficient to hold the + * mappings in the specified map. + * + * @param m the map whose mappings are to be placed in this map + * @throws NullPointerException if the specified map is null + * @since 1.3 + */ + public WeakIdentityHashMap(Map m) { + this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, + DEFAULT_INITIAL_CAPACITY), + DEFAULT_LOAD_FACTOR); + putAll(m); + } + + // internal utilities + + /** + * Value representing null keys inside tables. + */ + private static final Object NULL_KEY = new Object(); + + /** + * Use NULL_KEY for key if it is null. + */ + private static Object maskNull(Object key) { + return (key == null) ? NULL_KEY : key; + } + + /** + * Returns internal representation of null key back to caller as null. + */ + static Object unmaskNull(Object key) { + return (key == NULL_KEY) ? null : key; + } + + /** + * Checks for equality of non-null reference x and possibly-null y. By + * default uses Object.equals. + */ + private static boolean eq(Object x, Object y) { + return x == y; + } + + /** + * Retrieve object hash code and applies a supplemental hash function to the + * result hash, which defends against poor quality hash functions. This is + * critical because HashMap uses power-of-two length hash tables, that + * otherwise encounter collisions for hashCodes that do not differ + * in lower bits. + */ + static int hash(Object k) { + int h = System.identityHashCode(k); + + // This function ensures that hashCodes that differ only by + // constant multiples at each bit position have a bounded + // number of collisions (approximately 8 at default load factor). + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); + } + + /** + * Returns index for hash code h. + */ + private static int indexFor(int h, int length) { + return h & (length-1); + } + + /** + * Expunges stale entries from the table. + */ + private void expungeStaleEntries() { + for (Object x; (x = queue.poll()) != null; ) { + synchronized (queue) { + @SuppressWarnings("unchecked") + Entry e = (Entry) x; + int i = indexFor(e.hash, table.length); + + Entry prev = table[i]; + Entry p = prev; + while (p != null) { + Entry next = p.next; + if (p == e) { + if (prev == e) + table[i] = next; + else + prev.next = next; + // Must not null out e.next; + // stale entries may be in use by a HashIterator + e.value = null; // Help GC + size--; + break; + } + prev = p; + p = next; + } + } + } + } + + /** + * Returns the table after first expunging stale entries. + */ + private Entry[] getTable() { + expungeStaleEntries(); + return table; + } + + /** + * Returns the number of key-value mappings in this map. + * This result is a snapshot, and may not reflect unprocessed + * entries that will be removed before next attempted access + * because they are no longer referenced. + */ + public int size() { + if (size == 0) + return 0; + expungeStaleEntries(); + return size; + } + + /** + * Returns true if this map contains no key-value mappings. + * This result is a snapshot, and may not reflect unprocessed + * entries that will be removed before next attempted access + * because they are no longer referenced. + */ + public boolean isEmpty() { + return size() == 0; + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key==k}, + * then this method returns {@code v}; otherwise + * it returns {@code null}. (There can be at most one such mapping.) + * + *

A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + * + * @see #put(Object, Object) + */ + public V get(Object key) { + Object k = maskNull(key); + int h = hash(k); + Entry[] tab = getTable(); + int index = indexFor(h, tab.length); + Entry e = tab[index]; + while (e != null) { + if (e.hash == h && eq(k, e.get())) + return e.value; + e = e.next; + } + return null; + } + + /** + * Returns true if this map contains a mapping for the + * specified key. + * + * @param key The key whose presence in this map is to be tested + * @return true if there is a mapping for key; + * false otherwise + */ + public boolean containsKey(Object key) { + return getEntry(key) != null; + } + + /** + * Returns the entry associated with the specified key in this map. + * Returns null if the map contains no mapping for this key. + */ + Entry getEntry(Object key) { + Object k = maskNull(key); + int h = hash(k); + Entry[] tab = getTable(); + int index = indexFor(h, tab.length); + Entry e = tab[index]; + while (e != null && !(e.hash == h && eq(k, e.get()))) + e = e.next; + return e; + } + + /** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for this key, the old + * value is replaced. + * + * @param key key with which the specified value is to be associated. + * @param value value to be associated with the specified key. + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ + public V put(K key, V value) { + Object k = maskNull(key); + int h = hash(k); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + + for (Entry e = tab[i]; e != null; e = e.next) { + if (h == e.hash && eq(k, e.get())) { + V oldValue = e.value; + if (value != oldValue) + e.value = value; + return oldValue; + } + } + + modCount++; + Entry e = tab[i]; + tab[i] = new Entry<>(k, value, queue, h, e); + if (++size >= threshold) + resize(tab.length * 2); + return null; + } + + /** + * Rehashes the contents of this map into a new array with a + * larger capacity. This method is called automatically when the + * number of keys in this map reaches its threshold. + * + * If current capacity is MAXIMUM_CAPACITY, this method does not + * resize the map, but sets threshold to Integer.MAX_VALUE. + * This has the effect of preventing future calls. + * + * @param newCapacity the new capacity, MUST be a power of two; + * must be greater than current capacity unless current + * capacity is MAXIMUM_CAPACITY (in which case value + * is irrelevant). + */ + void resize(int newCapacity) { + Entry[] oldTable = getTable(); + int oldCapacity = oldTable.length; + if (oldCapacity == MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return; + } + + Entry[] newTable = newTable(newCapacity); + boolean oldAltHashing = useAltHashing; + useAltHashing |= newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD; + boolean rehash = oldAltHashing ^ useAltHashing; + transfer(oldTable, newTable, rehash); + table = newTable; + + /* + * If ignoring null elements and processing ref queue caused massive + * shrinkage, then restore old table. This should be rare, but avoids + * unbounded expansion of garbage-filled tables. + */ + if (size >= threshold / 2) { + threshold = (int)(newCapacity * loadFactor); + } else { + expungeStaleEntries(); + transfer(newTable, oldTable, false); + table = oldTable; + } + } + + /** Transfers all entries from src to dest tables */ + private void transfer(Entry[] src, Entry[] dest, boolean rehash) { + for (int j = 0; j < src.length; ++j) { + Entry e = src[j]; + src[j] = null; + while (e != null) { + Entry next = e.next; + Object key = e.get(); + if (key == null) { + e.next = null; // Help GC + e.value = null; // " " + size--; + } else { + if (rehash) { + e.hash = hash(key); + } + int i = indexFor(e.hash, dest.length); + e.next = dest[i]; + dest[i] = e; + } + e = next; + } + } + } + + /** + * Copies all of the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for any + * of the keys currently in the specified map. + * + * @param m mappings to be stored in this map. + * @throws NullPointerException if the specified map is null. + */ + public void putAll(Map m) { + int numKeysToBeAdded = m.size(); + if (numKeysToBeAdded == 0) + return; + + /* + * Expand the map if the map if the number of mappings to be added + * is greater than or equal to threshold. This is conservative; the + * obvious condition is (m.size() + size) >= threshold, but this + * condition could result in a map with twice the appropriate capacity, + * if the keys to be added overlap with the keys already in this map. + * By using the conservative calculation, we subject ourself + * to at most one extra resize. + */ + if (numKeysToBeAdded > threshold) { + int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1); + if (targetCapacity > MAXIMUM_CAPACITY) + targetCapacity = MAXIMUM_CAPACITY; + int newCapacity = table.length; + while (newCapacity < targetCapacity) + newCapacity <<= 1; + if (newCapacity > table.length) + resize(newCapacity); + } + + for (Map.Entry e : m.entrySet()) + put(e.getKey(), e.getValue()); + } + + /** + * Removes the mapping for a key from this weak hash map if it is present. + * More formally, if this map contains a mapping from key k to + * value v such that (key==k), that mapping is removed. + * (The map can contain at most one such mapping.) + * + *

Returns the value to which this map previously associated the key, + * or null if the map contained no mapping for the key. A + * return value of null does not necessarily indicate + * that the map contained no mapping for the key; it's also possible + * that the map explicitly mapped the key to null. + * + *

The map will not contain a mapping for the specified key once the + * call returns. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with key, or + * null if there was no mapping for key + */ + public V remove(Object key) { + Object k = maskNull(key); + int h = hash(k); + Entry[] tab = getTable(); + int i = indexFor(h, tab.length); + Entry prev = tab[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + if (h == e.hash && eq(k, e.get())) { + modCount++; + size--; + if (prev == e) + tab[i] = next; + else + prev.next = next; + return e.value; + } + prev = e; + e = next; + } + + return null; + } + + /** Special version of remove needed by Entry set */ + boolean removeMapping(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Entry[] tab = getTable(); + Map.Entry entry = (Map.Entry)o; + Object k = maskNull(entry.getKey()); + int h = hash(k); + int i = indexFor(h, tab.length); + Entry prev = tab[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + if (h == e.hash && e.equals(entry)) { + modCount++; + size--; + if (prev == e) + tab[i] = next; + else + prev.next = next; + return true; + } + prev = e; + e = next; + } + + return false; + } + + /** + * Removes all of the mappings from this map. + * The map will be empty after this call returns. + */ + public void clear() { + // clear out ref queue. We don't need to expunge entries + // since table is getting cleared. + while (queue.poll() != null) + ; + + modCount++; + java.util.Arrays.fill(table, null); + size = 0; + + // Allocation of array may have caused GC, which may have caused + // additional entries to go stale. Removing these entries from the + // reference queue will make them eligible for reclamation. + while (queue.poll() != null) + ; + } + + /** + * Returns true if this map maps one or more keys to the + * specified value. + * + * @param value value whose presence in this map is to be tested + * @return true if this map maps one or more keys to the + * specified value + */ + public boolean containsValue(Object value) { + if (value==null) + return containsNullValue(); + + Entry[] tab = getTable(); + for (int i = tab.length; i-- > 0;) + for (Entry e = tab[i]; e != null; e = e.next) + if (value.equals(e.value)) + return true; + return false; + } + + /** + * Special-case code for containsValue with null argument + */ + private boolean containsNullValue() { + Entry[] tab = getTable(); + for (int i = tab.length; i-- > 0;) + for (Entry e = tab[i]; e != null; e = e.next) + if (e.value==null) + return true; + return false; + } + + /** + * The entries in this hash table extend WeakReference, using its main ref + * field as the key. + */ + private static class Entry extends WeakReference implements Map.Entry { + V value; + int hash; + Entry next; + + /** + * Creates new entry. + */ + Entry(Object key, V value, + ReferenceQueue queue, + int hash, Entry next) { + super(key, queue); + this.value = value; + this.hash = hash; + this.next = next; + } + + @SuppressWarnings("unchecked") + public K getKey() { + return (K) WeakIdentityHashMap.unmaskNull(get()); + } + + public V getValue() { + return value; + } + + public V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + K k1 = getKey(); + Object k2 = e.getKey(); + if (k1 == k2) { + V v1 = getValue(); + Object v2 = e.getValue(); + if (v1 == v2 || (v1 != null && v1.equals(v2))) + return true; + } + return false; + } + + public int hashCode() { + K k = getKey(); + V v = getValue(); + return ((k==null ? 0 : hash(k)) ^ + (v==null ? 0 : v.hashCode())); + } + + public String toString() { + return getKey() + "=" + getValue(); + } + } + + private abstract class HashIterator implements Iterator { + private int index; + private Entry entry = null; + private Entry lastReturned = null; + private int expectedModCount = modCount; + + /** + * Strong reference needed to avoid disappearance of key + * between hasNext and next + */ + private Object nextKey = null; + + /** + * Strong reference needed to avoid disappearance of key + * between nextEntry() and any use of the entry + */ + private Object currentKey = null; + + HashIterator() { + index = isEmpty() ? 0 : table.length; + } + + public boolean hasNext() { + Entry[] t = table; + + while (nextKey == null) { + Entry e = entry; + int i = index; + while (e == null && i > 0) + e = t[--i]; + entry = e; + index = i; + if (e == null) { + currentKey = null; + return false; + } + nextKey = e.get(); // hold on to key in strong ref + if (nextKey == null) + entry = entry.next; + } + return true; + } + + /** The common parts of next() across different types of iterators */ + protected Entry nextEntry() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + if (nextKey == null && !hasNext()) + throw new NoSuchElementException(); + + lastReturned = entry; + entry = entry.next; + currentKey = nextKey; + nextKey = null; + return lastReturned; + } + + public void remove() { + if (lastReturned == null) + throw new IllegalStateException(); + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + + WeakIdentityHashMap.this.remove(currentKey); + expectedModCount = modCount; + lastReturned = null; + currentKey = null; + } + + } + + private class ValueIterator extends HashIterator { + public V next() { + return nextEntry().value; + } + } + + private class KeyIterator extends HashIterator { + public K next() { + return nextEntry().getKey(); + } + } + + private class EntryIterator extends HashIterator> { + public Map.Entry next() { + return nextEntry(); + } + } + + // Views + + private transient Set> entrySet = null; + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation), the results of + * the iteration are undefined. The set supports element removal, + * which removes the corresponding mapping from the map, via the + * Iterator.remove, Set.remove, + * removeAll, retainAll, and clear + * operations. It does not support the add or addAll + * operations. + */ + public Set keySet() { + Set ks = keySet; + return (ks != null ? ks : (keySet = new KeySet())); + } + + protected transient Set keySet; + + private class KeySet extends AbstractSet { + public Iterator iterator() { + return new KeyIterator(); + } + + public int size() { + return WeakIdentityHashMap.this.size(); + } + + public boolean contains(Object o) { + return containsKey(o); + } + + public boolean remove(Object o) { + if (containsKey(o)) { + WeakIdentityHashMap.this.remove(o); + return true; + } + else + return false; + } + + public void clear() { + WeakIdentityHashMap.this.clear(); + } + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. If the map is + * modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), + * the results of the iteration are undefined. The collection + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Collection.remove, removeAll, + * retainAll and clear operations. It does not + * support the add or addAll operations. + */ + public Collection values() { + Collection vs = values; + return (vs != null) ? vs : (values = new Values()); + } + + protected transient Collection values; + + private class Values extends AbstractCollection { + public Iterator iterator() { + return new ValueIterator(); + } + + public int size() { + return WeakIdentityHashMap.this.size(); + } + + public boolean contains(Object o) { + return containsValue(o); + } + + public void clear() { + WeakIdentityHashMap.this.clear(); + } + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. If the map is modified + * while an iteration over the set is in progress (except through + * the iterator's own remove operation, or through the + * setValue operation on a map entry returned by the + * iterator) the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding + * mapping from the map, via the Iterator.remove, + * Set.remove, removeAll, retainAll and + * clear operations. It does not support the + * add or addAll operations. + */ + public Set> entrySet() { + Set> es = entrySet; + return es != null ? es : (entrySet = new EntrySet()); + } + + private class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new EntryIterator(); + } + + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + Entry candidate = getEntry(e.getKey()); + return candidate != null && candidate.equals(e); + } + + public boolean remove(Object o) { + return removeMapping(o); + } + + public int size() { + return WeakIdentityHashMap.this.size(); + } + + public void clear() { + WeakIdentityHashMap.this.clear(); + } + + private List> deepCopy() { + List> list = new ArrayList<>(size()); + for (Map.Entry e : this) + list.add(new AbstractMap.SimpleEntry<>(e)); + return list; + } + + public Object[] toArray() { + return deepCopy().toArray(); + } + + public T[] toArray(T[] a) { + return deepCopy().toArray(a); + } + } +} diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/package.html new file mode 100644 index 0000000000000..112d4535aa26e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/collections/package.html @@ -0,0 +1,5 @@ + +The collections package provides a number implementations and extensions to the standard java collection classes. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/Platform.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/Platform.java new file mode 100644 index 0000000000000..68315ca7495df --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/Platform.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal; + +import com.sun.management.OperatingSystemMXBean; + +import java.lang.management.ManagementFactory; + +import java.io.File; + + +/** + * Platform provides information about the environment in which the JVM executes. + * + * @author mf 2013.03.21 + */ +public class Platform + { + // ----- constructors --------------------------------------------------- + + /** + * Blocked constructor. + */ + private Platform() + { + boolean fExa = false; + + try + { + fExa = new File("/opt/exalogic").exists() || + new File("/opt/exalogic-java").exists(); + } + catch (SecurityException e) + { + try + { + fExa = Class.forName("com.oracle.exalogic.ExaManager") != null; // identify if we are on Exalogic; TODO: and SSC detection + } + catch (Exception e2) {} + } + + // the system property ultimately controls if we are exa or not, the presence of the ExaManager just sets the + // default. This allows us to enable exa on platforms which we don't detect as such, i.e. SSC, but also allows + // us to easily disable exa optimizations on exa platforms if desired + f_fExaEnabled = Boolean.parseBoolean(System.getProperty("com.oracle.exa", fExa ? "true" : "false")) || + Boolean.parseBoolean(System.getProperty("com.oracle.exalogic")) || + Boolean.parseBoolean(System.getProperty("com.oracle.exadata")) || + Boolean.parseBoolean(System.getProperty("com.oracle.ssc")); + + // TODO: would be nice to identify if the JVM has been pinned to a limited number of CPUs in which + // case the proper answer is just Runtime.availableProcessors() + int cProcFair = Math.max(1, (int) Math.ceil((double) Runtime.getRuntime().availableProcessors() * + Math.min(1.0, (double) Runtime.getRuntime().maxMemory() / (double) getTotalPhysicalMemorySize()))); + + f_cProcFair = Integer.parseInt(System.getProperty(Platform.class.getName() + ".fairShareProcessors", + String.valueOf(cProcFair))); + } + + /** + * Return the Platform singleton instance. + * + * @return the Platform singleton instance. + */ + public static Platform getPlatform() + { + return INSTANCE; + } + + /** + * Return true iff the platform is part of the Oracle exa family. + *

+ * This property can be set directly via the com.oracle.exa system property. + * + * @return true iff the platform is part of the Oracle exa family. + */ + public final boolean isExaEnabled() + { + return f_fExaEnabled; + } + + /** + * Return the total amount of physical memory present on the machine. + * + * @return the machine memory size in bytes, or Runtime.maxMemory() if the value cannot be determined + */ + public long getTotalPhysicalMemorySize() + { + try + { + // TODO: Starting with Java 1.7 this method also exists on java.lang.management.OperatingSystemMXBean + // as well as the legacy com.sun.management.OperatingSystemMXBean. When we drop 1.6 support, we can safely + // switch to the "public" version + return ((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getTotalPhysicalMemorySize(); + } + catch (Throwable e) + { + return Runtime.getRuntime().maxMemory(); + } + } + + /** + * Return a fair share of processors for this process based on other JVM sizing limitations. + * + * This value can be set directly via the com.oracle.coherence.common.internal.Platform.fairShareProcessors system property. + * + * @return this processes fair share of processors + */ + public int getFairShareProcessors() + { + return f_cProcFair; + } + + // ----- data members --------------------------------------------------- + + /** + * True iff this is an exa* platform. + */ + private final boolean f_fExaEnabled; + + /** + * This processes fair share processor count + */ + private final int f_cProcFair; + + /** + * Singleton Platform instance. + */ + private static Platform INSTANCE = new Platform(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/AbstractContinuationFrame.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/AbstractContinuationFrame.java new file mode 100644 index 0000000000000..57e35b4b33515 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/AbstractContinuationFrame.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.continuations; + +import com.oracle.coherence.common.base.Continuation; + + +/** + * AbstractContinuationFrame is a Runnable which uses continuations to provide standard stack-frame like control flows. + *

+ * The AbstractContinuationFrame takes a number of continuations which are invoked based on how the frame completes. + *

    + *
  • + * failure continuation - the continuation called upon failure, providing the exception + *
  • + *
  • + * finally continuation - the continuation called upon either normal or failure completion + *
  • + *
  • + * result continuation - the continuation called upon normal completion of the operation, providing the result + *
  • + *
+ * The AbstractContinuationFrame emulates the following standard call flow: + *
+ * R result;
+ * try
+ *     {
+ *     result = call();
+ *     }
+ * catch (Throwable e)
+ *     {
+ *     continuationFail.proceed(e);
+ *     return;
+ *     }
+ * finally
+ *     {
+ *     continuationFinally.proceed(null);
+ *     }
+ *
+ * continuation.proceed(result);
+ * 
+ * + * @param the result type + * + * @author mf 2013.07.02 + */ +public abstract class AbstractContinuationFrame + implements Runnable + { + /** + * Construct a AbstractContinuationFrame. + * + * @param continuationFinally the continuation to run upon either normal or failed completion, may be null + */ + public AbstractContinuationFrame(Continuation continuationFinally) + { + this (/*continuationFail*/ null, continuationFinally, /*continuation*/ null); + } + + /** + * Construct a AbstractContinuationFrame. + * + * @param continuationFail the continuation to invoke if the operation fails, may be null + * @param continuation the continuation to invoke when the operation completes normally, may be null + */ + public AbstractContinuationFrame(Continuation continuationFail, Continuation continuation) + { + this (continuationFail, /*continuationFinally*/ null, continuation); + } + + /** + * Construct an AbstractContinuationFrame which inherits another's continuations. + * + * @param that the frame to inherit continuations from + */ + public AbstractContinuationFrame(AbstractContinuationFrame that) + { + this(that.getFailureContinuation(), that.getContinuation()); + } + + /** + * Construct a AbstractContinuationFrame. + * + * @param continuationFail the continuation to invoke if the operation fails, may be null + * @param continuationFinally the continuation to run upon either normal or failed completion, may be null + * @param continuation the continuation to invoke when the operation completes, may be null + */ + public AbstractContinuationFrame( + Continuation continuationFail, + final Continuation continuationFinally, + Continuation continuation) + { + if (continuationFinally != null) + { + continuationFail = new WrapperContinuation(continuationFail) + { + @Override + public void proceed(Throwable e) + { + try + { + super.proceed(e); + } + finally + { + Continuations.proceed(continuationFinally, null); + } + } + }; + + continuation = new WrapperContinuation(continuation) + { + @Override + public void proceed(R result) + { + try + { + Continuations.proceed(continuationFinally, null); + } + finally + { + super.proceed(result); + } + } + }; + + } + + m_continuationFail = continuationFail; + m_continuation = continuation; + } + + @Override + public final void run() + { + try + { + R result = call(); + m_continuationFail = null; // continuationFail is not intended to handle other continuation exceptions + Continuations.proceed(m_continuation, result); + } + catch (Throwable e) + { + Continuations.proceed(m_continuationFail, e); + } + } + + /** + * Perform the operation. + *

+ * If control returns from this method without continueAsync() having been called, then the appropriate continuation(s) + * will be automatically invoked. + *

+ * + * @return return the operation result + * + * @throws Exception upon synchronous failure + */ + protected abstract R call() + throws Exception; + + /** + * Return the continuation to invoke with the result of the operation. + * + * @return the result continuation + */ + protected Continuation getContinuation() + { + return m_continuation; + } + + /** + * Return the continuation to invoke upon failure. + * + * @return the failure continuation + */ + protected Continuation getFailureContinuation() + { + return m_continuationFail; + } + + /** + * Invoking this method indicates that processing is continuing asynchronously, and thus any return value or + * exception should not automatically trigger the invocation of the continuations. More specifically invoking + * this method nulls out the continuations. + *

+ * As this method returns null, it allows for a simple pattern of having the final statement of {@link #call} + * to be return continueAsync(); when continuing asynchronously. + *

+ * + * @return null + */ + protected R continueAsync() + { + m_continuationFail = null; + m_continuation = null; + return null; // we must use null as type specific implementations of call() introduce a cast to the real type + } + + // ----- data members ---------------------------------------------------- + + /** + * The continuation (if any) to invoke upon failure. + */ + private Continuation m_continuationFail; + + /** + * The continuation (if any) to invoke with the invocation result. + */ + private Continuation m_continuation; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/Continuations.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/Continuations.java new file mode 100644 index 0000000000000..3d1a6afd28ab1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/Continuations.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.continuations; + +import com.oracle.coherence.common.base.Continuation; + + +/** + * Helper for working with Continuations. + * + * @author mf 2013.07.02 + */ +public final class Continuations + { + private Continuations(){} + + /** + * Invoke {Continuation#proceeed} on the supplied continuation. If the continuation is null this method is a + * no-op + * + * @param cont the continuation or null + * @param result the result + * @param the result type + */ + public static void proceed(Continuation cont, R result) + { + if (cont != null) + { + cont.proceed(result); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/SynchronousContinuationFrame.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/SynchronousContinuationFrame.java new file mode 100644 index 0000000000000..7d1bde266e276 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/SynchronousContinuationFrame.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.continuations; + +import com.oracle.coherence.common.internal.continuations.AbstractContinuationFrame; +import com.oracle.coherence.common.base.Continuation; + +import java.util.concurrent.Callable; + + +/** + * SynchronousContinuationFrame is an adapter which allows pre-existing synchronous Callables to support continuations. + * + * @author mf/pp 2013.07.09 + */ +public class SynchronousContinuationFrame + extends AbstractContinuationFrame + { + /** + * Construct a SynchronousContinuationFrame. + * + * @param callable the callable to invoke when the frame is run + * @param continuationFail the continuation to invoke if the operation fails, may be null + * @param continuation the continuation to invoke when the operation completes, may be null + */ + public SynchronousContinuationFrame(Callable callable, + Continuation continuationFail, + Continuation continuation) + { + this (callable, continuationFail, /*continuationFinally*/ null, continuation); + } + + /** + * Construct a SynchronousContinuationFrame. + * + * @param callable the callable to invoke when the frame is run + * @param continuationFinally the continuation to run upon either normal or failed completion, may be null + */ + public SynchronousContinuationFrame(Callable callable, Continuation continuationFinally) + { + this (callable, /*continuationFail*/ null, continuationFinally, /*continuation*/ null); + } + + /** + * Construct an SynchronousContinuationFrame which inherits another's continuations. + * + * @param callable the callable to invoke when the frame is run + * @param that the frame to inherit continuations from + */ + public SynchronousContinuationFrame(Callable callable, AbstractContinuationFrame that) + { + this(callable, that.getFailureContinuation(), that.getContinuation()); + } + + /** + * Construct a SynchronousContinuationFrame. + * + * @param callable the callable to invoke when the frame is run + * @param continuationFail the continuation to invoke if the operation fails, may be null + * @param continuationFinally the continuation to run upon either normal or failed completion, may be null + * @param continuation the continuation to invoke when the operation completes, may be null + */ + public SynchronousContinuationFrame(Callable callable, + Continuation continuationFail, + Continuation continuationFinally, + Continuation continuation) + { + super(continuationFail, continuationFinally, continuation); + f_callable = callable; + } + + @Override + protected R call() + throws Exception + { + return f_callable.call(); + } + + // ----- data members --------------------------------------------------- + + /** + * The logic associated with the frame. + */ + protected final Callable f_callable; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/WrapperContinuation.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/WrapperContinuation.java new file mode 100644 index 0000000000000..b2657e2e299b0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/continuations/WrapperContinuation.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.continuations; + +import com.oracle.coherence.common.base.Continuation; + + +/** + * WrapperContinuation is a Continuation which simply delegates to another continuation. + * + * @author mf 2013.07.02 + */ +public class WrapperContinuation + implements Continuation + { + public WrapperContinuation(Continuation delegate) + { + f_delegate = delegate; + } + + @Override + public void proceed(R result) + { + Continuations.proceed(f_delegate, result); + } + + protected final Continuation f_delegate; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/AbstractBufferManager.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/AbstractBufferManager.java new file mode 100644 index 0000000000000..5c1181ce5e5cd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/AbstractBufferManager.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.io; + + +import com.oracle.coherence.common.io.BufferManager; + +import java.nio.ByteBuffer; + + +/** + * AbstractBufferManager provides a skeletal implementation of the BufferManager + * interface. + * + * @author mf 2010.12.03 + */ +public abstract class AbstractBufferManager + implements BufferManager + { + // ----- BufferManager interface ---------------------------------------- + + /** + * {@inheritDoc} + *

+ * The AbstractBufferManager implementation will attempt to allocate the + * specified size, but if this does not succeed, it will attempt to + * allocate the size specified by {@link #getPreferredUnitSize}, and + * in all else fails by {@link #getPreferredUnitSize}. + */ + public ByteBuffer acquirePref(int cbPref) + { + if (cbPref <= getMaximumUnitSize()) + { + try + { + return acquire(cbPref); + } + catch (OutOfMemoryError e) {} + } + + try + { + return acquire(getPreferredUnitSize()); + } + catch (OutOfMemoryError e) {} + + return acquire(getMinimumUnitSize()); + } + + /** + * {@inheritDoc} + *

+ * The AbstractBufferManager implementation will attempt to double the + * total amount of buffer space, i.e. it will return a value close to + * cbSum. If cbSum is zero, the returned size will be determined + * by {@link #getMinimumUnitSize}. + */ + public ByteBuffer acquireSum(int cbSum) + { + return (ByteBuffer) acquirePref(Math.max(getMinimumUnitSize(), cbSum)) + .clear(); + } + + /** + * {@inheritDoc} + */ + public ByteBuffer truncate(ByteBuffer buff) + { + ensureCompatibility(buff); + + int cbUsed = buff.remaining(); + if (isUnderUtilized(buff)) + { + // resize the buffer + ByteBuffer buffNew; + try + { + buffNew = acquire(cbUsed); + } + catch (OutOfMemoryError e) + { + return buff; // use the original + } + + buffNew.put(buff).flip(); + release(buff); + return buffNew; + } + return buff; + } + + /** + * {@inheritDoc} + *

+ * The AbstractBufferManager implementation only ensures that the + * compatibility of the supplied buffer, it does not actually reclaim it. + */ + public void release(ByteBuffer buff) + { + ensureCompatibility(buff); + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Return the minimum allocation size. + *

+ * This value determines the minimum size for {@link #acquireSum}. + *

+ * AbstractBufferManager always return 1KB. + * + * @return the byte size + */ + protected int getMinimumUnitSize() + { + return 1024; + } + + /** + * Return the preferred unit size. + *

+ * This value determines the default size for {@link #acquirePref} if the + * specified amount cannot be satisfied. + *

+ * AbstractBufferManager always return 64KB. + * + * @return the preferred unit size + */ + protected int getPreferredUnitSize() + { + return 64 * 1024; + } + + /** + * Return the maximum allocation size. + *

+ * This value determines the maximum size for {@link #acquire}. + *

+ * AbstractBufferManager always return 2GB. + * + * @return the preferred unit size + */ + protected int getMaximumUnitSize() + { + return Integer.MAX_VALUE; + } + + /** + * Identify if the specified buffer is under utilized. + *

+ * This is used in determining if a buffer should be truncated. + *

+ * AbstractBufferManager returns true if the supplied buffer is less then + * 12% utilized, and the capacity is greater the {@link #getMinimumUnitSize}. + * + * @param buff the buffer + * + * @return true iff the buffer is considered under utilized + */ + protected boolean isUnderUtilized(ByteBuffer buff) + { + int cbCap = buff.capacity(); + return cbCap > getMinimumUnitSize() && buff.remaining() < cbCap >>> 3; + } + + /** + * Ensure that the specified buffer is compatible with this manager. + *

+ * The AbstractBufferManager implementation is a no-op. + * + * @param buff the buffer to ensure. + * + * @throws IllegalArgumentException if the buffer is incompatible + */ + protected void ensureCompatibility(ByteBuffer buff) + { + } + + @Override + public long getCapacity() + { + return Long.MAX_VALUE; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/CheckedBufferManager.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/CheckedBufferManager.java new file mode 100644 index 0000000000000..445c3e541180e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/CheckedBufferManager.java @@ -0,0 +1,575 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.io; + + +import com.oracle.coherence.common.base.InverseComparator; +import com.oracle.coherence.common.internal.util.HeapDump; +import com.oracle.coherence.common.io.BufferManager; +import com.oracle.coherence.common.io.BufferManagers; +import com.oracle.coherence.common.util.MemorySize; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.IdentityHashMap; +import java.util.Collections; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicLong; +import java.util.logging.Logger; +import java.util.logging.Level; + + +/** + * CheckedBufferManager is a BufferManager wrapper which adds on safety checks + * to detect improper re-use of ByteBuffers. + * + * @author mf 2011.03.29 + */ +public class CheckedBufferManager + extends WrapperBufferManager + { + // ----- constructor -------------------------------------------- + + /** + * Construct a CheckedBufferManager around the specified manager. + * + * @param mgr the manager to delegate to + */ + public CheckedBufferManager(BufferManager mgr) + { + super(mgr); + } + + // ----- BufferManager interface -------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer acquire(int cbMin) + { + ByteBuffer buff; + int cAttempts = 0; + AcquisitionSite errorSite, site = null; + do + { + errorSite = site; + buff = f_delegate.acquire(cbMin); + ++cAttempts; + } + while (!zeroed(buff) || (site = m_mapAllocated.put(buff, new AcquisitionSite())) != null); + + checkLeaks(m_cbBufferAllocated.addAndGet(buff.capacity())); + + if (cAttempts > 1) + { + // this should only happen do to either an internal manager + // error, or because another thread is using the delegate + // manager directly and it is the one messed up, just log + // here + LOGGER.log(Level.WARNING, "Compensating for unaccounted for" + + " ByteBuffer re-use of " + (cAttempts - 1) + + " buffers for request size of " + cbMin + " from " + + f_delegate + (errorSite == null ? "" : (" last " + errorSite))); + } + + return buff; + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer acquirePref(int cbPref) + { + ByteBuffer buff; + int cAttempts = 0; + AcquisitionSite errorSite, site = null; + do + { + errorSite = site; + buff = f_delegate.acquirePref(cbPref); + ++cAttempts; + } + while (!zeroed(buff) || (site = m_mapAllocated.put(buff, new AcquisitionSite())) != null); + + checkLeaks(m_cbBufferAllocated.addAndGet(buff.capacity())); + + if (cAttempts > 1) + { + // this should only happen do to either an internal manager + // error, or because another thread is using the delegate + // manager directly and it is the one messed up, just log + // here + LOGGER.log(Level.WARNING, "Compensating for unaccounted for" + + " ByteBuffer re-use of " + (cAttempts - 1) + + " buffers for prefered size of " + cbPref + " from " + + f_delegate + (errorSite == null ? "" : (" last " + errorSite))); + } + + return buff; + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer acquireSum(int cbSum) + { + ByteBuffer buff; + int cAttempts = 0; + AcquisitionSite errorSite, site = null; + + do + { + errorSite = site; + buff = f_delegate.acquireSum(cbSum); + ++cAttempts; + } + while (!zeroed(buff) || (site = m_mapAllocated.put(buff, new AcquisitionSite())) != null); + + checkLeaks(m_cbBufferAllocated.addAndGet(buff.capacity())); + + if (cAttempts > 1) + { + // this should only happen do to either an internal manager + // error, or because another thread is using the delegate + // manager directly and it is the one messed up, just log + // here + LOGGER.log(Level.WARNING, "Compensating for unaccounted for" + + " ByteBuffer re-use of " + (cAttempts - 1) + + " buffers for accumulated size of " + cbSum + + f_delegate + (errorSite == null ? "" : (" last " + errorSite))); + } + + return buff; + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer truncate(ByteBuffer buff) + { + Map mapAllocated = m_mapAllocated; + RuntimeException e; + + AcquisitionSite site = mapAllocated.remove(buff); + if (site == null) + { + e = new IllegalArgumentException( + "Rejecting attempt to truncate" + + " unknown buffer of size " + buff.capacity() + + " from " + f_delegate); + } + else + { + ByteBuffer buffNew = f_delegate.truncate(buff); + + checkLeaks(m_cbBufferAllocated.addAndGet(buffNew.capacity() - buff.capacity())); + + AcquisitionSite errorSite; + if ((errorSite = mapAllocated.put(buffNew, buff == buffNew ? site : new AcquisitionSite())) == null) + { + return buffNew; + } + + e = new IllegalStateException( + "Unable to safely compensate for unaccounted " + + " ByteBuffer re-use of truncated buffer size of " + + buff.limit() + " to " + f_delegate + " prior " + errorSite); + } + + LOGGER.log(Level.SEVERE, e.getMessage(), e); + throw e; + } + + /** + * {@inheritDoc} + */ + @Override + public void release(ByteBuffer buff) + { + if (m_mapAllocated.remove(buff) == null) + { + IllegalArgumentException e = new IllegalArgumentException( + "Rejecting attempt to release" + + " unknown buffer of size " + buff.capacity() + + " to " + f_delegate); + LOGGER.log(Level.SEVERE, e.getMessage(), e); + throw e; + } + else + { + m_cbBufferAllocated.addAndGet(-buff.capacity()); + f_delegate.release(buff); + } + } + + // ----- Object interface --------------------------------------- + + @Override + public String toString() + { + int cBuff = m_mapAllocated.size(); + long cbNow = m_cbBufferAllocated.get(); + long cbHigh = m_cbHigh.get(); + long cbLow = m_cbLow.get(); + Set setSuspect = getSuspects(BYTE_WARNING_INTERVAL / 4); + + StringBuilder sb = new StringBuilder() + .append("CheckedBufferManager(oustanding buffers=").append(cBuff) + .append(", bytes(low=").append(new MemorySize(cbLow)) + .append(", allocated=").append(new MemorySize(cbNow)) + .append(", high=").append(new MemorySize(cbHigh)) + .append("), suspects=").append(setSuspect.size()) + .append(", delegate=").append(f_delegate) + .append(")"); + + if (setSuspect.size() > 0) + { + sb.append("\nSuspects:"); + } + for (LeakSuspect record : setSuspect) + { + sb.append("\n").append(record); + } + + return sb.toString(); + } + + + // ----- Disposable interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + f_delegate.dispose(); + } + + + // ----- CheckedBufferManager helpers --------------------------- + + /** + * Verify that the specified buffer contains all zero content. + * + * @param buffer the buffer to verify + * + * @return true iff the buffer is zeroed + */ + protected static boolean zeroed(ByteBuffer buffer) + { + if (BufferManagers.ZERO_ON_RELEASE) + { + int cb = buffer.capacity(); + int ofLim = buffer.limit(); + buffer.limit(cb); + + for (int i = 0; i < cb; ++i) + { + if (buffer.get(i) != 0) + { + return false; + } + } + + buffer.limit(ofLim); + } + // else; underlying manager isn't zeroing so we can't ensure zeros, note that we can't + // zero within the checked manager because of how truncate works + + return true; + } + + /** + * Called on each allocation in order to check for leaks. + * + * @param cbAllocated the current allocation count + */ + protected void checkLeaks(long cbAllocated) + { + long cbWarnNext = m_cbWarnNext.get(); + if (cbAllocated > cbWarnNext && m_cbWarnNext.compareAndSet(cbWarnNext, + Math.max(cbAllocated, cbWarnNext) + BYTE_WARNING_INTERVAL)) + { + String sDump = DUMP_ON_WARNING ? HeapDump.dumpHeap() : null; + String sSuffix = sDump == null ? "" : ("; " + sDump + " has been collected for analysis"); + + LOGGER.warning("Passing new allocation limit: " + this + sSuffix); + + // reset low/high at start of cycle + m_cbLow.set(cbAllocated); + m_cbHigh.set(cbAllocated); + } + else + { + // update low + for (long cbLow = m_cbLow.get(); + cbAllocated < cbLow && !m_cbLow.compareAndSet(cbLow, cbAllocated); + cbLow = m_cbLow.get()); + + // update lifetime high + for (long cbHigh = m_cbHigh.get(); + cbAllocated > cbHigh && !m_cbHigh.compareAndSet(cbHigh, cbAllocated); + cbHigh = m_cbHigh.get()); + } + } + + /** + * AcquisitionSite represents an acquisition call site. + */ + public static class AcquisitionSite + { + final StackTraceElement[] f_aStackTraceElement; + + public AcquisitionSite() + { + if (SAMPLING_RATIO == 1 || ThreadLocalRandom.current().nextInt(SAMPLING_RATIO) == 0) + { + StackTraceElement[] stack = new Throwable().getStackTrace(); + + if (stack.length > 1) + { + // trim this frame + StackTraceElement[] stackTrim = new StackTraceElement[stack.length - 1]; + + for (int i = 0; i < stackTrim.length; ++i) + { + stackTrim[i] = stack[i + 1]; + } + + stack = stackTrim; + } + + f_aStackTraceElement = stack; + } + else + { + f_aStackTraceElement = null; + } + } + + /** + * Return the stack trace associated with this site. + * + * @return the stack trace associated with this site + */ + public StackTraceElement[] getStackTrace() + { + return f_aStackTraceElement; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder("AcquisitionSite"); + StackTraceElement[] stack = getStackTrace(); + + if (stack == null) + { + return sb.append(" - unsampled").toString(); + } + + sb.append('\n'); + for (StackTraceElement e : stack) + { + sb.append("\tat ").append(e).append('\n'); + } + + return sb.toString(); + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) + { + return true; + } + else if (obj == null) + { + return false; + } + + return Arrays.equals(getStackTrace(), ((AcquisitionSite) obj).getStackTrace()); + } + + @Override + public int hashCode() + { + return Arrays.hashCode(getStackTrace()); + } + } + + /** + * AcquisitionRecord represents statistics for unrelased memory associated with a single allocate site. + */ + public static class LeakSuspect + implements Comparable + { + AcquisitionSite site; + long cOccurances; + long cb; + + /** + * Return the site at which the acquisitions were made. + * + * @return the site at which the acquisitions were made + */ + public AcquisitionSite getAcquisitionSite() + { + return site; + } + + /** + * Return the number of unreleased bytes for the associated allocation site. + * + * @return the number of unreleased bytes for the associated allocation site + */ + public long getByteCount() + { + return cb; + } + + /** + * Return the number of unreleased buffers for the associated allocation site. + * + * @return the number of unreleased buffers for the associated allocation site + */ + public long getBufferCount() + { + return cOccurances; + } + + @Override + public int compareTo(LeakSuspect that) + { + long cbDiff = cb - that.cb; + long cDiff = cOccurances - that.cOccurances; + return (int) (cbDiff == 0 + ? cDiff == 0 + ? hashCode() - that.hashCode() + : cDiff + : cbDiff); + } + + @Override + public String toString() + { + return new StringBuilder() + .append(cOccurances).append(" acquisitions consuming a total of ").append(new MemorySize(cb)) + .append(" from ").append(site).toString(); + } + } + + /** + * Return a set of leak suspects describing suspect leaks. + * + * @param cbSuspect the cumulative unreleased memory for an leak to be suspected. + * + * @return a set of leak suspects sorted by the size of the potential leak + */ + public SortedSet getSuspects(long cbSuspect) + { + Map.Entry[] aEntry = new Map.Entry[0]; + synchronized (m_mapAllocated) + { + aEntry = m_mapAllocated.entrySet().toArray(aEntry); + } + + SortedSet setSuspect = new TreeSet(InverseComparator.INSTANCE); + + // group retained buffers by their acquisition site + Map mapRecords = new HashMap<>(); + for (Map.Entry entry : aEntry) + { + ByteBuffer buff = entry.getKey(); + AcquisitionSite site = entry.getValue(); + LeakSuspect record = mapRecords.get(site); + if (record == null) + { + record = new LeakSuspect(); + record.site = entry.getValue(); + mapRecords.put(site, record); + } + record.cOccurances++; + record.cb += buff.capacity(); + if (record.cb > cbSuspect) + { + setSuspect.add(record); + } + } + + return setSuspect; + } + + // ----- constants ---------------------------------------------- + + /** + * The Logger to use. + */ + protected static final Logger LOGGER = Logger.getLogger(CheckedBufferManager + .class.getName()); + + /** + * The interval at which to log warning messages. + */ + protected static final long BYTE_WARNING_INTERVAL = new MemorySize(System.getProperty( + CheckedBufferManager.class.getName() + ".limit", String.valueOf(Runtime.getRuntime().maxMemory() / 10))) + .getByteCount(); + + /** + * The interval at which to log warning messages. + */ + protected static final boolean DUMP_ON_WARNING = Boolean.getBoolean(CheckedBufferManager.class.getName() + ".dump"); + + /** + * The ratio at which acquisitions are sampled. + */ + protected static final int SAMPLING_RATIO = Integer.parseInt(System.getProperty( + CheckedBufferManager.class.getName() + ".samplingRatio", "1")); + + // ----- data members ------------------------------------------- + + /** + * Map of buffers which this manager has handed out but which have + * yet to be released, to Site containing the stack of where + * there were acquired. + */ + protected final Map m_mapAllocated = + Collections.synchronizedMap( + new IdentityHashMap()); + + /** + * The total number of bytes currently handed out. + */ + protected final AtomicLong m_cbBufferAllocated = new AtomicLong(); + + /** + * The next size at which to warn. + */ + protected final AtomicLong m_cbWarnNext = new AtomicLong(BYTE_WARNING_INTERVAL); + + /** + * The low water mark since the last warning. + */ + protected final AtomicLong m_cbLow = new AtomicLong(); + + /** + * The all time high water mark. + */ + protected final AtomicLong m_cbHigh = new AtomicLong(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/SegmentedBufferManager.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/SegmentedBufferManager.java new file mode 100644 index 0000000000000..a5045f7db2b04 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/SegmentedBufferManager.java @@ -0,0 +1,1215 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.io; + + +import com.oracle.coherence.common.base.Disposable; +import com.oracle.coherence.common.collections.ConcurrentLinkedStack; +import com.oracle.coherence.common.collections.Stack; +import com.oracle.coherence.common.io.BufferManager; +import com.oracle.coherence.common.io.BufferManagers; +import com.oracle.coherence.common.io.Buffers; +import com.oracle.coherence.common.util.Duration; +import com.oracle.coherence.common.util.MemorySize; + +import java.nio.ByteOrder; +import java.util.logging.Logger; +import java.util.logging.Level; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.nio.ByteBuffer; + + +/** + * The SegmentedBufferManager performs buffer managment by dividing + * the buffers into a number of segments, such as small, medium, and large. + *

+ * Each segment contains a number of generations, where generations are created + * on demand and lazily collected. + * + * @author ch 2010.03.04 + */ +public class SegmentedBufferManager + implements BufferManager, Disposable + { + // ----- constructors --------------------------------------------------- + + /** + * Creates a SegmentedBufferManager. + * + * @param allocator the BufferAllocator to use to fill the pool + * @param cbBufferMin the minimum buffer size + * @param cbMax the total amount of memory to pool + */ + public SegmentedBufferManager(BufferAllocator allocator, int cbBufferMin, long cbMax) + { + this(allocator, DEFAULT_SEGMENT_COUNT, cbMax / DEFAULT_SEGMENT_COUNT, cbBufferMin, DEFAULT_GROWTH_FACTOR); + } + + /** + * Creates a SegmentedBufferManager. + * + * @param allocator the BufferAllocator to use to fill the pool + * @param cbMax the total amount of memory to pool + */ + public SegmentedBufferManager(BufferAllocator allocator, long cbMax) + { + this(allocator, DEFAULT_SEGMENT_COUNT, cbMax / DEFAULT_SEGMENT_COUNT, DEFAULT_BUF_SIZE, DEFAULT_GROWTH_FACTOR); + } + + /** + * Creates a SegmentedBufferManager. + * + * @param allocator the BufferAllocator to use to fill the pool + * @param cSegments the number of segments + * @param cbSegment the maximum number bytes each segment will consume + * @param cbBufferMin the smallest buffer size + * @param nGrowthFactor the segment growth factor + */ + public SegmentedBufferManager(BufferAllocator allocator, int cSegments, + long cbSegment, int cbBufferMin, int nGrowthFactor) + { + m_allocator = allocator; + final int cbDefaultBuf = SegmentedBufferManager.DEFAULT_BUF_SIZE; + + cbSegment = Math.min(cbSegment, (long) Integer.MAX_VALUE * GEN_ID_UNPOOLED); // max gen size is 2GB + + if ((cbBufferMin & (DEFAULT_BUF_SIZE - 1)) != 0) + { + // ensure that the size doesn't touch our bits + // TODO cbBufferMin = (cbBufferMin + DEFAULT_BUF_SIZE - 1) & ~(DEFAULT_BUF_SIZE - 1); + cbBufferMin = (cbBufferMin / cbDefaultBuf) * cbDefaultBuf + + (cbBufferMin % cbDefaultBuf == 0 ? 0 : cbDefaultBuf); + } + + final int cHighestBit = Integer.SIZE - 1; + final int cLowestBit = Integer.numberOfTrailingZeros(cbBufferMin); + final int nMaxSegments = cHighestBit - (cLowestBit * nGrowthFactor); + + if (nMaxSegments < 1) + { + throw new IllegalArgumentException("Growthfactor is to aggressive: " + + nGrowthFactor); + } + else if (cSegments > nMaxSegments) + { + throw new IllegalArgumentException("The number of segments exceeded: " + + nMaxSegments); + } + + int cbBuff = m_cbMin = cbBufferMin; + Segment[] aSegment = f_aSegments = new Segment[cSegments]; + for (int i = 0; i < cSegments; i++) + { + long cBuf = cbSegment / cbBuff; + aSegment[i] = allocateSegment(cbBuff, (int) (cBuf > Integer.MAX_VALUE ? Integer.MAX_VALUE : cBuf)); + cbBuff = cbBuff << nGrowthFactor; + } + + m_cbMax = cbBuff >> nGrowthFactor; + m_nSegmentGrowthFactor = nGrowthFactor; + } + + // ----- SegmentedBufferManager interface ------------------------------- + + /** + * Set the name of this BufferManager + * + * @param sName the name + */ + public void setName(String sName) + { + m_sName = sName; + } + + /** + * Return the name of this BufferManager + * + * @return the name + */ + public String getName() + { + return m_sName; + } + + // ----- BufferManager interface ---------------------------------------- + + + @Override + public long getCapacity() + { + long cbCapacity = 0; + + for (Segment seg : f_aSegments) + { + long cbBuf = seg.getBufferSize(); + long cbGen = cbBuf * seg.getGenerationSize(); + + cbCapacity += cbGen * GEN_ID_UNPOOLED; + } + + return cbCapacity; + } + + /** + * {@inheritDoc} + */ + public ByteBuffer acquire(final int cbMin) + { + ByteBuffer buff = ensureMinBuffer(cbMin, Integer.MAX_VALUE); + + if (buff.capacity() > cbMin) + { + buff.limit(cbMin); + } + + return buff; + } + + /** + * {@inheritDoc} + */ + public ByteBuffer acquirePref(int cbPref) + { + ByteBuffer buff = ensureBuffer(cbPref); + + if (buff.capacity() > cbPref) + { + buff.limit(cbPref); + } + + return buff; + } + + /** + * {@inheritDoc} + */ + public ByteBuffer acquireSum(int cbSum) + { + return ensureBuffer(cbSum); + } + + /** + * {@inheritDoc} + */ + public void release(ByteBuffer buffer) + { + getSegment(buffer.capacity()).release(buffer); + } + + /** + * {@inheritDoc} + */ + public ByteBuffer truncate(ByteBuffer buff) + { + int nGen = decodeGeneration(buff.capacity()); + return nGen < GEN_ID_TRUNCATE + ? buff // common path + : truncateComplex(buff); + } + + /** + * Truncate the specified buffer as described by {@link #truncate truncate}. + * + * @param buff the buffer to truncate + * + * @return the replacement buffer + */ + protected ByteBuffer truncateComplex(ByteBuffer buff) + { + int cbSeg = decodeSize(buff.capacity()); + int cbSegPre = cbSeg >> m_nSegmentGrowthFactor; + int cbUsed = buff.remaining(); + + if (cbSeg > m_cbMin && cbUsed <= cbSegPre) + { + // the used buffer could fit in the previous segment + ByteBuffer buffNew; + try + { + buffNew = ensureMinBuffer(/*cbMin*/ cbUsed, /*cbMax*/ cbSeg - 1); + } + catch (OutOfMemoryError e) + { + return buff; // use the original + } + + buffNew.put(buff).flip(); + release(buff); + return buffNew; + } + return buff; + } + + // ----- Disposable interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + public void dispose() + { + for (Segment pool : f_aSegments) + { + pool.dispose(); + } + } + + + // ----- Object interface ------------------------------------------------ + + @Override + public String toString() + { + long cbCapacity = 0; + long cbUsed = 0; + long cAlloc = 0; + long cNonPooled = 0; + long cbAvailable = 0; + long cbPeakHist = 0; + + StringBuilder sbSeg = new StringBuilder(); + for (Segment seg : f_aSegments) + { + long cbBuf = seg.getBufferSize(); + long cbGen = cbBuf * seg.getGenerationSize(); + + cbAvailable += seg.f_stackBuf.size() * cbBuf; + cbCapacity += cbGen * GEN_ID_UNPOOLED; + cbUsed += seg.getAcquired() * cbBuf; + cAlloc += seg.getAllocationCount(); + cNonPooled += seg.getNonPooledAllocationCount(); + cbPeakHist += seg.m_cMaxBuffersHistoric * cbBuf; // peak since last full GC + + sbSeg.append(seg).append(", "); + } + return getName() + "(capacity=" + new MemorySize(cbCapacity) + + ", usage=" + new MemorySize(cbUsed) + ".." + new MemorySize(cbPeakHist) + "/" + new MemorySize(cbAvailable) + + ", hit rate=" + ((cAlloc - cNonPooled) * 100/ (cAlloc == 0 ? 1 : cAlloc)) + "%" + + ", segment utilization=" + sbSeg.toString() + + "allocator=" + m_allocator + ")"; + } + + + // ----- helpers --------------------------------------------------------- + + /** + * Return the allocator used by this manager. + * + * @return the allocator + */ + protected BufferAllocator getAllocator() + { + return m_allocator; + } + + // ----- private members ------------------------------------------------- + + /** + * Given a buffer allocation size, extract the generation id of that + * buffer. + * + * @param cbBuffer the buffer allocation size + * + * @return the generation id + */ + protected int decodeGeneration(final int cbBuffer) + { + return (cbBuffer & GEN_ID_MASK) >> GEN_ID_SHIFT; + } + + /** + * Given an allocated buffer size, determine the configured buffer size + * of that buffer. + * + * @param cb the allocated buffer size + * + * @return the configured buffer size + */ + private int decodeSize(final int cb) + { + return cb & ~GEN_ID_MASK; + } + + /** + * Return a ByteBuffer from the optimal PoolSegment for a specific count of bytes. + * + * @param cb the size to match + * + * @return the buffer + */ + private ByteBuffer ensureBuffer(int cb) + { + Segment[] aSegments = f_aSegments; + int cSegments = aSegments.length; + int iSeg = 0; + if (cb >= m_cbMax) + { + iSeg = cSegments - 1; + } + else + { + for (int cbMin = m_cbMin; cb > cbMin; ++iSeg) + { + cb = cb >> m_nSegmentGrowthFactor; + } + } + + // try to find the closest matching segment by incrementally checking neighboring segments + // this as apposed to simply looping around showed 50% performance gaines in some tests + ByteBuffer buff = null; + for (int i = 0; i < cSegments && buff == null; ++i) + { + if (iSeg + i < cSegments) + { + buff = aSegments[iSeg + i].acquire(/*fEnsure*/ false); + } + + if (buff == null && i > 0 && iSeg - i >= 0) + { + buff = aSegments[iSeg - i].acquire(/*fEnsure*/ false); + } + // no space in segment; try next + } + + // fall back on non-pooled allocation if necessary + return buff == null ? aSegments[iSeg].acquire(/*fEnsure*/ true) : buff; + } + + /** + * Return a ByteBuffer of at least the specified size. + * + * @param cbMin the minimum required size + * @param cbMax the maximum desired size + * + * @return the buffer + */ + private ByteBuffer ensureMinBuffer(int cbMin, int cbMax) + { + Segment[] aSegments = f_aSegments; + int cSegments = aSegments.length; + if (cbMin > m_cbMax) + { + throw new OutOfMemoryError("requested buffer size exceeds pool maximum"); + } + + int iSeg = 0; + for (int cSeg = aSegments.length; + iSeg < cSeg && aSegments[iSeg].f_cbBuffer < cbMin; + ++iSeg) + {} + + for (int i = iSeg; i < cSegments; ++i) + { + Segment segment = aSegments[i]; + if (segment.f_cbUnpooledBuffer > cbMax) + { + break; + } + + ByteBuffer buf = segment.acquire(/*fEnsure*/ false); + if (buf != null) + { + return buf; + } + // no space in segment; try next + } + + // all suitable segments are full; allow non-pooled from best fit + return aSegments[iSeg].acquire(/*fEnsure*/ true); + } + + /** + * Return the matching PoolSegment for a specific count of bytes. + * + * @param cb the size to match + * + * @return the PoolSegment that matches the size + * + * @throws IllegalArgumentException if no PoolSegment matches the size + * exactly + */ + private Segment getSegment(int cb) + throws IllegalArgumentException + { + Segment[] aSegments = f_aSegments; + final int cbDecoded = cb = decodeSize(cb); + final int cSeg = aSegments.length; + int iSeg = 0; + + for (int cbMin = m_cbMin; cb > cbMin && iSeg < cSeg; ++iSeg) + { + cb = cb >> m_nSegmentGrowthFactor; + } + + if (iSeg < cSeg && cbDecoded == aSegments[iSeg].getBufferSize()) + { + return aSegments[iSeg]; + } + + throw new IllegalArgumentException("No pool segment for size: " + + cbDecoded + " in " + cSeg + " segment(s) between " + + aSegments[0].getBufferSize() + " .. " + + aSegments[cSeg - 1].getBufferSize()); + } + + + // ----- inner class: Segment ------------------------------------------- + + /** + * Allocate a segment for buffers of the specified size. + * + * @param cbBuffer the size of the individual ByteBuffers + * @param cBuffers the number of ByteBuffers for the pool + * + * @return the allocated segment + */ + protected Segment allocateSegment(int cbBuffer, int cBuffers) + { + return new Segment(cbBuffer, cBuffers); + } + + + /** + * PoolSegment defines the pools which buffers can be allocated from + * and released to. The implementation provide highly concurrent access so + * that elements can be acquired and released to the pool without + * synchronization. + *

+ * A PoolSegment is shrunk when its capacity remains greater than the actual + * usage for a period of time. The capacity will be evaluated periodically + * based on the number of buffer releases (cReevalFreq) and at + * specific time (PERIODIC_CLEANUP_FREQUENCY) intervals. + * When a pool shrinks, buffers that belong to purged generations will no + * longer be retained when released back to the pool. + */ + protected class Segment + implements Disposable + { + // ----- constructors--------------------------------------------- + + /** + * Construct a pool for buffers of the specified size. + * + * @param cbBuffer the size of the individual ByteBuffers + * @param cBuffers the number of ByteBuffers for the pool + */ + protected Segment(int cbBuffer, int cBuffers) + { + f_cbBuffer = cbBuffer; + // COH-4231: there should be at least one buffer per generation + f_cBufferGen = Math.max(cBuffers / GEN_ID_UNPOOLED, 1); + f_stackBuf = new ConcurrentLinkedStack(); + f_cGeneration = new AtomicInteger(GEN_ID_EMPTY); + f_cReleased = new AtomicLong(0L); + f_cNonPooledReleased = new AtomicLong(0L); + f_cAcquired = new AtomicLong(0L); + f_cNonPooledAllocations = new AtomicLong(0L); + f_cbUnpooledBuffer = encodeGeneration(GEN_ID_UNPOOLED); + } + + // ----- public methods ------------------------------------------ + + /** + * Acquire the next available buffer from the pool. If the pool is + * empty, either grow the pool or as the last resort return a + * non-pooled buffer. + * + * @return a ByteBuffer + */ + public ByteBuffer acquire() + { + return acquire(/*fEnsure*/ true); + } + + /** + * Acquire the next available buffer from the pool. If the pool is + * empty, either grow the pool or as the last resort return a + * non-pooled buffer. + * + * @param fEnsure true iff non-pooled allocation is allowable + * + * @return a ByteBuffer or null if fEnsure == false and no suitable buffer is available + */ + public ByteBuffer acquire(boolean fEnsure) + { + // optimized for most common scenario: there's something in the pool + // (otherwise it wouldn't be a pool) + ByteBuffer buffer = f_stackBuf.pop(); + if (buffer == null) + { + buffer = acquireComplex(fEnsure); + if (buffer == null) + { + return null; + } + } + + f_cAcquired.incrementAndGet(); + return buffer; + } + + /** + * Return true iff the segment is currently allowed to shrink + * + * @return true iff the segment is currently allowed to shrink + */ + protected boolean isShrinkable() + { + return true; + } + + /** + * Release a buffer back to the pool, and occasionally check if it is + * possible to shrink the pool. + * + * @param buffer the buffer to release + */ + public void release(ByteBuffer buffer) + { + int nGeneration = decodeGeneration(buffer.capacity()); + + if ((f_cReleased.incrementAndGet() & STATS_FREQUENCY) == 0) + { + recordUsage(); + evaluateCapacity(/*fOthers*/ true); + } + + if (nGeneration == GEN_ID_UNPOOLED && + f_cNonPooledReleased.incrementAndGet() % UNPOOLED_RECLAIM_INTERVAL != 0 && // only for so long + f_stackBuf.size() < f_cBufferGen * GEN_ID_TRUNCATE) // only so much + { + // we've been given an unpooled buffer to release, which indicates that at some "recent" point + // we were out of pool buffers and had to allocate an unpooled one which is expensive. If this + // happens frequently then either the pool is undersized or the application has a leak. In + // the case of a leak, overtime it could completely drain the pool of poolable buffers, which + // if not refilled would entirely negate the pooling benefits. In an effort to avoid penalizing + // good shared pool users we will allow pooling of the "unpooled" buffers, but not all of them. + // But we do limit the number of re-uses so that if there is no leak we won't overinflate our + // pool. Also we don't allow ourselves to hold unpooled buffers if the pool is fairly full + --nGeneration; // treat it like the max gen, so it may still be released now if pool has "shrunk" + } + + // always store in default java byte order BIG_ENDIAN + // so that acquired buffers are as good as freshly allocated + // ones. + buffer.order(ByteOrder.BIG_ENDIAN).clear(); // just resets position and limit + if (BufferManagers.ZERO_ON_RELEASE) + { + Buffers.zero(buffer); + } + + // Release the buffer back into the pool if: + // 1) buffer is not of the non-pooled generation + // 2) buffer belongs to a generation that is less or equal to the + // current generation + // 3) the current generation is 'LOCKED' (meaning at least one + // other thread is in need of a buffer, or is shrinking + // capacity at this instant) + final int cCurrentGen = getGenerationId(); + if (nGeneration <= cCurrentGen || (cCurrentGen == GEN_ID_LOCKED && nGeneration != GEN_ID_UNPOOLED) || !isShrinkable()) + { + f_stackBuf.push(buffer); + } + else + { + dropBuffer(buffer); + } + } + + /** + * Release the specified buffer from the segment. + * + * @param buffer the buffer + */ + protected void dropBuffer(ByteBuffer buffer) + { + m_allocator.release(buffer); + } + + // ----- Disposable implementation ------------------------------ + + /** + * {@inheritDoc} + */ + public void dispose() + { + trim(0); + } + + // ----- accessors ---------------------------------------------- + + /** + * Get the default size of buffers in this pool. Note that the + * actual size of buffers may be larger, since each generation's + * size is incrementally larger to differentiate it from other + * generations. + * + * @return the default size of the buffers in this pool + */ + public int getBufferSize() + { + return f_cbBuffer; + } + + /** + * Get the number of buffers that are allocated per generation. + * + * @return the number of buffers in each generation + */ + public int getGenerationSize() + { + return f_cBufferGen; + } + + // ----- private accessors -------------------------------------- + + /** + * Get the current number of generations for this pool. + * + * @return the number of generations for this pool + */ + private int getGenerationId() + { + return f_cGeneration.get(); + } + + /** + * Return the number of currently acquired buffers. + * + * @return the number of currently acquired buffers + */ + protected int getAcquired() + { + return Math.max(0, (int) (f_cAcquired.get() - f_cReleased.get())); + } + + /** + * Return the number of allocations performed on this segment. + * + * @return the number of allocations performed on this segment + */ + private long getAllocationCount() + { + return f_cAcquired.get(); + } + + /** + * Return the number of releases performed on this segment. + * + * @return the number of releases performed on this segment + */ + private long getReleaseCount() + { + return f_cReleased.get(); + } + + /** + * Return the number of non-pooled allocations performed on this segment. + * + * @return the number of non-pooled allocations performed on this segment + */ + private long getNonPooledAllocationCount() + { + return f_cNonPooledAllocations.get(); + } + + // ----- internal --------------------------------------------------- + + /** + * Return a buffer, potentially grow the PoolSegment or returning a non + * pooled buffer. + * + * @param fEnsure true if a non-pooled buffer should be allocated if necessary + * + * @return a buffer + */ + protected ByteBuffer acquireComplex(boolean fEnsure) + { + final AtomicInteger atomicGen = f_cGeneration; + while (true) + { + // we poll after obtaining cGen to avoid accidently growing by + // two generations + int cGen = atomicGen.get(); + + ByteBuffer buffer = f_stackBuf.pop(); + if (buffer != null) + { + return buffer; + } + + switch (cGen) + { + case GEN_ID_UNPOOLED - 1: + // no more generations; allocate a "throw away" buffer + return fEnsure ? allocateNonPooledBuffer() : null; + + case GEN_ID_LOCKED: + // this spin is limited as it will end once there are + // available buffers in the queue + break; + + case GEN_ID_EMPTY: + // fall through + + default: + // attempt to allocate a generation + if (atomicGen.compareAndSet(cGen, GEN_ID_LOCKED)) + { + int nGen = cGen + 1; + boolean fSuccess = false; + try + { + // allocate an entire generation + recordUsage(); + fSuccess = allocateGeneration(nGen); + } + finally + { + atomicGen.set(fSuccess ? nGen : cGen); + } + } + // else spin to obtain growth lock + break; + } + } + } + + /** + * Allocate a new buffer which will not be returned to the pool + * after it has been released. + * + * @return a buffer that will not be pooled when it is released + */ + protected ByteBuffer allocateNonPooledBuffer() + { + f_cNonPooledAllocations.incrementAndGet(); + return m_allocator.allocate(f_cbUnpooledBuffer); + } + + /** + * Allocate a generation of ByteBuffers. + * + * @param nGeneration the generation id of the block + * + * @return true iff the generation was successfully allocated + */ + protected boolean allocateGeneration(int nGeneration) + { + int cbBuffer = encodeGeneration(nGeneration); + + LOGGER.log(Level.FINE, getName() + " growing segment '" + + getBufferSize() + "' to " + (nGeneration + 1) + + " generations"); + + // record when this happened (do this first so we won't bother + // checking the capacity to see if it needs to shrink on another + // thread) + m_ldtNextEvaluation = System.currentTimeMillis() + + CLEANUP_FREQUENCY_MILLIS; + + try + { + return allocateGenerationBuffers(nGeneration, cbBuffer); + } + catch (OutOfMemoryError e) + { + return false; + } + } + + /** + * Allocate a series of buffers. + * + * @param nGen the generation id + * @param cbBuffer the size of each buffer + * + * @return true iff the generation was allocated + */ + protected boolean allocateGenerationBuffers(int nGen, int cbBuffer) + { + for (int i = 0, c = getGenerationSize(); i < c; ++i) + { + try + { + f_stackBuf.push(m_allocator.allocate(cbBuffer)); + } + catch (OutOfMemoryError e) + { + return i > 0; // return true if any buffers for this generation were allocated + } + } + + return true; + } + + /** + * Evaluate if and how much the pool should shrink. If the capacity + * is twice the amount needed in the pool. The pool will be cut in + * half. + * + * @param fEvalPeers true if all segments should be evaluated + */ + private void evaluateCapacity(final boolean fEvalPeers) + { + final long ldtNow = System.currentTimeMillis(); + final int nGen = getGenerationId(); + + if (nGen > GEN_ID_EMPTY + && ldtNow > m_ldtNextEvaluation + && f_cGeneration.compareAndSet(nGen, GEN_ID_LOCKED)) + { + // record next check time + m_ldtNextEvaluation = ldtNow + CLEANUP_FREQUENCY_MILLIS; + int nDesiredGen = nGen; + try + { + if (isShrinkable()) + { + int nInUse = nGen - (f_stackBuf.size() / f_cBufferGen); // we certainly need whatever the app is currently using + int nRecent = GEN_ID_EMPTY + (m_cMaxBuffers / f_cBufferGen) + (m_cMaxBuffers % f_cBufferGen == 0 ? 0 : 1); // recent high water mark + + nDesiredGen = Math.min(nGen, Math.max(nInUse, nRecent)); + + int cCapacity = Math.min(nGen + 1, GEN_ID_UNPOOLED); + if (nDesiredGen == GEN_ID_EMPTY || (nDesiredGen < cCapacity && nDesiredGen != nGen)) + { + m_cMaxBuffersHistoric = m_cMaxBuffers; + + LOGGER.log(Level.FINE, getName() + " shrinking segment '" + getBufferSize() + + "' by " + (nGen - nDesiredGen) + + " generation(s) to " + (nDesiredGen + 1) + ", based on recent high water mark of " + + new MemorySize(m_cMaxBuffers * f_cbBuffer)); + + // since we hand out buffers in LIFO order we must actively trim buffers now; rather then + // let them bleed off over time otherwise we could hold them "forever" + int cTrimmed = trim(nDesiredGen); + + LOGGER.log(Level.FINEST, getName() + " scavenged " + cTrimmed + " buffers; " + + new MemorySize(f_cbBuffer * cTrimmed)); + } + } + + if (fEvalPeers) + { + for (Segment pool : SegmentedBufferManager.this.f_aSegments) + { + if (pool != this) + { + pool.evaluateCapacity(/*fOthers*/ false); + } + } + } + } + finally + { + m_cMaxBuffers /= 2; // don't completely blow out stats + f_cGeneration.set(nDesiredGen); + } + } + } + + /** + * Record the current buffer usage. + */ + private void recordUsage() + { + m_cMaxBuffers = Math.max(m_cMaxBuffers, getAcquired()); + m_cMaxBuffersHistoric = Math.max(m_cMaxBuffersHistoric, m_cMaxBuffers); + } + + /** + * Release the buffers from later generations. + * + * @param nGeneration the minimum generation to retain + * + * @return the number of trimmed buffers + */ + private int trim(final int nGeneration) + { + int cbCutoff = encodeGeneration(nGeneration + 1); + int cRemove = 0; + + // in case the application has a leak, and we've been replenishing the + // pool with "unpoolable" buffers, refuse to reclaim all of them + + ConcurrentLinkedStack stackTmp = new ConcurrentLinkedStack(); + for (ByteBuffer buf = f_stackBuf.pop(); buf != null; buf = f_stackBuf.pop()) + { + if (buf.capacity() >= cbCutoff) + { + ++cRemove; + dropBuffer(buf); + } + else + { + stackTmp.push(buf); + } + } + + for (ByteBuffer buf : stackTmp) + { + f_stackBuf.push(buf); + } + + return cRemove; + } + + /** + * Return the buffer allocation size for a given generation. + * + * @param nGenId the generation id + *= + * @return the buffer allocation size + */ + protected int encodeGeneration(final int nGenId) + { + return getBufferSize() | (nGenId << GEN_ID_SHIFT); + } + + @Override + public String toString() + { + return new MemorySize(f_cbBuffer).toString() + "(" + (getAcquired() * 100) / (f_cBufferGen * GEN_ID_UNPOOLED) + "%)"; + } + + // ----- constants ---------------------------------------------- + + /** + * Generation id that indicates that the segment has yet to be initialized. + */ + private static final int GEN_ID_EMPTY = -1; + + /** + * Generation id that indicates that the generation lock has been + * taken, which means that a thread is either growing or shrinking + * the pool. + */ + private static final int GEN_ID_LOCKED = -2; + + // ----- data members-------------------------------------------- + + /** + * The configured size of the buffers in the pool. + */ + protected final int f_cbBuffer; + + /** + * The number of buffers allocated in a generation. + */ + protected final int f_cBufferGen; + + /** + * The stack which pools all the buffers that are not acquired. + *

+ * Maintained as a stack improves performance especially for the RDMA extended allocator as + * keys for recently used buffers may still be in the HCA cache. + */ + protected final Stack f_stackBuf; + + /** + * The current generation id. This is a counter that starts at zero + * and goes up to GEN_ID_UNPOOLED. Buffers that belong to + * generation id zero through (GEN_ID_UNPOOLED - 1) may be + * pooled, and buffers that belong to generation id + * GEN_ID_UNPOOLED are never pooled. By altering the + * current generation id, only buffers of that (or older) generation + * are returned to the pool when they are released. + */ + protected final AtomicInteger f_cGeneration; + + /** + * The next the segment should be evaluated for shrinkage. + */ + private volatile long m_ldtNextEvaluation; + + /** + * This is the count of pooled buffers that have been handed out by + * this pool. + */ + protected final AtomicLong f_cAcquired; + + /** + * The count of non-pooled allocations. + */ + protected final AtomicLong f_cNonPooledAllocations; + + /** + * This is the count of buffers that have been released back to this + * pool. + */ + protected final AtomicLong f_cReleased; + + /** + * The count of the number of unpooled releases. + */ + protected final AtomicLong f_cNonPooledReleased; + + /** + * The size of a non-pooled buffer. Since the size of a buffer + * indicates its generation id, we pre-calculate the size of the + * generation of buffers that are not pooled. + */ + protected final int f_cbUnpooledBuffer; + + /** + * The peak recorded sample usage during the pending cleanup interval. + */ + private int m_cMaxBuffers; + + /** + * The peak recorded sample usage since the last shrinkage. + */ + private int m_cMaxBuffersHistoric; + } + + + // ----- inner class: BufferAllocator ----------------------------------- + + /** + * A BufferAllocator is provides a mean for allocating ByteBuffers. + */ + public interface BufferAllocator + { + /** + * Allocate and return buffer of the specified size. + * + * @param cb the required buffer size + * + * @return the buffer + * + * @throws OutOfMemoryError if the request cannot be satisified + */ + public ByteBuffer allocate(int cb); + + /** + * Release a ByteBuffer back to the allocator. + * + * @param buff the buffer to release + */ + public void release(ByteBuffer buff); + } + + // ----- constants ------------------------------------------------------ + + /** + * The generation id is stored in bits 6-9 (bits 0-5 are reserved to + * enforce a 64 byte paragraph boundary). + */ + private static final int GEN_ID_SHIFT = 6; + + /** + * The number of bits reserved to store the generation id. + */ + protected static final int GEN_ID_BITS = 4; + + /** + * The bit mask of the bits used to store the generation id inside a size. + */ + private static final int GEN_ID_MASK = ((1 << GEN_ID_BITS) - 1) + << GEN_ID_SHIFT; + + /** + * The ID of the generation that is freely allocated and un-pooled. In + * other words, when we reach this generation, the buffers that are + * allocated will not be returned to the pool when they are released. + */ + protected static final int GEN_ID_UNPOOLED = (1 << GEN_ID_BITS) - 1; + + /** + * The ID of the first generation at which truncation will be considered necessary. + * Exempting lower generations from truncation allows for the truncate logic to very + * efficiently avoid truncating buffers when there is significant space available + * within the segment. + */ + private static final int GEN_ID_TRUNCATE = (GEN_ID_UNPOOLED * 2) / 3; + + /** + * The number of times an unpooled buffer is reused before being released. + */ + public static final long UNPOOLED_RECLAIM_INTERVAL = 1024; + + /** + * The default release frequency at which to records statistics. + * + * Note this value must be a power of two - 1. + */ + public static final int STATS_FREQUENCY = 255; + + /** + * The default size of the smallest ByteBuffer. Note that the actual size + * of the ByteBuffer may be larger, since the size includes an implicit + * generation ID. Also note that the default buffer size cannot use any of + * the bits 0-9. + */ + public static final int DEFAULT_BUF_SIZE = 1 + << (GEN_ID_BITS + GEN_ID_SHIFT); // 1024 + + /** + * The growth factor between each pool segment. The factor describes the + * number of left shifts. + */ + public static final short DEFAULT_GROWTH_FACTOR = 1; + + /** + * The default number of segments. + */ + public static final int DEFAULT_SEGMENT_COUNT = 7; // sufficiently large to allow for 1KB..64KB segments, covering standard MTU sizes + + /** + * Reevaluation of capacity does not occur more than once every + * period, as defined here. Furthermore, reevaluation of one pool + * (i.e. a pool of one buffer size) will ensure that the other pools + * (i.e. the pools that hold buffers of the other sizes) have also + * reevaluated, since it is possible that they have no activity that + * would cause them to reevaluate on their own. + */ + protected static final long CLEANUP_FREQUENCY_MILLIS = new Duration(System.getProperty( + SegmentedBufferManager.class.getName() + ".cleanup.frequency", "1s")).as(Duration.Magnitude.MILLI); + + /** + * The logger. + */ + protected static final Logger LOGGER = Logger.getLogger( + SegmentedBufferManager.class.getName()); + + // ----- data members --------------------------------------------------- + + /** + * The BufferAllocator to use to grow the pool. + */ + private final BufferAllocator m_allocator; + + /** + * The pool is composed of several sub-ordinate pools represented by the + * PoolSegment inner class, each of a specific allocation size. This array + * holds the various PoolSegment instances, starting with the smallest + * allocation size and proceeding to the largest. + */ + private final Segment[] f_aSegments; + + /** + * The buffer size of the smallest buffer. + */ + private final int m_cbMin; + + /** + * The buffer size of the largest buffer. + */ + private final int m_cbMax; + + /** + * The segment growth factor. + */ + private final int m_nSegmentGrowthFactor; + + /** + * The name of the allocator + */ + protected String m_sName = getClass().getSimpleName() + "(" + System.identityHashCode(this) + ")"; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/SlabBufferManager.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/SlabBufferManager.java new file mode 100644 index 0000000000000..09b419d59e351 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/SlabBufferManager.java @@ -0,0 +1,856 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.io; + +import com.oracle.coherence.common.collections.WeakIdentityHashMap; + +import com.oracle.coherence.common.io.BufferManagers; + +import java.lang.ref.WeakReference; + +import java.nio.ByteBuffer; + +import java.util.BitSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import com.oracle.coherence.common.util.SafeClock; + +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import java.util.logging.Level; + + +/** + * The SlabBufferManager is a BufferManager which allocates buffers in large slabs which are then sliced into smaller + * ByteBuffers. + * + * @author mf 2013.03.18 + */ +public class SlabBufferManager + extends SegmentedBufferManager + { + // ----- constructors --------------------------------------------------- + + /** + * Creates a SlabBufferManager. + * + * @param allocator the BufferAllocator to use to fill the pool + * @param cbBufferMin the minimum buffer size + * @param cbMax the total amount of memory to pool + */ + public SlabBufferManager(BufferAllocator allocator, int cbBufferMin, long cbMax) + { + super(allocator, cbBufferMin, cbMax); + } + + /** + * Creates a SlabBufferManager. + * + * @param allocator the BufferAllocator to use to fill the pool + * @param cbMax the total amount of memory to pool + */ + @SuppressWarnings("unused") + public SlabBufferManager(BufferAllocator allocator, long cbMax) + { + super(allocator, cbMax); + } + + /** + * Creates a SlabBufferManager. + * + * @param allocator the BufferAllocator to use to fill the pool + * @param cSegments the number of segments + * @param cbSegment the maximum number bytes each segment will consume + * @param cbBufferMin the smallest buffer size + * @param nGrowthFactor the segment growth factor + */ + @SuppressWarnings("unused") + public SlabBufferManager(BufferAllocator allocator, int cSegments, long cbSegment, int cbBufferMin, + int nGrowthFactor) + { + super(allocator, cSegments, cbSegment, cbBufferMin, nGrowthFactor); + } + + + + // ----- inner class: SlabSegment --------------------------------------- + + @Override + protected Segment allocateSegment(int cbBuffer, int cBuffers) + { + return new SlabSegment(cbBuffer, cBuffers); + } + + /** + * The SlabSegment is a Segment which manages generations as slabs rather then as + * individual buffers. + */ + protected class SlabSegment + extends Segment + { + /** + * Construct a pool for buffers of the specified size. + * + * @param cbBuffer the size of the individual ByteBuffers + * @param cBuffers the number of ByteBuffers for the pool + */ + protected SlabSegment(int cbBuffer, int cBuffers) + { + super(cbBuffer, cBuffers); + for (int i = 0, c = f_aSlabs.length; i < c; ++i) + { + f_aSlabs[i] = instantiateSlab(encodeGeneration(i)); + } + } + + @Override + protected int getAcquired() + { + return Math.max(0, super.getAcquired() - (int) f_cReclaimed.get()); + } + + @Override + protected boolean allocateGenerationBuffers(int nGen, int cbBuffer) + { + // since we're growing perhaps we've leaked; ensure lower generations + for (int i = 0; i < nGen; ++i) + { + if (f_aSlabs[i].ensure()) + { + f_cReclaimed.addAndGet(getGenerationSize()); + LOGGER.log(Level.WARNING, getName() + " replenished leaked segment '" + getBufferSize() + + "' slab for generation " + i + + "; set the com.oracle.common.io.BufferManagers.checked system property to true to help identify leak suspects"); + } + } + + // TODO: check unpooled slabs? + + Slab slab = f_aSlabs[nGen]; + if (!slab.ensure()) + { + // revive generation that was never fully released + LOGGER.log(Level.FINE, getName() + " reviving segment '" + getBufferSize() + "' slab for generation " + + nGen); + } + // else; new memory allocation + + return true; + } + + + @Override + protected ByteBuffer allocateNonPooledBuffer() + { + synchronized (f_mapBufSlabUnpooled) + { + // drop any leaked slabs + Slab slab = m_slabUnpooledHead; + while (slab != null) + { + Slab slabPrev = slab.m_slabPrev; + if (slab.isLeaked()) + { + removeSlabFromList(slab); + + if (!slab.f_afOutstanding.isEmpty()) + { + LOGGER.log(Level.WARNING, getName() + " detected leaked segment '" + getBufferSize() + + "' slab for generation " + GEN_ID_UNPOOLED + "; " + slab.f_afOutstanding.cardinality() + + " of " + f_cBufferGen + " buffers were leaked" + + "; set the com.oracle.common.io.BufferManagers.checked system property to true to help identify leak suspects"); + } + // else; just a GC + } + + slab = slabPrev; // progress towards tail + } + + ByteBuffer buff = null; + do + { + // attempt to just revive a partially released slab, head has the most reclaimable buffers + Slab slabRevive = m_slabUnpooledHead; + if (slabRevive != null) + { + if (!slabRevive.ensure()) + { + // revive generation that was never fully released + LOGGER.log(Level.FINE, getName() + " reviving segment '" + getBufferSize() + "' slab for generation " + + GEN_ID_UNPOOLED); + } + + buff = f_stackBuf.pop(); + if (buff != null) + { + return buff; + } + } + + // allocate an unpooled slab + f_cNonPooledAllocations.incrementAndGet(); + instantiateSlab(f_cbUnpooledBuffer).ensure(); // note: ensure inserts its buffers into the segment + + LOGGER.log(Level.FINE, getName() + " allocating unpooled slab for segment '" + getBufferSize()); + + buff = f_stackBuf.pop(); + } + while (buff == null); // very unlikely to require more then one pass + + return buff; + } + } + + @Override + protected boolean isShrinkable() + { + long ldtNow = SafeClock.INSTANCE.getSafeTimeMillis(); + if (m_ldtShrinkableEval < ldtNow - CLEANUP_FREQUENCY_MILLIS) + { + m_ldtShrinkableEval = ldtNow; + GcTracker[] aInfo = m_aGcInfo; + if (aInfo != null) + { + for (GcTracker info : aInfo) + { + if (info != null && !info.hasGCd()) + { + // this collector hasn't collected since our last allocation, avoid dropping for now + // the intent here is to ultimately avoid creating new garbage until our old garbage + // has been cleaned up. This is important when dealing with DirectByteBuffers as + // they don't cause GC pressure and thus may go along time without being GC'd which + // can cause a large off-heap memory leak. + return m_fShrinkable = false; + } + } + } + + return m_fShrinkable = true; + } + + return m_fShrinkable; + } + + @Override + public void dropBuffer(ByteBuffer buffer) + { + int nGen = decodeGeneration(buffer.capacity()); + + if (nGen == GEN_ID_UNPOOLED) + { + synchronized (f_mapBufSlabUnpooled) + { + Slab slab = f_mapBufSlabUnpooled.remove(buffer); + if (slab == null) + { + // the application has apparently has now, or at some point in the past given us a buffer which + // we didn't produce. We have no evidence that it is the callers fault though, so we don't throw + LOGGER.log(Level.WARNING, getName() + " detected release of unknown ByteBuffer for segment '" + + getBufferSize() + "' generation " + nGen + + "; set the com.oracle.common.io.BufferManagers.checked system property to true to help identify suspects"); + // return and let GC deal with it + } + else if (slab.releaseSlice(buffer)) + { + f_cNonPooledAllocations.decrementAndGet(); + } + } + } + else + { + f_aSlabs[nGen].releaseSlice(buffer); + } + } + + @Override + public String toString() + { + int cUnpooledSlabs; + synchronized (f_mapBufSlabUnpooled) + { + cUnpooledSlabs = new HashSet<>(f_mapBufSlabUnpooled.values()).size(); + } + return super.toString() + '/' + (f_cGeneration.get() + 1 + cUnpooledSlabs); + } + + // ----- helper methods --------------------------------------------- + + /** + * Remove the specified {@link Slab} from the linked list of buffers. + * + * @param slabToRemove the slab to remove + */ + protected void removeSlabFromList(Slab slabToRemove) + { + Slab slabNext = slabToRemove.m_slabNext; + Slab slabPrev = slabToRemove.m_slabPrev; + + if (slabNext == null) + { + m_slabUnpooledHead = slabPrev; + } + else + { + slabNext.m_slabPrev = slabPrev; + } + + if (slabPrev == null) + { + m_slabUnpooledTail = slabNext; + } + else + { + slabPrev.m_slabNext = slabNext; + } + + slabToRemove.m_slabNext = slabToRemove.m_slabPrev = null; + } + + /** + * Instantiate a new unallocated slab. + * + * @param cbBuffer the size of each buffer within the slab + * + * @return the slab + */ + protected Slab instantiateSlab(int cbBuffer) + { + return new Slab(cbBuffer); + } + + // ----- inner class: Slab ------------------------------------------ + + /** + * Slab represents a single Buffer allocation. + */ + protected class Slab + implements Comparable + { + /** + * Allocate a slab + * + * @param cbBuffer the size of each buffer + */ + public Slab(int cbBuffer) + { + f_cbSlice = cbBuffer; + } + + @Override + public String toString() + { + return "Slab " + System.identityHashCode(this) + ", bufSize " + f_cbSlice + ", leaked " + isLeaked() + ", outstanding " + f_afOutstanding.cardinality() + "/" + getGenerationSize(); + } + + /** + * Return true iff the segment is retaining no actual memory. + * + * @return true iff the segment is retaining no actual memory. + */ + public synchronized boolean isLeaked() + { + return m_weakBuf == null || m_weakBuf.get() == null; + } + + /** + * Return the buffer associated with this slab, or null if none is present + * + * @return the buffer + */ + public ByteBuffer getSlabBuffer() + { + WeakReference weakBuf = m_weakBuf; + return weakBuf == null ? null : m_weakBuf.get(); + } + + /** + * Release a slice back to the slab. + * + * @param buffer the buffer + * + * @return true the slab was destroyed as part of this operation + */ + public synchronized boolean releaseSlice(ByteBuffer buffer) + { + ByteBuffer bufSlab = getSlabBuffer(); + int nPosition = getSlicePosition(buffer); + if (nPosition >= 0 && f_afOutstanding.get(nPosition) && bufSlab != null) + { + // retain knowledge of which slices have been released so we can restore them later + // if we need to grow before we've released all slices. Note we don't hold refs to + // the released slices as that would largely prevent us from being able to detect a + // leaked segment + f_afOutstanding.clear(nPosition); + + if (f_afOutstanding.isEmpty()) + { + // we've reclaimed the entire generation (slab); release it now + LOGGER.log(Level.FINE, getName() + " releasing segment '" + getBufferSize() + + "' slab for generation " + decodeGeneration(f_cbSlice)); + getAllocator().release(bufSlab); + m_weakBuf.clear(); + m_weakBuf = null; + + if (f_cbSlice == f_cbUnpooledBuffer) + { + removeSlabFromList(this); + } + return true; + } + else if (f_cbSlice == f_cbUnpooledBuffer) + { + // one additional slice is revivable, sort order may change + Slab slabNext = m_slabNext; + + if (slabNext != null && compareTo(slabNext) < 0) + { + // swap positions with next, note we can only move forward + Slab slabPrev = m_slabPrev; + + m_slabNext = slabNext.m_slabNext; + if (m_slabNext != null) + { + m_slabNext.m_slabPrev = this; + } + + slabNext.m_slabPrev = slabPrev; + slabNext.m_slabNext = this; + + m_slabPrev = slabNext; + + if (slabPrev == null) + { + m_slabUnpooledTail = slabNext; + } + else + { + slabPrev.m_slabNext = slabNext; + } + + if (m_slabUnpooledHead == slabNext) + { + m_slabUnpooledHead = this; + } + } + } + } + else + { + // double release of slice + LOGGER.log(Level.WARNING, getName() + " double release of '" + getBufferSize() + + "' buffer in generation " + decodeGeneration(f_cbSlice) + + "; set the com.oracle.common.io.BufferManagers.checked system property to true to help identify suspects"); + } + + return false; + } + + /** + * Ensure that the slab has not been leaked, and reallocate if it has. + * + * @return true iff ensure performed an allocation + */ + public synchronized boolean ensure() + { + int cBufGen = getGenerationSize(); + ByteBuffer bufSlab = getSlabBuffer(); + boolean fAlloc = bufSlab == null; + + if (fAlloc) + { + bufSlab = getAllocator().allocate(f_cbSlice * cBufGen); + m_weakBuf = new WeakReference<>(bufSlab); + f_afOutstanding.clear(); // outstanding were leaked and GC'd + + // record GC counts at the time of the most recent allocation + List listGc = ManagementFactory.getGarbageCollectorMXBeans(); + GarbageCollectorMXBean[] aGc = listGc.toArray(new GarbageCollectorMXBean[listGc.size()]); + GcTracker[] aInfo = new GcTracker[aGc.length]; + for (int i = 0; i < aGc.length && aGc[i] != null; ++i) + { + aInfo[i] = new GcTracker(aGc[i]); + } + m_aGcInfo = aInfo; + } + else if (f_afOutstanding.cardinality() == cBufGen) + { + // none of the buffers were ever released + return false; + } + // else; there are some released buffers we can reslice and return + + if (f_cbSlice == f_cbUnpooledBuffer) + { + Slab slabNext = m_slabNext; + Slab slabPrev = m_slabPrev; + + // unlink self (likely from head) + if (slabNext != null) + { + slabNext.m_slabPrev = slabPrev; + } + else if (m_slabUnpooledHead == this) + { + m_slabUnpooledHead = slabPrev; + } + // else; not in chain + + if (slabPrev != null) + { + slabPrev.m_slabNext = slabNext; + } + else if (m_slabUnpooledTail == this) + { + m_slabUnpooledTail = slabNext; + } + // else; not in chain + + // (re)link at the tail + Slab slabTail = m_slabUnpooledTail; + + m_slabUnpooledTail = this; + m_slabNext = slabTail; + m_slabPrev = null; + + if (slabTail == null) + { + m_slabUnpooledHead = this; + } + else + { + slabTail.m_slabPrev = this; + } + } + + // slice and return + for (int i = f_afOutstanding.nextClearBit(0); i < cBufGen; i = f_afOutstanding.nextClearBit(i)) + { + int ofStart = i * f_cbSlice; + int ofEnd = ofStart + f_cbSlice; + bufSlab.limit(ofEnd).position(ofStart); + + ByteBuffer bufSlice = bufSlab.slice(); + + if (f_cbSlice == f_cbUnpooledBuffer) + { + // caller must hold sync on f_mapBufSlabUnpooled + f_mapBufSlabUnpooled.put(bufSlice, this); + } + + f_afOutstanding.set(i); + f_stackBuf.push(bufSlice); + } + + bufSlab.clear(); // leave pos & limit at their extremes so we can safely invoke getSlicePosition later + + return fAlloc; + } + + /** + * Return the position (measured in slices) of this specified slice within the slab. The slice must be available + * for writes, i.e. not in use by the application. + * + * @param buffSlice the slice + * + * @return the position, or -1 if not found + */ + protected synchronized int getSlicePosition(ByteBuffer buffSlice) + { + // determine starting offset of buffer withing slab + ByteBuffer buffSlab = getSlabBuffer(); + if (buffSlab == null) + { + return -1; + } + else if (buffSlice.hasArray() && buffSlab.hasArray()) + { + return buffSlice.array() == buffSlab.array() ? buffSlice.arrayOffset() / buffSlice.capacity() : -1; + } + else if (buffSlice.isDirect()) + { + int cbSlice = buffSlice.capacity(); // all slices from this slab are the same size + ThreadLocalRandom tlRandom = ThreadLocalRandom.current(); + long lThreadId = Thread.currentThread().getId(); + long lRand; + + // obtain and write a random value to the buffer and verify the value is present within the slab + do + { + lRand = tlRandom.nextLong(); + } + while (lRand == 0); + buffSlice.putLong(0, lRand); // slice is released so we can write to it + + // probe outstanding slab positions for the random write + for (int nPosition = f_afOutstanding.nextSetBit(0); + nPosition >= 0; + nPosition = f_afOutstanding.nextSetBit(nPosition + 1)) + { + int ofSlab = nPosition * cbSlice; + if (buffSlab.getLong(ofSlab) == lRand) + { + // looks like a match, verify with an additional non-random write + long lUnique = (((long) nPosition) << 32) | lThreadId; + buffSlice.putLong(0, lUnique); + if (buffSlab.getLong(ofSlab) == lUnique) + { + // "confirmed" + if (BufferManagers.ZERO_ON_RELEASE) + { + buffSlice.putLong(0, 0); // finish by zeroing out the value + } + return nPosition; + } + + buffSlice.putLong(0, lRand); + } + } + + return -1; + } + + throw new IllegalStateException(); + } + + @Override + public int compareTo(Slab that) + { + return f_afOutstanding.cardinality() - that.f_afOutstanding.cardinality(); + } + + /** + * The size of each slice. + */ + protected final int f_cbSlice; + + /** + * BitSet indicating which slices are outstanding + */ + protected final BitSet f_afOutstanding = new BitSet(getGenerationSize()); + + /** + * WeakReference tracking the slab ByteBuffer + */ + protected WeakReference m_weakBuf; + + /** + * Next largest unpooled slab. + */ + protected Slab m_slabNext; + + /** + * Next smallest unpooled slab. + */ + protected Slab m_slabPrev; + } + + // ----- inner class: GcTracker ---------------------------------------- + + /** + * GcTracker provides a mechanism to detect if a GC has occurred in the supplied + * collector since the tracker was created. + */ + protected class GcTracker + { + GcTracker(GarbageCollectorMXBean bean) + { + this.f_cGcLast = bean.getCollectionCount(); + this.f_bean = bean; + } + + public boolean hasGCd() + { + return f_cGcLast != f_bean.getCollectionCount(); + } + + private final long f_cGcLast; + private final GarbageCollectorMXBean f_bean; + } + + // ----- data members ----------------------------------------------- + + /** + * Array of slabs for poolable generations. + */ + protected final Slab[] f_aSlabs = new Slab[GEN_ID_UNPOOLED]; + + /** + * Map from unpooled ByteBuffers to their corresponding unpooled slab. + * + * Weak to allow leaks in the application to not cause true memory leaks. + * + * All access must be synchronized on the map. + */ + protected final Map f_mapBufSlabUnpooled = new WeakIdentityHashMap<>(); + + /** + * The slab with the most available reclaimable slices + */ + protected Slab m_slabUnpooledHead; + + /** + * The slab with the least available reclaimable slices + */ + protected Slab m_slabUnpooledTail; + + /** + * The number of reclaimed buffers. + */ + protected final AtomicLong f_cReclaimed = new AtomicLong(); + + /** + * GC counter info as of last slab allocation. + */ + private volatile GcTracker[] m_aGcInfo; + + /** + * The cached shrinkable state. + */ + private volatile boolean m_fShrinkable; + + /** + * The time at which m_fShrinkable was last calculated + */ + private long m_ldtShrinkableEval; + } + + // ----- inner class: DirectBufferAllocator ----------------------------- + + /** + * A buffer allocator that allow the associated memory of the direct buffer + * to be released explicitly. + */ + public static class DirectBufferAllocator + implements BufferAllocator + { + + @Override + public ByteBuffer allocate(int cb) + { + return ByteBuffer.allocateDirect(cb); + } + + @Override + public void release(ByteBuffer buff) + { + if (!buff.isDirect()) + { + throw new IllegalArgumentException(); + } + + clean(buff); + } + + // ----- helper methods --------------------------------------------- + + /** + * Release the associated memory of the specified direct byte buffer. + * + * @param buff the buffer to be cleaned + */ + protected static void clean(ByteBuffer buff) + { + Consumer consumerClean = m_cleaner; + if (consumerClean == null) + { + synchronized (DirectBufferAllocator.class) + { + if ((consumerClean = m_cleaner) == null) + { + Consumer consumerTmp = REFLECTION_CLEANER; + try + { + consumerTmp.accept(buff); + } + catch (Throwable t) + { + consumerTmp = VOID_CLEANER; + } + finally + { + m_cleaner = consumerTmp; + } + } + } + } + + if (consumerClean != null) + { + consumerClean.accept(buff); + } + } + + // ----- data members------------------------------------------------ + + /** + * A dummy ByteBuffer cleaner. + */ + private static final Consumer VOID_CLEANER = bb -> {}; + + /** + * The direct byte buffer cleaner via reflection. + */ + private static final Consumer REFLECTION_CLEANER = new Consumer() + { + @Override + public void accept(ByteBuffer buff) + { + try + { + Method methClean = m_methClean; + Object oCleaner; + + if (methClean == null) + { + Method methCleaner = m_methCleaner = buff.getClass().getDeclaredMethod("cleaner", null); + methCleaner.setAccessible(true); + oCleaner = methCleaner.invoke(buff); + + m_methClean = methClean = oCleaner.getClass().getDeclaredMethod("clean", null); + } + else + { + oCleaner = m_methCleaner.invoke(buff); + } + + methClean.invoke(oCleaner, null); + } + catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) + { + m_methCleaner = m_methClean = null; + + throw new RuntimeException(e); + } + } + + /** + * The Cleaner method. + */ + private Method m_methCleaner; + + /** + * The clean method + */ + private Method m_methClean; + }; + + /** + * The cached cleaner for direct byte buffer. + */ + private static Consumer m_cleaner; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/WrapperBufferManager.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/WrapperBufferManager.java new file mode 100644 index 0000000000000..b3f381e54c4ee --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/io/WrapperBufferManager.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.io; + +import com.oracle.coherence.common.io.BufferManager; + +import java.nio.ByteBuffer; + +/** + * WrapperBufferManager is a BufferManager wrapper. + * + * @author coh 2011.12.08 + */ +public abstract class WrapperBufferManager + implements BufferManager + { + // ----- constructors--------------------------------------------- + + /** + * Create a new WrapperBufferManager. + * + * @param delegate the BufferManager to delegate to + */ + public WrapperBufferManager(BufferManager delegate) + { + f_delegate = delegate; + } + + // ----- Object interface --------------------------------- + + @Override + public String toString() + { + return String.format("%s(delegate=%s)", getClass().getSimpleName(), f_delegate); + } + + // ----- BufferManager interface --------------------------------- + + @Override + public void dispose() + { + f_delegate.dispose(); + } + + @Override + public ByteBuffer acquire(int cbMin) + { + return f_delegate.acquire(cbMin); + } + + @Override + public ByteBuffer acquirePref(int cbPref) + { + return f_delegate.acquirePref(cbPref); + } + + @Override + public ByteBuffer acquireSum(int cbSum) + { + return f_delegate.acquireSum(cbSum); + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer truncate(ByteBuffer buff) + { + return f_delegate.truncate(buff); + } + + @Override + public void release(ByteBuffer buff) + { + f_delegate.release(buff); + } + + @Override + public long getCapacity() + { + return f_delegate.getCapacity(); + } + + // ----- data members -------------------------------------------------- + + /** + * The BufferManager to delegate to. + */ + protected final BufferManager f_delegate; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/AbstractStickySelectionService.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/AbstractStickySelectionService.java new file mode 100644 index 0000000000000..5bb56a5eb3d05 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/AbstractStickySelectionService.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + + +import com.oracle.coherence.common.base.Factory; +import com.oracle.coherence.common.net.SelectionService; + +import java.io.IOException; +import java.nio.channels.SelectableChannel; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * An abstract base class for for a delegating load-balancing SelectionService. + * + * @author mf 2013.08.16 + */ +public abstract class AbstractStickySelectionService + implements SelectionService + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a AbstractStickySelectionService which delegates to the provided + * SelectionServices. + * + * @param cServices the maximum number of SelectionServices to use + * @param factory the factory for producing SelectionServices + */ + public AbstractStickySelectionService(int cServices, + Factory factory) + { + if (factory == null) + { + throw new IllegalArgumentException("factory cannot be null"); + } + + // While we could lazily create the services, that would then require + // memory barriers and more complex checks in register(). Considering + // that the "common" case would be to use a ResumableSelectionService, + // which is in itself lazy, creating these up front is quite + // reasonable, and efficient. + SelectionService[] aSvc = new SelectionService[cServices]; + for (int i = 0; i < cServices; ++i) + { + aSvc[i] = factory.create(); + } + f_aServices = aSvc; + } + + + // ----- SelectionService interface ------------------------------------ + + /** + * {@inheritDoc} + */ + public void register(SelectableChannel chan, Handler handler) + throws IOException + { + ensureService(chan).register(chan, handler); + if (handler == null) + { + f_mapService.remove(chan); // leave it in f_mapPerm + } + } + + /** + * {@inheritDoc} + */ + public void invoke(SelectableChannel chan, Runnable runnable, long cMillis) + throws IOException + { + ensureService(chan).invoke(chan, runnable, cMillis); + } + + @Override + public void associate(SelectableChannel chanParent, SelectableChannel chanChild) + throws IOException + { + synchronized (this) + { + SelectionService svcOld = chanParent == null + ? f_mapPerm.remove(chanChild) + : f_mapPerm.put(chanChild, ensureService(chanParent)); + if (svcOld != null) + { + // previously associated child, try to cleanup + svcOld.associate(null, chanChild); + } + f_mapService.clear(); // see ensureService; also destroys any prior cached mapping for the child + } + } + + /** + * {@inheritDoc} + */ + public void shutdown() + { + for (SelectionService svc : f_aServices) + { + svc.shutdown(); + } + f_mapService.clear(); + f_mapPerm.clear(); + } + + + // ----- helpers: ------------------------------------------------------- + + /** + * Find the SelectionService to be used for the specified channel. + * + * @param chan the channel + * + * @return the service + */ + protected SelectionService ensureService(SelectableChannel chan) + { + SelectionService svc = f_mapService.get(chan); + + if (svc == null) + { + synchronized (this) + { + svc = f_mapService.get(chan); + if (svc == null) + { + svc = f_mapPerm.get(chan); + if (svc == null) + { + svc = selectService(chan); + f_mapPerm.put(chan, svc); + + // to help allow channels to be eventually GC'd we clear out the (non-weak) cache each time + // we add a channel to the service. It will be re-cached on first access, see below. + // Alternatively we could just remove closed connections from the map, but this would prevent + // unregistered yet open connections from being GC'd/closed. + f_mapService.clear(); + } + else + { + f_mapService.put(chan, svc); + } + } + } + } + + return svc; + } + + /** + * Select a child SelectionService for which the specified channel will be permanently assigned. + * + * @param chan the channel + * + * @return the service + */ + abstract protected SelectionService selectService(SelectableChannel chan); + + + // ----- data members --------------------------------------------------- + + /** + * An array of child SelectionServices. + */ + protected final SelectionService[] f_aServices; + + /** + * Mapping of registered Channels to their corresponding SelectionService. The mapping is only removed upon + * service shutdown, GC of the channel, or explicit (re)association. This ensures that subsequent if we have + * cycles of registration, unregistration re-registration that we won't potentially have two underlying services + * concurrently managing the same channel. + */ + protected final WeakHashMap f_mapPerm = new WeakHashMap(); + + /** + * Cached mapping of registered Channels to their corresponding SelectionService. + */ + protected final Map f_mapService = new ConcurrentHashMap(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/DemultiplexedSocketProvider.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/DemultiplexedSocketProvider.java new file mode 100644 index 0000000000000..cc4644c8c5eb8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/DemultiplexedSocketProvider.java @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + +import java.io.IOException; + +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; + +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import com.oracle.coherence.common.net.InetSocketAddress32; +import com.oracle.coherence.common.net.InetSocketProvider; + +/** + * DemultiplexedSocketProvider is a bridge Socket provider that allows to use + * MultiplexedSocketProvider without converting the socket addresses into + * InetSocketAddres32. DemultiplexedSocketProvider converts the socket addresses + * with the passed in subport. The ServerSocketChannel created using the DemultiplexedSocketProvider + * will bind the server socket to the given subport. Similarly client socket, + * will try to connect to remote peer on the given subport. + * + * @author bb 2011.12.06 + */ +public class DemultiplexedSocketProvider + extends InetSocketProvider + { + /** + * Construct a DemultiplexedSocketProvider + * + * @param delegate the underlying MultiplexedSocketProvider + * @param subport subport to use to convert regular InetSocketAddresses, or -1 for standard port + */ + public DemultiplexedSocketProvider(MultiplexedSocketProvider delegate, int subport) + { + if (subport == 0 || subport < -1 || subport > 65535) + { + // cannot create a DemultiplexedSocketProvider with ephemeral subport + // since it will never be accessible outside of DemultiplexedSocketProvider + throw new IllegalArgumentException("Illegal subport: "+subport); + } + m_nSubport = subport; + m_delegate = delegate; + } + + /** + * {@inheritDoc} + */ + @Override + public ServerSocketChannel openServerSocketChannel() + throws IOException + { + return new DemultiplexedServerSocketChannel(m_delegate); + } + + /** + * {@inheritDoc} + */ + @Override + public ServerSocket openServerSocket() + throws IOException + { + return openServerSocketChannel().socket(); + } + + /** + * {@inheritDoc} + */ + @Override + public SocketChannel openSocketChannel() + throws IOException + { + return new DemultiplexedSocketChannel(m_delegate); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket openSocket() + throws IOException + { + return new DemultiplexedSocket((MultiplexedSocketProvider.MultiplexedSocket) m_delegate.openSocket()); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return "DemultiplexedSocketProvider(" + m_delegate + ")"; + } + + /** + * Return the delegate SocketProvider. + * + * @return the delegate SocketProvider + */ + public MultiplexedSocketProvider getDelegate() + { + return m_delegate; + } + + /** + * DemultiplexedServerSocketChannel extends MultiplexedServerSocketChannel + * so that it can covert all the SocketAddresses into InetSocketAddress32. + * It doesn't simply wrap the channel, as it needs to be compatible with + * the delegate's selectors + */ + protected class DemultiplexedServerSocketChannel + extends MultiplexedSocketProvider.MultiplexedServerSocketChannel + { + /** + * Create a DemultiplexedServerSocketChannel + * + * @param provider Underlying MultiplexedSocketProvider + * + * @throws IOException if I/O error occurs + */ + public DemultiplexedServerSocketChannel(MultiplexedSocketProvider provider) + throws IOException + { + super(provider); + } + + /** + * {@inheritDoc} + */ + @Override + public SocketChannel accept() + throws IOException + { + MultiplexedSocketProvider.MultiplexedSocketChannel channel = + (MultiplexedSocketProvider.MultiplexedSocketChannel) super.accept(); + return (channel == null) + ? channel + : new DemultiplexedSocketChannel(channel); + } + + @Override + protected ServerSocket createServerSocket() + throws IOException + { + return new WrapperServerSocket(super.createServerSocket()) + { + @Override + public ServerSocketChannel getChannel() + { + return DemultiplexedServerSocketChannel.this; + } + + @Override + public void bind(SocketAddress endpoint) + throws IOException + { + bind(endpoint, 0); + } + + @Override + public void bind(SocketAddress endpoint, int backlog) + throws IOException + { + super.bind(getMultiplexedSocketAddress(endpoint), backlog); + m_address = endpoint; + } + + @Override + public int getLocalPort() + { + return MultiplexedSocketProvider.getBasePort(super.getLocalPort()); + } + + @Override + public SocketAddress getLocalSocketAddress() + { + SocketAddress addr = m_address; + if (addr == null) + { + addr = m_address = MultiplexedSocketProvider.getTransportAddress( + (InetSocketAddress32) super.getLocalSocketAddress()); + } + return addr; + } + + /** + * Local address + */ + protected SocketAddress m_address; + }; + } + + @Override + protected ServerSocketChannel getChannel() + { + return this; + } + } + + /** + * DemultiplexedSocketChannel extends MultiplexedSocketChannel + * so that it can convert all the SocketAddresses into InetSocketAddress32 + */ + protected class DemultiplexedSocketChannel + extends MultiplexedSocketProvider.MultiplexedSocketChannel + { + /** + * Create a DemultiplexedSocketChannel + * + * @param delegate underlying SocketChannel + */ + public DemultiplexedSocketChannel(MultiplexedSocketProvider.MultiplexedSocketChannel delegate) + { + // A DemultiplexedSocketChannel is also a MultiplexedSocketChannel. It works on top of the plain delegate socket channel + // from the passed in MultiplexedSocketChannel. + super(delegate.delegate(), delegate.m_addrLocal, delegate.m_bufHeaderIn); + if (delegate.getClass() != MultiplexedSocketProvider.MultiplexedSocketChannel.class) + { + throw new IllegalArgumentException("DemultiplexedSocketChannel can only work with MultiplexedSocketChannel"); + } + } + + /** + * Create a DemultiplexedSocketChannel + * + * @param provider underlying MultiplexedSocketProvider + * + * @throws IOException if an IO error occurs + */ + public DemultiplexedSocketChannel(MultiplexedSocketProvider provider) + throws IOException + { + super(provider.getDependencies().getDelegateProvider().openSocketChannel(), /*addrLocal*/ null, /*bufHeader*/ null); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean connect(SocketAddress remote) + throws IOException + { + return super.connect(getMultiplexedSocketAddress(remote)); + } + + @Override + protected Socket wrapSocket(Socket socket) + { + return new DemultiplexedSocket((MultiplexedSocketProvider.MultiplexedSocket) super.wrapSocket(socket)) + { + @Override + public SocketChannel getChannel() + { + return DemultiplexedSocketChannel.this; + } + }; + } + } + + /** + * DemultiplexedSocket wraps MultiplexedSocket + * so that it can convert all the SocketAddresses into InetSocketAddress32 + */ + protected class DemultiplexedSocket + extends WrapperSocket + { + /** + * Create a DemultiplexedSocket. + * + * @param delegate delegate socket + */ + public DemultiplexedSocket(MultiplexedSocketProvider.MultiplexedSocket delegate) + { + super(delegate); + } + + /** + * {@inheritDoc} + */ + @Override + public void bind(SocketAddress addr) + throws IOException + { + super.bind(getMultiplexedSocketAddress(addr)); + m_addressLocal = addr; + } + + /** + * {@inheritDoc} + */ + @Override + public void connect(SocketAddress addr) + throws IOException + { + connect(addr, 0); + } + + /** + * {@inheritDoc} + */ + @Override + public void connect(SocketAddress addr, int cMillis) + throws IOException + { + super.connect(getMultiplexedSocketAddress(addr), cMillis); + m_addressRemote = addr; + } + + /** + * {@inheritDoc} + */ + public int getLocalPort() + { + return MultiplexedSocketProvider.getBasePort(super.getLocalPort()); + } + + /** + * {@inheritDoc} + */ + @Override + public SocketAddress getLocalSocketAddress() + { + SocketAddress addrLocal = m_addressLocal; + if (addrLocal == null) + { + addrLocal = m_addressLocal = MultiplexedSocketProvider.getTransportAddress( + (InetSocketAddress32) super.getLocalSocketAddress()); + } + return addrLocal; + } + + /** + * {@inheritDoc} + */ + @Override + public int getPort() + { + return MultiplexedSocketProvider.getBasePort(super.getPort()); + } + + /** + * {@inheritDoc} + */ + @Override + public SocketAddress getRemoteSocketAddress() + { + SocketAddress addrRemote = m_addressRemote; + if (addrRemote == null) + { + addrRemote = m_addressRemote = MultiplexedSocketProvider.getTransportAddress( + (InetSocketAddress32) super.getRemoteSocketAddress()); + } + return addrRemote; + } + + /** + * Local address + */ + protected SocketAddress m_addressLocal; + + /** + * Peer address + */ + protected SocketAddress m_addressRemote; + } + + /** + * Helper method to convert InetSocketAddress to InetSockeAddress32 for use + * by MultiplexedSocketProvider + * + * @param address InetSocketAddress to convert + * @return InetSocketAddress32 + */ + protected InetSocketAddress32 getMultiplexedSocketAddress(SocketAddress address) + { + if (address == null) + { + return null; + } + else if (address instanceof InetSocketAddress) + { + InetSocketAddress inetAddr = (InetSocketAddress) address; + return new InetSocketAddress32(inetAddr.getAddress(), + MultiplexedSocketProvider.getPort(inetAddr.getPort(), m_nSubport)); + } + throw new IllegalArgumentException("Invalid socket address type: "+address); + } + + /** + * Underlying MultiplexedSocketProvider + */ + protected final MultiplexedSocketProvider m_delegate; + + /** + * Subport to be used by this DemultiplexedSocketProvider + */ + protected final int m_nSubport; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/HashSelectionService.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/HashSelectionService.java new file mode 100644 index 0000000000000..e7d8a3d02d0cc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/HashSelectionService.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + + +import com.oracle.coherence.common.base.Factory; +import com.oracle.coherence.common.base.Hasher; +import com.oracle.coherence.common.net.SelectionService; + +import java.nio.channels.SelectableChannel; + + +/** + * The HashSelectionService partitions channel registrations over a number + * of child SelectionServices for the purposes of load balancing. + * + * @author mf 2010.11.23 + */ +public class HashSelectionService + extends AbstractStickySelectionService + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a HashSelectionService which delegates to the provided + * SelectionServices. + * + * @param cServices the maximum number of SelectionServices to use + * @param factory the factory for producing SelectionServices + */ + public HashSelectionService(int cServices, Factory factory) + { + super(cServices, factory); + } + + @Override + protected SelectionService selectService(SelectableChannel chan) + { + return f_aServices[Hasher.mod(chan.hashCode(), f_aServices.length)]; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/InterruptibleChannels.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/InterruptibleChannels.java new file mode 100644 index 0000000000000..805e2103a71e0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/InterruptibleChannels.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + +import java.io.IOException; + +import java.lang.reflect.Field; + +import java.nio.channels.spi.AbstractInterruptibleChannel; + + +/** + * Set of InterruptibleChannels related helper methods. + * + * @author mf 2013.11.04 + */ +public final class InterruptibleChannels + { + /** + * Attempt to change a channel's interruptibility. + * + * Note: this feature is deprecated as it no longer with Java 9. + * + * @param chan the channel + * @param fInterruptible true if interrupts are to be supported; false otherwise + * + * @return true iff the channel was successfully updated + * + * @deprecated + */ + public static boolean setInterruptible(AbstractInterruptibleChannel chan, boolean fInterruptible) + { + // This is a "work-around for" + // JDK-6908931 : (so) Thread.interrupt impact on thread doing non-blocking I/O operation is not clear + if (s_fieldInterruptor == null || (!fInterruptible && s_interruptibleNoOp == null)) + { + return false; + } + + try + { + while (chan instanceof WrapperSocketChannel) // special awareness of wrapper channels + { + chan = ((WrapperSocketChannel) chan).f_delegate; + } + + s_fieldInterruptor.set(chan, fInterruptible + ? null + : s_interruptibleNoOp); + + return true; + } + catch (Throwable e) + { + return false; + } + } + + // ----- inner class: InterruptibleFetcher ------------------------------ + + /** + * Helper hack to fetch an implementation of sun.nio.ch.Interruptible + */ + private static class InterruptibleFetcher + extends AbstractInterruptibleChannel + { + /** + * Retrieve the internal interruptible and close the channel. + * + * @return the interruptible + */ + Object fetch() + { + try + { + begin(); // initializes the interruptor field + return s_fieldInterruptor.get(this); + } + catch (Throwable e) {} + finally + { + try + { + end(true); + close(); + } + catch (Throwable e) {} + } + + return null; + } + + @Override + protected void implCloseChannel() + throws IOException + { + } + } + + // ----- constants ------------------------------------------------------ + + /** + * Field for accessing an AbstractInterruptibleChannel's private interruptor. + */ + private static final Field s_fieldInterruptor; + + /** + * An InterruptibleNoOp, obtained from a pre-closed channel. + */ + private static final Object s_interruptibleNoOp; + + static + { + Field field = null; + try + { + if (System.getProperty("java.vm.specification.version").startsWith("1.")) + { + Field fieldTmp = AbstractInterruptibleChannel.class.getDeclaredField("interruptor"); + fieldTmp.setAccessible(true); + field = fieldTmp; + } + // else; we're on at least 9, it is inaccessable unless --permit-illegal-access is specified and + // that generates warnings. The primary usecase for this functionality is TMB, and thankfully TMB will do + // reconnects on unexpected closes, so we simply drop this feature + } + catch (Throwable e) {} + + s_fieldInterruptor = field; + s_interruptibleNoOp = field == null ? null : new InterruptibleFetcher().fetch(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/MultiProviderSelectionService.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/MultiProviderSelectionService.java new file mode 100644 index 0000000000000..5affff4f13a3d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/MultiProviderSelectionService.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + + +import com.oracle.coherence.common.base.Factory; +import com.oracle.coherence.common.net.SelectionService; + +import java.io.IOException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + + +/** + * The MultiProviderSelectionService supports registration of channels from + * multiple SelectorProviders. + * + * @author mf 2010.11.23 + */ +public class MultiProviderSelectionService + implements SelectionService + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a MultiProviderSelectionService. + * + * @param factory the factory for producing SelectionServices + */ + public MultiProviderSelectionService(Factory factory) + { + m_factory = factory; + } + + + // ----- SelectionService interface ------------------------------------- + + /** + * {@inheritDoc} + */ + public void register(SelectableChannel chan, Handler handler) + throws IOException + { + getSelectionService(chan).register(chan, handler); + } + + /** + * {@inheritDoc} + */ + public void invoke(SelectableChannel chan, Runnable runnable, long cMillis) + throws IOException + { + getSelectionService(chan).invoke(chan, runnable, cMillis); + } + + @Override + public void associate(SelectableChannel chanParent, SelectableChannel chanChild) + throws IOException + { + // Note: parent (if non-null) and child must be from the same provider + if (chanParent != null && chanParent.provider() != chanChild.provider()) + { + throw new IllegalArgumentException("parent and child must use the same SelectorProvider"); + } + getSelectionService(chanChild).associate(chanParent, chanChild); + } + + /** + * {@inheritDoc} + */ + public void shutdown() + { + ConcurrentMap map = m_mapServices; + m_mapServices = null; + + for (SelectionService svc : map.values()) + { + svc.shutdown(); + } + } + + protected SelectionService getSelectionService(SelectableChannel chan) + { + ConcurrentMap map = m_mapServices; + if (map == null) + { + throw new IllegalStateException("the service has been shutdown"); + } + + if (chan == null) + { + throw new IllegalArgumentException("null channel"); + } + + SelectorProvider provider = chan.provider(); + SelectionService svc = map.get(provider); + if (svc == null) + { + SelectionService svcNew = m_factory.create(); + svc = map.putIfAbsent(provider, svcNew); + if (svc == null) + { + // our put succeeded, start a daemon thread to handle this + // new service + svc = svcNew; + } + else + { + // another thread just did the same, use it's value + svcNew.shutdown(); + } + } + return svc; + } + + + // ----- data members --------------------------------------------------- + + /** + * The factory to use when creating new SelectionServices. + */ + protected Factory m_factory; + + /** + * Map of SelectorProvider to SelectionService. + */ + protected ConcurrentMap m_mapServices + = new ConcurrentHashMap(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/MultiplexedSocketProvider.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/MultiplexedSocketProvider.java new file mode 100644 index 0000000000000..3c49687b963a0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/MultiplexedSocketProvider.java @@ -0,0 +1,3333 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.collections.UnmodifiableSetCollection; +import com.oracle.coherence.common.io.Buffers; +import com.oracle.coherence.common.net.InetAddressComparator; +import com.oracle.coherence.common.net.InetAddresses; +import com.oracle.coherence.common.net.InetSocketAddress32; +import com.oracle.coherence.common.net.SafeSelectionHandler; +import com.oracle.coherence.common.net.SelectionService; +import com.oracle.coherence.common.net.SelectionServices; +import com.oracle.coherence.common.net.SocketProvider; +import com.oracle.coherence.common.net.TcpSocketProvider; +import com.oracle.coherence.common.util.Duration; + +import java.net.ProtocolFamily; +import java.net.SocketOption; +import java.net.StandardSocketOptions; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.nio.channels.spi.AbstractSelector; +import java.nio.channels.spi.AbstractSelectableChannel; +import java.nio.channels.spi.SelectorProvider; + +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.IOException; +import java.io.OutputStream; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import java.util.concurrent.*; + +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.LogRecord; + + +/** + * MultiplexedSocketProvider produces a family of sockets which utilize + * {@link InetSocketAddress32 extended port} values to allow for multiplexing + * of sockets. The primary benefit of multiplexed server sockets is that it + * allows a process to host many logical server sockets over a single inet + * port, thus reducing firewall configuration. + *

+ * This SocketProvider makes use of {@link InetSocketAddress32} based addresses. + * The break down of the port range is as follows. + *

    + *
  • + * 0x00000000 .. 0x0000FFFF maps directly to standard inet based addresses, + * i.e. non-multiplexed + *
  • + *
  • + * 0x00010000 .. 0xFFFFFFFF maps to multiplexed sockets + *
  • + *
+ *

+ * Non-multiplexed sockets produced by this provider can communicate with + * standard sockets, while multiplexed sockets can only communicate with other + * multiplexed socket instances. + *

+ * In the case of a multiplexed socket port, the upper sixteen bits represent + * the actual inet port binding, and the lower sixteen bits represents the + * sub-port or channel within the actual socket. The encoding of these two + * 16-bit port values into a 32-bit int is as follows: + *

nPort = ~(nPortBase <<< 16 | nPortSub)

+ * A sub-port of 0 represents a sub-ephemeral address. With the above encoding a + * 32-bit port value of -1 thus represents a "double" ephemeral port which means + * an ephemeral sub-port on an ephemeral port. + *

+ * As a matter of convenience {@link #resolveAddress} supports resolving ports in a + * dot delimited format, for instance 80.1 represents inet port 80, and sub-port 1. + *

+ * Client sockets do not support local port bindings for ports above 0xFFFF. + *

+ * Sub-ports in the range of 1..1023 inclusive are considered to be "well known" + * and are not for general use. To make use of a sub port in this range, one + * must be associated with a service and recorded in the {@link WellKnownSubPorts} + * enum. Generally applications will either use ephemeral sub-ports, or sub-ports + * of 1024 or greater. + *

+ * The MultiplexedSocketProvider also supports bindings to non-local NAT addresses. + * Specifically if there is a NAT address which routes to a local address, it is allowable + * to bind to NAT address using this provider. + *

+ * + * @see InetSocketAddress32 + * + * @author mf 2010.12.27 + */ +public class MultiplexedSocketProvider + implements SocketProvider + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a MultiplexedSocketProvider. + * + * @param deps the provider dependencies. + */ + public MultiplexedSocketProvider(Dependencies deps) + { + m_dependencies = copyDependencies(deps).validate(); + } + + + // ----- MultiplexedSocketProvider interface ---------------------------- + + /** + * Return the provider's dependencies. + * + * @return the provider's dependencies + */ + public Dependencies getDependencies() + { + return m_dependencies; + } + + // ----- SocketProvider interface --------------------------------------- + + /** + * {@inheritDoc} + *

+ * The address may be specified as is host:port, or host:base.sub where + * the former uses an explicit 32 bit port, and the latter represents + * the port as two 16 bit pairs from which a 32 bit port will be computed. + */ + @Override + public SocketAddress resolveAddress(String sAddr) + { + int ofPort; + int ofAddrEnd; + + if (sAddr.startsWith("[")) + { + // ipv6 formatted: [addr]:port + ofAddrEnd = sAddr.lastIndexOf("]:") + 1; + if (ofAddrEnd == 2) // 2 for [] + { + throw new IllegalArgumentException("address does not contain an hostname or ip"); + } + else if (ofAddrEnd == -1) + { + throw new IllegalArgumentException("address does not contain a port"); + } + + ofPort = ofAddrEnd + 1; + } + else + { + // ipv4 formatted: addr:port + ofAddrEnd = sAddr.lastIndexOf(':'); + if (ofAddrEnd == 0) + { + throw new IllegalArgumentException("address does not contain an hostname of ip"); + } + else if (ofAddrEnd == -1) + { + throw new IllegalArgumentException("address does not contain a port"); + } + + ofPort = ofAddrEnd + 1; + } + + String sHost = sAddr.substring(0, ofAddrEnd); + int ofPortSub = sAddr.indexOf('.', ofPort); + if (ofPortSub == -1) + { + int nPort = Integer.parseInt(sAddr.substring(ofPort)); + + return new InetSocketAddress32(sHost, nPort); + } + else + { + int nPortBase = Integer.parseInt(sAddr.substring(ofPort, ofPortSub)); + int nPortSub = Integer.parseInt(sAddr.substring(ofPortSub + 1)); + + return new InetSocketAddress32(sHost, getPort(nPortBase, nPortSub)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getAddressString(Socket socket) + { + InetAddress addr = socket.getInetAddress(); + if (addr == null) + { + return null; + } + + // use host ip address + String sAddr = addr.getHostAddress(); + if (sAddr.contains(":")) + { + // ipv6 representation + sAddr = "[" + sAddr + "]"; + } + + int nPort = socket.getPort(); + return sAddr + ":" + (isPortExtended(nPort) + ? getBasePort(nPort) + "." + getSubPort(nPort) + : nPort); + } + + /** + * {@inheritDoc} + */ + @Override + public String getAddressString(ServerSocket socket) + { + InetAddress addr = socket.getInetAddress(); + boolean fAny = addr.isAnyLocalAddress(); + String sAddr; + if (fAny) + { + // replace wildcard address with local hostname as this + try + { + addr = InetAddress.getLocalHost(); + } + catch (UnknownHostException e) {} + + sAddr = addr.getHostName(); // Note: using addr.getCanonicalHostname generally returns an IP, thus defeating the purpose + } + else + { + // use host ip address + sAddr = addr.getHostAddress(); + } + + if (sAddr.contains(":")) + { + // ipv6 representation + sAddr = "[" + sAddr + "]"; + } + + int nPort = socket.getLocalPort(); + return sAddr + ":" + (isPortExtended(nPort) + ? getBasePort(nPort) + "." + getSubPort(nPort) + : nPort); + } + + + /** + * {@inheritDoc} + */ + @Override + public ServerSocketChannel openServerSocketChannel() + throws IOException + { + return new MultiplexedServerSocketChannel(this); + } + + /** + * {@inheritDoc} + */ + @Override + public ServerSocket openServerSocket() + throws IOException + { + return openServerSocketChannel().socket(); + } + + /** + * {@inheritDoc} + */ + @Override + public SocketChannel openSocketChannel() + throws IOException + { + return new MultiplexedSocketChannel(getDependencies() + .getDelegateProvider().openSocketChannel(), /*addrLocal*/ null, /*bufHeader*/ null); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket openSocket() + throws IOException + { + return new MultiplexedSocket(getDependencies().getDelegateProvider() + .openSocket(), /*channel*/ null); + } + + /** + * {@inheritDoc} + */ + @Override + public SocketProvider getDelegate() + { + return getDependencies().getDelegateProvider(); + } + + // ----- inner class: MultiplexedSelectorProvider ----------------------- + + /** + * MultiplexedSelectorProvider provides a SelectorProvider interface to + * this SocketProvider. + */ + protected static class MultiplexedSelectorProvider + extends SelectorProvider + { + // ----- constructors ------------------------------------------- + + public MultiplexedSelectorProvider(SelectorProvider delegate) + { + m_delegate = delegate; + } + + public MultiplexedSelectorProvider(SocketProvider providerSocket) + throws IOException + { + ServerSocketChannel chan = providerSocket.openServerSocketChannel(); + m_delegate = chan.provider(); + chan.close(); + } + + // ----- SelectorProvider interface ----------------------------- + + @Override + public DatagramChannel openDatagramChannel() + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public DatagramChannel openDatagramChannel(ProtocolFamily family) + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public Pipe openPipe() + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public AbstractSelector openSelector() + throws IOException + { + return new MultiplexedSelector(m_delegate.openSelector(), this); + } + + @Override + public ServerSocketChannel openServerSocketChannel() + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public SocketChannel openSocketChannel() + throws IOException + { + throw new UnsupportedOperationException(); + } + + + // ----- Object interface --------------------------------------- + + @Override + public boolean equals(Object o) + { + if (o == this) + { + return true; + } + else if (o instanceof MultiplexedSelectorProvider) + { + return m_delegate.equals(((MultiplexedSelectorProvider) o).m_delegate); + } + else + { + return false; + } + } + + @Override + public int hashCode() + { + return m_delegate.hashCode(); + } + + // ----- data members ------------------------------------------- + + /** + * The delegate SelectorProvider. + */ + protected SelectorProvider m_delegate; + } + + + // ----- inner class: MultiplexedSelector ------------------------------- + + /** + * MultiplexedSelector is a Selector implementation for use with + * Sockets produced by this provider. + */ + protected static class MultiplexedSelector + extends WrapperSelector + { + // ----- constructors ------------------------------------------- + + /** + * Construct a MultiplexedSelector. + * + * @param delegate the delegate selector + * @param provider the corresponding SelectorProvider + * + * @throws IOException if an I/O error occurs + */ + protected MultiplexedSelector(Selector delegate, SelectorProvider provider) + throws IOException + { + super(delegate, provider); + m_setKeysRO = new UnmodifiableSetCollection( + m_setKeys, super.keys()); + } + + // ----- Selector interface ------------------------------------- + + @Override + public Set keys() + { + Set setKeys = m_setKeysRO; + ensureOpen(); + return setKeys; + } + + @Override + public Set selectedKeys() + { + Set setReady = m_setReady; + ensureOpen(); + return setReady; + } + + @Override + public int selectNow() + throws IOException + { + return select(-1); + } + + @Override + public synchronized int select(long cMillisTimeout) + throws IOException + { + Set setKeys = m_setKeys; + Set setKeysRO = m_setKeysRO; + Set setReady = m_setReady; + Set setPend = m_setPending; + Selector delegate = m_delegate; + + ensureOpen(); + + synchronized (setKeysRO) // required by Selector doc + { + synchronized (setReady) // required by Selector doc + { + int cNew; + synchronized (setPend) + { + if (!m_setCancelled.isEmpty()) + { + // we need to clear the canceled set, and ensure that any + // canceled keys are removed from setKeys and setPend. Since + // the canceled set can be updated concurrently, we must do + // this a key at a time rather then via setCancel.clear(), this + // way we ensure we've don't leave orphans in setKeys or setPend + for (Iterator iter = m_setCancelled.iterator(); + iter.hasNext(); ) + { + SelectionKey key = iter.next(); + setKeys.remove(key); + setPend.remove(key); + iter.remove(); + } + } + cNew = processPendingKeys(); + } + + try + { + if (cMillisTimeout >= 0 && cNew == 0) + { + Blocking.select(delegate, cMillisTimeout); + synchronized (setPend) + { + cNew = processPendingKeys(); + } + } + else + { + delegate.selectNow(); + } + } + finally + { + cleanupCancelledKeys(); + } + + Set setClient = delegate.selectedKeys(); + for (SelectionKey key : setClient) + { + if (setReady.add((SelectionKey) key.attachment())) + { + ++cNew; + } + } + setClient.clear(); + + return cNew; + } + } + } + + @Override + public int select() + throws IOException + { + return select(0); + } + + + // ----- AbstractSelector interface ----------------------------- + + @Override + protected void implCloseSelector() throws IOException + { + Set setKeys = m_setKeys; + Set setKeysRO = m_setKeysRO; + Set setReady = m_setReady; + super.implCloseSelector(); //let the thread in select return and release the locks + synchronized(this) + { + synchronized (setKeysRO) + { + synchronized (setReady) + { + synchronized (setKeys) + { + for (Iterator iter = setKeys.iterator(); + iter.hasNext(); ) + { + SelectionKey key = iter.next(); + if (key.isValid()) + { + key.cancel(); + } + iter.remove(); + } + } + } + } + } + } + + @Override + protected SelectionKey register(final AbstractSelectableChannel ch, int ops, + Object att) + { + Set setKeys = m_setKeys; + + synchronized (setKeys) // ensures key can't be returned from selector without being visible in setKeys() + { + SelectionKey key; + if (ch instanceof MultiplexedServerSocketChannel) + { + key = ((MultiplexedServerSocketChannel) ch).makeKey(this); + + key.interestOps(ops); + key.attach(att); + + setKeys.add(key); + } + else + { + key = super.register(ch, ops, att); + } + + if (((MultiplexedChannel) ch).readyOps() != 0) + { + addPendingKey(key); + } + + return key; + } + } + + // ----- Object interface -------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return "MultiplexedSelector(" + m_delegate + ")"; + } + + // ----- helper methods ---------------------------------------- + + /** + * Ensure that the Selector is open. + */ + protected void ensureOpen() + { + if (!isOpen()) + { + throw new ClosedSelectorException(); + } + } + + /** + * Add the ready SelectionKey to the pending set. + * + * @param key the ready key + */ + protected void addPendingKey(SelectionKey key) + { + Set set = m_setPending; + synchronized (set) + { + set.add(key); + } + } + + /** + * Update Selector ready set with ready keys added to the pending set + * from the underlying Selector. + * + * @return number of new keys added to the readySet. + */ + protected int processPendingKeys() + { + Set setPend = m_setPending; + Set setReady = m_setReady; + int cNew = 0; + + if (!setPend.isEmpty()) + { + for (Iterator iter = setPend.iterator(); iter.hasNext();) + { + SelectionKey key = iter.next(); + int nOps = ((MultiplexedChannel) key.channel()).readyOps(); + if (nOps == 0) + { + iter.remove(); + } + else if ((key.interestOps() & nOps) != 0 && setReady.add(key)) + { + ++cNew; + } + } + } + return cNew; + } + + // ----- data members ------------------------------------------- + + /** + * The registered key set. + */ + protected Set m_setKeys = new HashSet(); + + /** + * The exposed registered key set. + */ + protected Set m_setKeysRO; + + /** + * The ready key set. + */ + protected Set m_setReady = new HashSet(); + + /** + * The pending ready key set. + */ + protected Set m_setPending = new HashSet(); + + /** + * The cancelled key set. + */ + protected Set m_setCancelled = Collections.newSetFromMap(new ConcurrentHashMap()); + } + + + // ----- inner class: MultiplexedChannel -------------------------------- + + /** + * Common interface implemented by all channels serviced by this provider. + */ + protected interface MultiplexedChannel + { + /** + * Return the operations that can be satisfied by already buffered data. + * + * @return the operations that can be satisfied by already buffered data. + */ + public int readyOps(); + } + + + // ----- inner class: MultiplexedServerSocketChannel -------------------- + + /** + * MultiplexedServerSocketChannel is an implementation of a + * ServerSocketChannel which shares an underlying ServerSocketChannel with + * a number of other MultiplexedServerSocketChannels. + */ + protected static class MultiplexedServerSocketChannel + extends ServerSocketChannel + implements MultiplexedChannel + { + // ----- constructors ------------------------------------------- + + public MultiplexedServerSocketChannel(MultiplexedSocketProvider provider) + throws IOException + { + super(new MultiplexedSelectorProvider(provider.getDependencies().getDelegateProvider())); + + m_provider = provider; + m_socket = createServerSocket(); + } + + protected ServerSocket createServerSocket() + throws IOException + { + return new ServerSocket() + { + @Override + public void bind(SocketAddress endpoint) + throws IOException + { + bind(endpoint, 0); + } + + @Override + public void bind(SocketAddress endpoint, int backlog) + throws IOException + { + if (isBound()) + { + throw new IOException("already bound"); + } + else if (endpoint == null || endpoint instanceof InetSocketAddress32) + { + InetSocketAddress32 addr = (InetSocketAddress32) endpoint; + + m_address = m_provider.open(addr, MultiplexedServerSocketChannel.this, null); + } + else + { + throw new IllegalArgumentException("unsupported SocketAddress type"); + } + } + + @Override + public InetAddress getInetAddress() + { + return m_address.getAddress(); + } + + @Override + public int getLocalPort() + { + return m_address.getPort(); + } + + @Override + public SocketAddress getLocalSocketAddress() + { + return m_address; + } + + @Override + public ServerSocketChannel getChannel() + { + return MultiplexedServerSocketChannel.this; + } + + @Override + public boolean isBound() + { + return m_address != null; + } + + @Override + public boolean isClosed() + { + return m_fClosed; + } + + @Override + public void close() + throws IOException + { + BlockingQueue queue = m_queue; + boolean fClosed; + synchronized (queue) + { + super.close(); // just to free underlying FD + + fClosed = m_fClosed; + m_fClosed = true; + } + + if (!fClosed) + { + InetSocketAddress32 addr = (InetSocketAddress32) getLocalSocketAddress(); + if (addr != null) + { + m_provider.close(addr); + } + + for (ServerSelectionKey key = m_keyHead; key != null; key = key.m_next) + { + key.cancel(); + } + + // close all pending client channels in the queue, otherwise they will + // be leaked and could live a long time before bing GC'd and the other + // side sees the socket get closed + for (SocketChannel chan = queue.poll(); chan != null; chan = queue.poll()) + { + try + { + chan.close(); + } + catch (IOException ioe) {} + } + // Its possible that other threads might be doing blocking accept on the + // ServerSocketChannel. This marker channel allows them to unblock and + // identify that the ServerChannel has been closed. + queue.add(SERVER_CHANNEL_CLOSED_MARKER); + } + } + + @Override + public Socket accept() + throws IOException + { + ServerSocketChannel chanServer = getChannel(); + if (chanServer.isBlocking()) + { + return chanServer.accept().socket(); + } + throw new IllegalBlockingModeException(); + } + + @Override + public String toString() + { + if (isBound()) + { + return "MultiplexedServerSocket[addr=" + m_address.getAddress() + + ",port=" + MultiplexedSocketProvider.getBasePort(m_address.getPort()) + + ",subport=" + MultiplexedSocketProvider.getSubPort(m_address.getPort()) + "]"; + } + return "MultiplexedServerSocket[unbound]"; + } + + InetSocketAddress32 m_address; + }; + } + + + protected ServerSocketChannel getChannel() + { + return this; + } + + // ----- ServerSocketChannel interface -------------------------- + + @Override + public ServerSocket socket() + { + return m_socket; + } + + @Override + public SocketChannel accept() + throws IOException + { + if (!socket().isBound()) + { + throw new IOException("not bound"); + } + + try + { + BlockingQueue queue = m_queue; + SocketChannel chan; + if (isBlocking()) + { + long cMillis = socket().getSoTimeout(); + chan = cMillis == 0 + ? queue.take() + : queue.poll(cMillis, TimeUnit.MILLISECONDS); + } + else + { + chan = queue.poll(); + } + + if (chan == null) + { + return chan; + } + else if (chan == SERVER_CHANNEL_CLOSED_MARKER) + { + //requeue to unblock other threads that might be blocked in accept() + queue.add(SERVER_CHANNEL_CLOSED_MARKER); + throw new IOException("socket closed"); + } + else + { + try + { + chan.socket().setReceiveBufferSize(socket().getReceiveBufferSize()); + } + catch (IOException e) + { + // apparently the accepted socket has been closed; while we could try to + // pull another from the queue that would increase the complexity of this + // method, especially in the case of a timed wait. So instead we return + // a closed socket, which is perfectly allowable + } + } + + return chan; + } + catch (InterruptedException e) + { + throw new InterruptedIOException(e.getMessage()); + } + } + + @Override + protected void implCloseSelectableChannel() + throws IOException + { + socket().close(); + } + + @Override + protected void implConfigureBlocking(boolean block) + throws IOException + { + // nothing to do + } + + @Override + public ServerSocketChannel bind(SocketAddress local, int backlog) + throws IOException + { + m_socket.bind(local, backlog); + return this; + } + + @Override + public ServerSocketChannel setOption(SocketOption name, T value) + throws IOException + { + if (name == StandardSocketOptions.SO_RCVBUF) + { + socket().setReceiveBufferSize(((Integer) value).intValue()); + } + else if (name == StandardSocketOptions.SO_REUSEADDR) + { + socket().setReuseAddress(((Boolean) value).booleanValue()); + } + else + { + throw new UnsupportedOperationException(name.toString()); + } + + return this; + } + + // ----- NetworkChannel methods -------------------------------- + + @Override + public SocketAddress getLocalAddress() + throws IOException + { + return socket().getLocalSocketAddress(); + } + + @Override + public T getOption(SocketOption name) + throws IOException + { + if (name == StandardSocketOptions.SO_RCVBUF) + { + return (T) Integer.valueOf(socket().getReceiveBufferSize()); + } + else if (name == StandardSocketOptions.SO_REUSEADDR) + { + return (T) Boolean.valueOf(socket().getReuseAddress()); + } + else + { + throw new UnsupportedOperationException(name.toString()); + } + } + + @Override + public Set> supportedOptions() + { + return SERVER_OPTIONS; + } + + // ----- helpers ------------------------------------------------ + + /** + * Add an SocketChannel to the accept queue + * + * @param chan the channel + * + * @return true iff the channel was queued + */ + protected boolean add(SocketChannel chan) + { + BlockingQueue queue = m_queue; + synchronized (queue) + { + if (!m_fClosed && queue.offer(chan)) + { + for (ServerSelectionKey key = m_keyHead; key != null; key = key.m_next) + { + MultiplexedSelector selectorMultiplexed = (MultiplexedSelector) key.selector(); + selectorMultiplexed.addPendingKey(key); + selectorMultiplexed.wakeup(); + } + return true; + } + } + return false; + } + + /** + * Register this channel with specified selector. + * + * @param selector the selector to register with + * + * @return the selection key + */ + protected SelectionKey makeKey(Selector selector) + { + ServerSelectionKey key = new ServerSelectionKey(selector); + synchronized (m_queue) + { + key.m_next = m_keyHead; + m_keyHead = key; + } + return key; + } + + @Override + public int readyOps() + { + return m_queue.isEmpty() ? 0 : SelectionKey.OP_ACCEPT; + } + + @Override + public String toString() + { + return "MultiplexedServerSocketChannel(" + socket() + ")"; + } + + + // ---- inner class: ServerSelectionKey ------------------------- + + class ServerSelectionKey + extends SelectionKey + { + ServerSelectionKey(Selector selector) + { + m_selector = selector; + } + + @Override + public SelectableChannel channel() + { + return getChannel(); + } + + @Override + public Selector selector() + { + return m_selector; + } + + @Override + public boolean isValid() + { + return !m_fCanceled; + } + + @Override + public void cancel() + { + m_fCanceled = true; + ((MultiplexedSelector) m_selector).m_setCancelled.add(this); + + synchronized (MultiplexedServerSocketChannel.this.m_queue) + { + ServerSelectionKey keyLast = null; + for (ServerSelectionKey key = m_keyHead; + key != null; key = key.m_next) + { + if (key == this) + { + if (keyLast == null) + { + m_keyHead = m_next; + } + else + { + keyLast.m_next = m_next; + } + break; + } + keyLast = key; + } + } + } + + @Override + public int interestOps() + { + ensureValid(); + return m_nInterest; + } + + @Override + public SelectionKey interestOps(int ops) + { + ensureValid(); + if (ops == 0 || ops == OP_ACCEPT) + { + m_nInterest = ops; + return this; + } + throw new IllegalArgumentException(); + } + + @Override + public int readyOps() + { + // The only valid op for ServerSelectionKey is OP_ACCEPT. + // The key will be returned in the MultiplexedSelector selected set + // only if there is a pending socket for the MultiplexedServerSocketChannel + // In that case, OP_ACCEPT is the readyOps. + return OP_ACCEPT; + } + + protected void ensureValid() + { + if (m_fCanceled) + { + throw new CancelledKeyException(); + } + } + + /** + * The associated selector. + */ + protected Selector m_selector; + + /** + * True iff the key has been canceled. + */ + protected boolean m_fCanceled; + + /** + * The registered interest set. + */ + protected int m_nInterest; + + /** + * The next key associated with this channel. + */ + protected ServerSelectionKey m_next; + } + + // ----- data members ------------------------------------------- + + /** + * The queue of ready client channels. + */ + protected final BlockingQueue m_queue = new LinkedBlockingDeque(); + + /** + * The ServerSocket representation of this channel. + */ + protected ServerSocket m_socket; + + /** + * The head of the SelectionKey linked-list. + */ + protected ServerSelectionKey m_keyHead; + + /** + * MultiplexedSocketProvider associated with this ServerSocketChannel + */ + protected MultiplexedSocketProvider m_provider; + + /** + * Flag indicating if the channel is closed. + */ + protected boolean m_fClosed; + + /** + * Special SocketChannel that is added to the client channel queue to + * indicate that this ServerSocketChannel is closed. This is needed to + * unblock threads waiting for client sockets in accept(). + */ + protected static final SocketChannel SERVER_CHANNEL_CLOSED_MARKER = new SocketChannel(null) + { + @Override + public Socket socket() + { + return null; + } + + @Override + public boolean isConnected() + { + return false; + } + + @Override + public boolean isConnectionPending() + { + return false; + } + + @Override + public boolean connect(SocketAddress remote) + throws IOException + { + return false; + } + + @Override + public boolean finishConnect() + throws IOException + { + return false; + } + + @Override + public int read(ByteBuffer dst) + throws IOException + { + return 0; + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) + throws IOException + { + return 0; + } + + @Override + public int write(ByteBuffer src) + throws IOException + { + return 0; + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException + { + return 0; + } + + @Override + protected void implCloseSelectableChannel() + throws IOException + { + } + + @Override + protected void implConfigureBlocking(boolean block) + throws IOException + { + } + + @Override + public SocketChannel bind(SocketAddress local) + throws IOException + { + return null; + } + + @Override + public SocketChannel setOption(SocketOption name, T value) + throws IOException + { + return null; + } + + @Override + public SocketChannel shutdownInput() + throws IOException + { + return null; + } + + @Override + public SocketChannel shutdownOutput() + throws IOException + { + return null; + } + + @Override + public SocketAddress getRemoteAddress() + throws IOException + { + return null; + } + + @Override + public SocketAddress getLocalAddress() + throws IOException + { + return null; + } + + @Override + public T getOption(SocketOption name) + throws IOException + { + return null; + } + + @Override + public Set> supportedOptions() + { + return null; + } + }; + } + + + // ----- inner class: MultiplexedSocketChannel -------------------------- + + /** + * MultiplexedSocketChannel + */ + protected static class MultiplexedSocketChannel extends WrapperSocketChannel implements MultiplexedChannel + { + // ----- constructors ------------------------------------------- + + /** + * Create a MultiplexedSocketChannel for an incoming SocketChannel + * + * @param delegate incoming socket channel delegate + * @param addrLocal the local address associated with this socket, or null + * @param bufIn initial bytes to be returned from read calls before socket data, or null + */ + public MultiplexedSocketChannel(SocketChannel delegate, SocketAddress addrLocal, ByteBuffer bufIn) + { + super(delegate, new MultiplexedSelectorProvider(delegate.provider())); + + // Note: addrLocal is generally an InetSocketAddress32, the only exception is in the case that + // we are accepting from a DemultiplexedServerSocket in which case the supplied address is just + // an InetSocketAddress, which we ignore + m_addrLocal = addrLocal instanceof InetSocketAddress32 + ? (InetSocketAddress32) addrLocal + : null; + m_bufHeaderIn = bufIn; + } + + + // ----- WrapperSocketChannel methods --------------------------- + + /** + * Return the delegate channel + * + * @return the delegate channel + */ + protected SocketChannel delegate() + { + return f_delegate; + } + + /** + * Produce a wrapper around the specified socket. + * + * @param socket the socket to wrap + * @return the wrapper socket + */ + protected Socket wrapSocket(Socket socket) + { + return new MultiplexedSocket(socket, this); + } + + // ----- SocketChannel methods ---------------------------------- + + @Override + public boolean isConnected() + { + return super.isConnected() && m_bufHeaderOut == null; + } + + @Override + public boolean isConnectionPending() + { + return super.isConnectionPending() || m_bufHeaderOut != null; + } + + @Override + public boolean connect(SocketAddress remote) + throws IOException + { + if (!(remote instanceof InetSocketAddress32)) + { + throw new IllegalArgumentException("unsupported SocketAddress type"); + } + + InetSocketAddress32 addrPeer = (InetSocketAddress32) remote; + if (addrPeer.isUnresolved()) + { + throw new UnresolvedAddressException(); + } + + int nPort = addrPeer.getPort(); + boolean fConnected = super.connect(getTransportAddress(addrPeer)); + + m_addrPeer = addrPeer; + + if (isPortExtended(nPort)) + { + ByteBuffer buf = m_bufHeaderOut = ByteBuffer.allocate(8); + buf.putInt(PROTOCOL_ID).putInt(getSubPort(nPort)).flip(); + + return finishConnect(); + } + else + { + return fConnected; + } + } + + @Override + public boolean finishConnect() + throws IOException + { + boolean fResult = super.finishConnect(); + if (fResult) + { + ByteBuffer buf = m_bufHeaderOut; + if (buf != null) + { + delegate().write(buf); + if (buf.hasRemaining()) + { + LOGGER.log(Level.FINEST, + "{0} physical connection established, {2} of multiplexed" + " protocol header pending for logical connection to be established", + new Object[]{this, buf.remaining()}); + return false; + } + + LOGGER.log(Level.FINEST, "{0} multiplexed connection established", new Object[]{this}); + m_bufHeaderOut = null; + } + } + + return fResult; + } + + + @Override + public int write(ByteBuffer src) + throws IOException + { + return m_bufHeaderOut == null || finishConnect() + ? super.write(src) + : 0; + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException + { + return m_bufHeaderOut == null || finishConnect() + ? super.write(srcs, offset, length) + : 0; + } + + @Override + public int read(ByteBuffer dst) + throws IOException + { + if (m_bufHeaderIn == null) + { + return super.read(dst); + } + else if (socket().isClosed()) + { + throw new ClosedChannelException(); + } + else + { + return readHeader(new ByteBuffer[]{dst}, 0, 1); + } + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) + throws IOException + { + if (m_bufHeaderIn == null) + { + return super.read(dsts, offset, length); + } + else if (socket().isClosed()) + { + throw new ClosedChannelException(); + } + else + { + return readHeader(dsts, offset, length); + } + } + + @Override + protected void implCloseSelectableChannel() + throws IOException + { + super.implCloseSelectableChannel(); + m_bufHeaderIn = null; + } + + @Override + public int readyOps() + { + return m_bufHeaderIn == null + ? 0 + : SelectionKey.OP_READ; + } + + @Override + public String toString() + { + return "MultiplexedSocketChannel(" + socket() + ")"; + } + + // ----- helpers ----------------------------------------------- + + /** + * Transfer as many bytes as possible from the inbound header buffer to the supplied buffer + * + * @param aBufDst the destination buffers + * @param offset the starting offset to write into + * @param length the maximum number of output buffers to access + * + * @return the number of bytes transferred + */ + protected int readHeader(ByteBuffer[] aBufDst, int offset, int length) + { + ByteBuffer bufIn = m_bufHeaderIn; + int cbIn = bufIn.remaining(); + int cb = 0; + for (int i = 0; i < length && cbIn > 0; ++i) + { + ByteBuffer bufOut = aBufDst[offset + i]; + int cbOut = bufOut.remaining(); + + for (int j = 0, c = Math.min(cbOut, cbIn); j < c; ++j) + { + bufOut.put(bufIn.get()); + ++cb; + --cbIn; + } + } + + if (cbIn == 0) + { + m_bufHeaderIn = null; // header has been transferred + } + + return cb; + } + + @Override + public WrapperSelector.WrapperSelectionKey registerInternal(WrapperSelector selector, int ops, Object att) + throws IOException + { + WrapperSelector.WrapperSelectionKey key = new SocketSelectionKey(selector, f_delegate + .register(selector.getDelegate(), 0), att); + key.interestOps(ops); + return key; + } + + /** + * SelectionKey which is aware of the state of the channel's inbound buffer. + */ + protected class SocketSelectionKey extends WrapperSelector.WrapperSelectionKey + { + public SocketSelectionKey(WrapperSelector selector, SelectionKey key, Object att) + { + super(selector, key, att); + } + + @Override + public SelectableChannel channel() + { + return MultiplexedSocketChannel.this; + } + + @Override + public SelectionKey interestOps(int ops) + { + // handle the case where we've connected the underlying socket but not yet pushed the protocol + // header. In such as case we tell the user that we're not connected, thus they express OP_CONNECT + // interest, but what we really need is OP_WRITE interest + boolean fConnecting = m_bufHeaderOut != null && // logical connection is still pending + !delegate().isConnectionPending() && // physical connection is complete + (ops & OP_CONNECT) != 0 && // app expressed interest in OP_CONNECT + (ops & OP_WRITE) == 0; // didn't ask for OP_WRITE + + super.interestOps(fConnecting + ? (ops | OP_WRITE) & ~OP_CONNECT + // add in OP_WRITE and remove OP_CONNECT, since physical connect is complete + : ops); + + m_nOpsInterest = ops; + + return this; + } + + @Override + public int interestOps() + { + return m_nOpsInterest; + } + + @Override + public int readyOps() + { + // TODO: this is a little broken, as it relies on the current value of + // interestOps rather then the value used during the last call into the + // selector. + return (super.readyOps() | MultiplexedSocketChannel.this.readyOps()) & interestOps(); + } + + @Override + public String toString() + { + return "MultiplexedSocketChannel{" + delegate() + "}"; + } + + /** + * The cached interest ops. + */ + protected int m_nOpsInterest; + } + + // ----- data members ------------------------------------------ + + /** + * The peer's address. + */ + protected InetSocketAddress32 m_addrPeer; + + /** + * The socket's local address. + */ + protected InetSocketAddress32 m_addrLocal; + + /** + * The outbound protocol header. + */ + protected ByteBuffer m_bufHeaderOut; + + /** + * The inbound protocol header, or more specifically bytes which were read looking for the header but + * need to be returned from socket read calls before any further socket data. + */ + protected ByteBuffer m_bufHeaderIn; + } + + // ----- inner class: MultiplexedSocket ------------------------------ + + /** + * MultiplexedSocket is an implementation of a Socket that works with + * multiplexed socket addresses represented by InetSocketAddress32. + */ + protected static class MultiplexedSocket extends WrapperSocket + { + // ----- constructors ------------------------------------------- + + /** + * Construct a MultiplexedSocket + * + * @param delegate underlying delegate socket + * @param channel SocketChannel to be associated with the MultiplexedSocket. + * Could be null for an outbound socket. + */ + public MultiplexedSocket(Socket delegate, MultiplexedSocketChannel channel) + { + super(delegate); + f_channel = channel; + if (channel == null) + { + f_out = null; + f_in = null; + } + else + { + f_out = new SocketChannelOutputStream(f_channel); + f_in = new SocketChannelInputStream(f_channel); + } + } + + /** + * {@inheritDoc} + */ + @Override + public SocketChannel getChannel() + { + return f_channel; + } + + /** + * {@inheritDoc} + */ + @Override + public void connect(SocketAddress addr) + throws IOException + { + connect(addr, 0); + } + + /** + * {@inheritDoc} + */ + @Override + public void connect(SocketAddress addr, int cMillis) + throws IOException + { + if (!(addr instanceof InetSocketAddress32)) + { + throw new IllegalArgumentException("unsupported SocketAddress type"); + } + + InetSocketAddress32 addr32 = (InetSocketAddress32) addr; + super.connect(getTransportAddress(addr32), cMillis); + m_addrPeer = addr32; + + if (isPortExtended(addr32.getPort())) + { + ByteBuffer buff = ByteBuffer.allocate(8); + buff.putInt(PROTOCOL_ID).putInt(getSubPort(addr32.getPort())).flip(); + + getOutputStream().write(buff.array()); + getOutputStream().flush(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void bind(SocketAddress addr) + throws IOException + { + if (addr == null || addr instanceof InetSocketAddress32) + { + InetSocketAddress32 addrBind = (InetSocketAddress32) addr; + if (addrBind != null) + { + int nSub = getSubPort(addrBind.getPort()); + if (nSub != 0 && nSub != -1) + { + throw new IOException("cannot bind client sockets to non-zero sub-ports"); + } + } + + if (addrBind == null) + { + super.bind(null); + addrBind = new InetSocketAddress32(super.getLocalAddress(), super.getLocalPort()); + } + else + { + super.bind(getTransportAddress(addrBind)); + } + m_addrLocal = addrBind; + } + else + { + throw new IllegalArgumentException("unsupported SocketAddress type"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public SocketAddress getLocalSocketAddress() + { + InetSocketAddress32 addr = m_addrLocal; + if (addr == null) + { + if (f_channel != null) + { + // in the case of an accepted channel we need to use the server socket's address + addr = m_addrLocal = f_channel.m_addrLocal; + } + + if (addr == null) + { + InetSocketAddress addrReal = (InetSocketAddress) super.getLocalSocketAddress(); + if (addrReal != null) + { + addr = m_addrLocal = new InetSocketAddress32(addrReal.getAddress(), addrReal.getPort()); + } + } + } + return addr; + } + + @Override + public InetAddress getLocalAddress() + { + InetSocketAddress32 addr = (InetSocketAddress32) getLocalSocketAddress(); + return addr == null ? InetAddresses.ADDR_ANY : addr.getAddress(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getLocalPort() + { + InetSocketAddress32 addr = (InetSocketAddress32) getLocalSocketAddress(); + return addr == null + ? -1 + : addr.getPort(); + } + + /** + * {@inheritDoc} + */ + @Override + public SocketAddress getRemoteSocketAddress() + { + InetSocketAddress32 addr = m_addrPeer; + if (addr == null) + { + // compute from delegate socket + if (f_channel != null) + { + // in the case this is a channel associated socket, use its address info + addr = m_addrPeer = f_channel.m_addrPeer; + } + + if (addr == null) + { + // compute from delegate socket + InetSocketAddress addrReal = (InetSocketAddress) super.getRemoteSocketAddress(); + if (addrReal != null) + { + addr = m_addrPeer = new InetSocketAddress32(addrReal.getAddress(), addrReal.getPort()); + } + } + } + return addr; + } + + /** + * {@inheritDoc} + */ + @Override + public int getPort() + { + InetSocketAddress32 addr = (InetSocketAddress32) getRemoteSocketAddress(); + return addr == null + ? 0 + : addr.getPort(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isInputShutdown() + { + MultiplexedSocketChannel chan = f_channel; + return chan == null + ? super.isInputShutdown() + : chan.m_bufHeaderIn == null && super.isInputShutdown(); + } + + /** + * {@inheritDoc} + */ + @Override + public void shutdownInput() + throws IOException + { + super.shutdownInput(); + + MultiplexedSocketChannel chan = f_channel; + if (chan != null) + { + chan.m_bufHeaderIn = null; + } + } + + @Override + public InputStream getInputStream() + throws IOException + { + return f_in == null ? super.getInputStream() : f_in; + } + + @Override + public OutputStream getOutputStream() + throws IOException + { + return f_out == null ? super.getOutputStream() : f_out; + } + + /** + * {@inheritDoc} + */ + @Override + public void close() + throws IOException + { + super.close(); + + MultiplexedSocketChannel chan = f_channel; + if (chan != null) + { + chan.m_bufHeaderIn = null; + } + } + + @Override + public String toString() + { + return "MultiplexedSocket{" + super.toString() + "}"; + } + + /** + * Associated Socket channel. Could be null for an outbound socket. + */ + protected final MultiplexedSocketChannel f_channel; + + /** + * OutuputStream for channel based sockets. + */ + protected final SocketChannelOutputStream f_out; + + /** + * InputStream for channel based sockets. + */ + protected final SocketChannelInputStream f_in; + + /** + * Local address + */ + protected InetSocketAddress32 m_addrLocal; + + /** + * Peer address + */ + protected InetSocketAddress32 m_addrPeer; + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Start listening for connections on the specified address. + * + * @param addr the address to listen on + * @param server the server socket + * @param setBaseExclude the set of base ports to exclude during ephemeral bind, or null + * + * @return the bound address + * + * @throws IOException on an I/O error + */ + protected InetSocketAddress32 open(InetSocketAddress32 addr, MultiplexedServerSocketChannel server, Set setBaseExclude) + throws IOException + { + if (addr == null) + { + addr = new InetSocketAddress32(InetAddress.getLocalHost(), 0); + } + else if (addr.isUnresolved()) + { + throw new SocketException(addr.getHostName()); + } + + InetSocketAddress addrListen = getTransportAddress(addr); + InetAddress addrIp = addr.getAddress(); + int nPort = addr.getPort(); + ServerSocket socket = server.socket(); + + int nPortSub = getSubPort(nPort); + if (nPortSub > 0 && nPortSub <= WELL_KNOWN_SUB_PORT_END) + { + // ensure that the sub port has been registered as a WellKnownSubPort. This is meant to prevent + // accidental usage of the space by apps which have not had a well known port assigned to them. + // this is not a security feature. + boolean fKnown = false; + for (WellKnownSubPorts port : WellKnownSubPorts.values()) + { + if (port.getSubPort() == nPortSub) + { + fKnown = true; + break; + } + } + if (!fKnown) + { + throw new IOException( + "attempt to bind to unassigned sub-port " + nPortSub + " in well known subport range"); + } + } + + // handle NAT addresses + if (addrIp != null && InetAddresses.isNatLocalAddress(addrIp, nPort)) + { + addrIp = InetAddresses.ADDR_ANY; + } + + // handle the case where doing a multiplexed binding to all IPs + if (addrIp != null && addrIp.isAnyLocalAddress()) + { + boolean fEphemeral = getSubPort(nPort) == 0 /*ephemeral sub*/ || getBasePort(nPort) == 0 /*ephemeral base*/; + + // manually add a listener for each IP in the system + Set setAddrAcquired = new HashSet<>(); + Set setIpAll = new TreeSet<>(InetAddressComparator.INSTANCE); // ordered to avoid "deadlock" in case of concurrent process bind + + // COH-12612: the IPv6 zone ID can be mangled on some operating systems (e.g. OS X). + // skip over duplicate addresses to avoid "address already in use" errors. + for (Enumeration iter = NetworkInterface.getNetworkInterfaces(); iter + .hasMoreElements(); ) + { + NetworkInterface nic = iter.nextElement(); + for (Enumeration iterAddr = nic.getInetAddresses(); iterAddr.hasMoreElements(); ) + { + setIpAll.add(iterAddr.nextElement()); + } + + for (Enumeration iterSub = nic.getSubInterfaces(); iterSub.hasMoreElements(); ) + { + for (Enumeration iterAddr = iterSub.nextElement().getInetAddresses(); iterAddr + .hasMoreElements(); ) + { + setIpAll.add(iterAddr.nextElement()); + } + } + } + + try + { + for (Iterator iter = setIpAll.iterator(); iter.hasNext(); ) + { + InetAddress addrNext = iter.next(); + if (!addrNext.isAnyLocalAddress()) // Solaris lists wildcard as one of the local IPs + { + try + { + // re-enter for a single IP + InetSocketAddress32 addrAdd = open(new InetSocketAddress32(addrNext, nPort), server, setBaseExclude); + setAddrAcquired.add(addrAdd); + if (getSubPort(nPort) == 0 || getBasePort(nPort) == 0) + { + // first acquisition from ephemeral; switch to + // actual for the remainder of this pass + nPort = addrAdd.getPort(); + } + } + catch (IOException e) + { + // this may be a non-bindable IP, such as IPv6 temporary IPs rather then a port + // conflict. Validate by seeing by testing for port conflict + + try + { + close(open(new InetSocketAddress32(addrNext, 0), server, setBaseExclude)); + } + catch (IOException e2) + { + // this must be an outbound only IP so we shouldn't have attempted to bind it + continue; + } + + // if we get here then apparently the original port was simply not available + if (fEphemeral && nPort != addr.getPort()) + { + // since we're emulating a ephemeral binding on wildcard it's possible that the + // actual ephemeral allocation on the first real IP isn't actually available + // on one of the subsequent IPs + + // Note: we hold onto the ports we've already acquired, not because we'll use + // them but because we don't want to get them again and keep failing + + nPort = addr.getPort(); // try again + iter = setIpAll.iterator(); // from the beginning + + if (setBaseExclude == null) + { + setBaseExclude = new HashSet<>(); + } + + // avoid this port on future binds, this is necessary in case of a double ephemeral + // binding, we'd endlessly reslect the same base port and fail forever + setBaseExclude.add(getBasePort(nPort)); + } + else + { + throw e; + } + } + } + } + + // clear out the addresses we're going to keep so they don't get freed in the finally + // leaving just those temporary ephemeral bindings which we were not able to acquire + // on all IPs + for (Iterator iter = setAddrAcquired.iterator(); iter.hasNext(); ) + { + InetSocketAddress32 addr32 = iter.next(); + if (addr32.getPort() == nPort) + { + iter.remove(); + } + } + + return fEphemeral + ? new InetSocketAddress32(addr.getAddress(), nPort) + : addr; + } + finally // clean up any bindings we aren't going to keep + { + for (InetSocketAddress32 addrDrop : setAddrAcquired) + { + try + { + close(addrDrop); + } + catch (IOException e) {} + } + } + } + + int nPortEphemeralLow = m_nPortEphemeralLow; + int nPortEphemeralHi = m_nPortEphemeralHi; + + // handle the case where it is a double ephemeral address + ConcurrentMap mapListener = m_mapListener; + if (getBasePort(nPort) == 0 && !mapListener.isEmpty()) + { + // select any multiplexed listener + for (Map.Entry entry : mapListener.entrySet()) + { + int nPortUsed = entry.getKey().getPort(); + if ((setBaseExclude == null || setBaseExclude.contains(nPortUsed)) && + nPortUsed >= nPortEphemeralLow && + nPortUsed <= nPortEphemeralHi && + entry.getKey().getAddress().equals(addrIp)) + { + try + { + // re-enter for each possible port + return open(new InetSocketAddress32(addrIp, getPort(nPortUsed, getSubPort(nPort))), + server, null); + } + catch (IOException e) + { + } + } + } + } + + // handle the basic case for a single IP + ServerSocketChannel chanGarbage = null; + while (true) + { + Listener listener = mapListener.get(addrListen); + + if (listener == null) + { + // try to bind to the address + ServerSocketChannel chanListen = new ListenChannel( + getDependencies().getDelegateProvider().openServerSocketChannel()); + try + { + ServerSocket socketListen = chanListen.socket(); + + socketListen.setReceiveBufferSize(socket.getReceiveBufferSize()); + socketListen.bind(addrListen, getDependencies().getBacklog()); + + if (getBasePort(nPort) == 0) + { + // "learn" the native ephemeral base port range as we bind to real ephemeral ports + int nPortBind = chanListen.socket().getLocalPort(); + if (nPortBind == 65535) + { + // we don't want this ephemeral port since we would not be able to bind sub-ports to it; + // retry while holding it to obtain a different ephemeral base. + chanGarbage = chanListen; // will be closed after next bind + continue; + } + else if (nPortBind < nPortEphemeralLow) + { + m_nPortEphemeralLow = nPortBind; + } + if (nPortBind > nPortEphemeralHi) + { + m_nPortEphemeralHi = nPortBind; + } + + addrListen = (InetSocketAddress) chanListen.socket().getLocalSocketAddress(); + } + + chanListen.configureBlocking(false); + listener = new Listener(chanListen); + + getDependencies().getSelectionService().register(chanListen, listener); + mapListener.put(addrListen, listener); + } + catch (IOException e) + { + chanListen.close(); + if (chanGarbage != null) + { + chanGarbage.close(); + } + throw e; + } + } + + if (chanGarbage != null) + { + chanGarbage.close(); + chanGarbage = null; + } + + synchronized (listener) + { + ServerSocketChannel chanListen = listener.getChannel(); + if (chanListen.isOpen()) + { + return listener.register(getSubPort(nPort), server); + } + } + } + } + + /** + * Stop listening for connections on the specified address. + * + * @param addr the address to stop listening on + * + * @throws IOException on an I/O error + */ + protected void close(InetSocketAddress32 addr) + throws IOException + { + InetAddress addrIp = addr.getAddress(); + int nPort = addr.getPort(); + + if (addrIp != null && addrIp.isAnyLocalAddress()) + { + // manually remove a listener for each IP in the system + for (Enumeration iter = NetworkInterface.getNetworkInterfaces(); iter.hasMoreElements(); ) + { + for (Enumeration iterAddr = iter.nextElement().getInetAddresses(); iterAddr + .hasMoreElements(); ) + { + try + { + InetAddress addrIface = iterAddr.nextElement(); + // COH-17162: uniquely Solaris IPMP permits having an any local address + // within the network interface + if (!addrIface.isAnyLocalAddress()) + { + close(new InetSocketAddress32(addrIface, addr.getPort())); + } + } + catch (IOException e) + { + } + } + } + return; + } + + ConcurrentMap mapListener = m_mapListener; + + SocketAddress addrListen = getTransportAddress(addr); + Listener listener = mapListener.get(addrListen); + + if (listener == null) + { + throw new IOException("not bound"); + } + else + { + synchronized (listener) + { + if (listener.deregister(getSubPort(nPort))) + { + listener.getChannel().close(); + mapListener.remove(addrListen); + } + } + } + } + + // ----- inner class: Listener ------------------------------------------ + + /** + * Listener is a SelectionHandler which waits on the real + * ServerSocketChannel for new connections. + */ + protected class Listener extends SafeSelectionHandler + { + public Listener(ServerSocketChannel chan) + throws IOException + { + super(chan); + } + + @Override + protected int onReadySafe(int nOps) + throws IOException + { + SelectionService svc = getDependencies().getSelectionService(); + SocketChannel chan; + + while ((chan = getChannel().accept()) != null) + { + try + { + chan.configureBlocking(false); + svc.register(chan, new Switcher(chan)); + } + catch (IOException e) + { + // accepted chan is no longer usable + try + { + chan.close(); + } + catch (IOException e2) + { + } + } + } + + return OP_ACCEPT; + } + + @Override + protected int onException(Throwable t) + { + if (getChannel().isOpen()) + { + LogRecord rec = new LogRecord(Level.WARNING, "unhandled exception; continuing"); + rec.setThrown(t); + getDependencies().getLogger().log(rec); + return OP_ACCEPT; + } + // else in shutdown; ignore + return 0; + } + + /** + * Register an acceptor queue with a sub-port on this listener + * + * @param nPortSub the sub-port, or -1 for standard port binding + * @param server the server socket + * + * @throws IOException on registration failure + * + * @return the bound address + */ + protected InetSocketAddress32 register(int nPortSub, MultiplexedServerSocketChannel server) + throws IOException + { + ConcurrentNavigableMap map = m_mapBindings; + + ServerSocket socket = getChannel().socket(); + + if (nPortSub == 0) + { + // first try a "random" port + nPortSub = EPHEMERAL_SUB_PORT_START + (System + .identityHashCode(server) % (0xFFFF - EPHEMERAL_SUB_PORT_START)); + if (map.putIfAbsent(nPortSub, server) == null) + { + // port acquired + return new InetSocketAddress32(socket.getInetAddress(), getPort(socket.getLocalPort(), nPortSub)); + + } + + // scan through and find a free port + nPortSub = 0xFFFF; + for (Iterator iter = m_setEphemeral.iterator(); nPortSub >= EPHEMERAL_SUB_PORT_START; ) + { + int nPortUsed = Math.max(EPHEMERAL_SUB_PORT_START - 1, iter.hasNext() + ? iter.next() + : 0); + + for (; nPortSub > nPortUsed; --nPortSub) + { + // nPortSub is free + if (map.putIfAbsent(nPortSub, server) == null) + { + // port acquired + return new InetSocketAddress32(socket.getInetAddress(), + getPort(socket.getLocalPort(), nPortSub)); + } + } + nPortSub = nPortUsed - 1; + } + + throw new IOException("no available ephemeral sub-ports within base port " + socket.getLocalPort()); + } + else if (map.putIfAbsent(nPortSub, server) != null) + { + throw new IOException("address already in use: " + getAddressString(socket) + '.' + nPortSub); + } + + return new InetSocketAddress32(socket.getInetAddress(), getPort(socket.getLocalPort(), nPortSub)); + } + + /** + * Deregister an acceptor. + * + * @param nPortSub the sub-port + * + * @return true iff this was the last registered port + * + * @throws IOException on deregistration failure + */ + protected boolean deregister(int nPortSub) + throws IOException + { + ConcurrentMap map = m_mapBindings; + + if (map.remove(nPortSub) == null) + { + throw new IOException("not bound"); + } + + return map.isEmpty(); + } + + // ----- inner class: Switcher ---------------------------------- + + /** + * Switcher handles the initial protocol header from new connections. + */ + public class Switcher extends SafeSelectionHandler + { + public Switcher(SocketChannel chan) + { + super(chan); + } + + @Override + protected int onReadySafe(int nOps) + throws IOException + { + final SocketChannel chan = getChannel(); + final ByteBuffer buf = m_buf; + if (chan.read(buf) < 0) + { + // socket closed before, process based on what has been read thus far + accept(); + return 0; + } + + // check for multiplexed protocol header + boolean fStandard = false; + switch (buf.position()) + { + default: + fStandard |= buf.get(3) != (byte) PROTOCOL_ID; + case 3: + fStandard |= buf.get(2) != (byte) (PROTOCOL_ID >>> 8); + case 2: + fStandard |= buf.get(1) != (byte) (PROTOCOL_ID >>> 16); + case 1: + fStandard |= buf.get(0) != (byte) (PROTOCOL_ID >>> 24); + case 0: + break; + } + + Map mapBindings = m_mapBindings; + if (fStandard || // non-multiplexed header bytes + !buf.hasRemaining() || // enough bytes to accept if multiplexed + mapBindings.containsKey(-1) && mapBindings.size() == 1) // no sub-port listeners + { + accept(); + return 0; + } + else if (m_timer == null) + { + // this only occurs on our initial registration, i.e. first pass + // if we failed to accept on first pass setup a timer to route this + // as a "standard" socket if we fail to accept within timeout + // Note: we do this here to avoid registering the non-cancelable timer + // if we don't have to + final Dependencies deps = getDependencies(); + Runnable timer = m_timer = new Runnable() + { + public void run() + { + if (isPending()) + { + // timeout; to be here we either receive nothing at all on the connection; or we've received + // only a partial protocol header + try + { + // perform one final read attempt + switch (chan.read(buf)) + { + case -1: // socket had actually been closed, but we hadn't detected it + LOGGER.log(Level.WARNING, + "{0} handling delayed close of accepted connection from {1} after {2} ms", + new Object[]{Listener.this.getChannel().socket() + .getLocalSocketAddress(), chan.socket() + .getRemoteSocketAddress(), deps + .getIdentificationTimeoutMillis()}); + break; + + case 0: // "expected" result + LOGGER.log(Level.WARNING, + "{0} failed to identify protocol from {1} bytes for connection from {2} after {3} ms, " + "handling as non-multiplexed", + new Object[]{Listener.this.getChannel().socket() + .getLocalSocketAddress(), buf.position(), chan.socket() + .getRemoteSocketAddress(), deps + .getIdentificationTimeoutMillis()}); + break; + + default: // more data was available, but we hadn't detected it + LOGGER.log(Level.WARNING, + "{0} handling delayed read on accepted connection from {1} after {2} ms", + new Object[] {Listener.this.getChannel().socket().getLocalSocketAddress(), + chan.socket().getRemoteSocketAddress(), + deps.getIdentificationTimeoutMillis()}); + break; + } + + accept(); + } + catch (IOException e) + { + onException(e); + } + } + } + }; + deps.getSelectionService().invoke(chan, timer, deps.getIdentificationTimeoutMillis()); + } + + return OP_READ; + } + + @Override + protected int onException(Throwable t) + { + if (LOGGER.isLoggable(Level.FINEST)) + { + LogRecord record = new LogRecord(Level.FINEST, "{0} exception while waiting for multiplexed header on {1}"); + record.setParameters(new Object[]{ + Listener.this.getChannel().socket().getLocalSocketAddress(), + getChannel().socket().getRemoteSocketAddress()}); + record.setThrown(t); + LOGGER.log(record); + } + + return super.onException(t); + } + + /** + * Accept the chanel and queue it to the appropriate MultiplexedServerSocketChannel + * + * @throws IOException if an I/O error occurs + */ + protected void accept() + throws IOException + { + SocketChannel chan = getChannel(); + ByteBuffer buf = m_buf; + + // identify if the protocol header is present + int nSubPort; + if (!buf.hasRemaining() && buf.getInt(0) == PROTOCOL_ID) + { + // multiplexed client is connecting + nSubPort = buf.getInt(4); + buf = null; + } + else + { + // standard client connecting + nSubPort = -1; + if (!buf.flip().hasRemaining()) + { + buf = null; + } + // else give the bytes back to the channel + } + + // find SeverSocketChannel + final SelectionService svc = getDependencies().getSelectionService(); + svc.register(chan, /*handler*/ null); + + // use invocation to ensure that the channel has been unregistered before continuing, specifically we + // must be unregisterd before setting chan to blocking mode + final int nFinSubPort = nSubPort; + final ByteBuffer bufFin = buf; + final SocketChannel chanFin = chan; + svc.invoke(chan, new Runnable() + { + @Override + public void run() + { + SocketChannel chan = chanFin; + try + { + try + { + chan.configureBlocking(true); // look like every accepted socket + } + catch (IllegalBlockingModeException e) + { + // deregistration hasn't occurred yet (there is no order between register and invoke) + svc.invoke(chan, this, 0); + return; + } + + MultiplexedServerSocketChannel server = m_mapBindings.get(nFinSubPort); + if (server != null) + { + // The MultiplexedChannel is replaced with another MultiplexedChannel to address a deficiency in + // the WrapperChannel infrastructure which prevents a channel from being registered, canceled, and + // re-registered with the same Selector. This is caused by the fact that WrapperSocketChannel extends + // AbstractSocketChannel which in turn hides any reasonable way of removing formerly registered + // SelectionKeys. So here we work-around the issue by producing a new channel. + + // Also we take this opportunity to swap the local address for the accepted connection if + // it appears to be a NAT based connection. + + SocketAddress addrSrv = server.getLocalAddress(); + InetAddress addrSrvIP = InetAddresses.getAddress(addrSrv); + InetAddress addrLocal = InetAddresses.isNatLocalAddress(addrSrv) + ? addrSrvIP // substitute the local NAT address + : addrSrvIP.isAnyLocalAddress() && InetAddresses.hasNatLocalAddress() + ? InetAddresses.getRoutes(InetAddresses.getLocalBindableAddresses(), + Collections.singleton(chan.socket().getInetAddress())).iterator().next() // find best (possibly NAT) address + : chan.socket().getLocalAddress(); // don't change + + chan = new MultiplexedSocketChannel(((MultiplexedSocketChannel) chan).delegate(), + new InetSocketAddress32(addrLocal, chan.socket().getLocalPort()), bufFin); + } + + if (server == null || !server.add(chan)) + { + // no registered ServerSocketChannel, or server queue was full + LogRecord record = new LogRecord(Level.FINE, + "{0} rejecting connection from {1} to subport {2} due to {3}, header {4}"); + record.setParameters(new Object[] { + Listener.this.getChannel().socket().getLocalSocketAddress(), + chan.socket().getRemoteSocketAddress(), + nFinSubPort, + server == null + ? "absence of corresponding MultiplexedServerSocket" + : "backlogged MultiplexedServerSocket", + bufFin == null ? null : Buffers.toString(bufFin)}); + LOGGER.log(record); + + // TODO: it would be nice to be able to send back some indicator that this is meant to appear + // as a reject rather then an EOS. As is a multiplexed client would only see a rejected connection + // if there was no listener for the base port, but no listener for a sub-port looks like a connect/eos + // the problem with doing this would be that we couldn't cover all cases, such as ones where we + // don't yet know if the client is multiplexed. So while we could add the feature we still couldn't + // cover all cases, thus any user of multiplexing must be able to handle a connect/eos case as if + // it was a reject. + + chan.close(); + } + } + catch (IOException e) + { + try + { + chan.close(); + } + catch (IOException e2) {} + } + } + }, 0); + + m_buf = null; // mark as accepted + } + + /** + * Return true iff the channel has yet to be "accepted". + * + * @return true iff the channel has yet to be "accepted" + */ + protected boolean isPending() + { + return getChannel().isOpen() && m_buf != null; + } + + /** + * Holder for protocol header. + */ + protected ByteBuffer m_buf = ByteBuffer.allocate(8); + + /** + * Timeout task watching for non-multiplexed connections. + */ + protected Runnable m_timer; + } + + /** + * Map of port to servers. + */ + protected final ConcurrentNavigableMap + m_mapBindings = new ConcurrentSkipListMap(); + + /** + * The Set of allocated sub-ports in the ephemeral range. + */ + protected final SortedSet m_setEphemeral = m_mapBindings.descendingKeySet(); + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Return the underlying transport address for the specified address. + * + * @param addr the multiplexed address + * + * @return the transport address + */ + public static InetSocketAddress getTransportAddress(InetSocketAddress32 addr) + { + if (addr == null) + { + return null; + } + return new InetSocketAddress(addr.getAddress(), getBasePort(addr.getPort())); + } + + /** + * Return true iff the specified port represents an extended port. + * + * @param nPort the port to test + * + * @return true iff the specified port represents a extended port + */ + public static boolean isPortExtended(int nPort) + { + return (nPort & 0xFFFF0000) != 0; + } + + /** + * Return the base (transport) port for a given 32b port. + * + * @param nPort the port + * + * @return the base port + */ + public static int getBasePort(int nPort) + { + return isPortExtended(nPort) + ? ~nPort >>> 16 + : nPort; + } + + /** + * Return the sub-port for a given 32b port. + * + * @param nPort the port + * + * @return the sub-port, or -1 if none + */ + public static int getSubPort(int nPort) + { + return isPortExtended(nPort) + ? ~nPort & 0x0FFFF + : -1; + } + + /** + * Return the 32 bit port for the specified base and sub port + * + * @param nPortBase the base port + * @param nPortSub the sub port, or -1 for none + * + * @return the 32 bit port version + */ + public static int getPort(int nPortBase, int nPortSub) + { + if (nPortBase < 0 || nPortBase > 0xFFFF) + { + throw new IllegalArgumentException("base port " + nPortBase + " is out of range"); + } + if (nPortSub < -1 || nPortSub > 0xFFFF) + { + throw new IllegalArgumentException("sub port " + nPortSub + " is out of range"); + } + if (nPortBase == 0xFFFF && nPortSub >= 0) + { + throw new IllegalArgumentException("base port of 65535 does not support sub ports"); + } + + return nPortSub == -1 + ? nPortBase + : ~(nPortBase << 16 | nPortSub); + } + + + // ----- inner class: ListenChannel --------------------------------------------------- + + /** + * Helper wrapper for the real ServerSocketChannel to allow it to be managed by the + * multiplexed SelectionService. + */ + protected class ListenChannel + extends WrapperServerSocketChannel + implements MultiplexedChannel + { + public ListenChannel(ServerSocketChannel delegate) + throws IOException + { + super(delegate, new MultiplexedSelectorProvider(delegate.provider())); + } + + @Override + public SocketChannel accept() + throws IOException + { + SocketChannel chan = f_delegate.accept(); + return chan == null + ? null : new MultiplexedSocketChannel(chan, this.socket().getLocalSocketAddress(), null); + } + + + @Override + public int readyOps() + { + return 0; + } + } + + // ----- interface: Dependencies ---------------------------------------- + + /** + * Dependencies describes the MultiplexedSocketProvider's dependencies. + */ + public interface Dependencies + { + /** + * Return the underlying SocketProvider to use. + * + * @return the SocketProvider + */ + public SocketProvider getDelegateProvider(); + + /** + * Return the SelectionService to utilize for processing IO. + * + * @return the SelectionService + */ + public SelectionService getSelectionService(); + + /** + * Return the backlog setting for the underlying SocketProvider. + * + * @return the backlog setting + */ + public int getBacklog(); + + /** + * Return the number of milliseconds an accepted connection has to provide a multiplexed protocol header + * before it is considered to be a standard (non-multiplexed) connection. + *

+ * A high timeout will only negatively impact the arguably rare use-case of a non-multiplexed clients which + * connects, sends nothing, and waits for the server-side to perform the first transmission. This initial server + * transmission to a "quiet" non-multiplexed client will be artificially delayed by the identification timeout. + * All other usage patterns will not be negatively impacted by a high timeout though would be negatively + * impacted by a low timeout as even minimal packet loss could cause them to be incorrectly identified, + * resulting in improper connection routing or socket closure. + *

+ *

+ * See Tuning TCP Parameters for the 21st Century + * for details on TCP's initial retransmission timeout. With even minimal packet loss the initial transmission + * could easily take tens of seconds. It is this initial retransmission timeout which makes a low identification + * timeout unsafe. + *

+ *

+ * The default value may be controlled by the com.oracle.coherence.common.internal.net.MultiplexedSocketProvider.server.identification.timeout + * system property and defaults to one minute. + *

+ * + * @return the timeout in milliseconds + */ + public long getIdentificationTimeoutMillis(); + + /** + * Return the Logger to use. + * + * @return the logger + */ + public Logger getLogger(); + } + + + // ----- inner class: DefaultDependencies ------------------------------- + + /** + * Produce a copy of the specified Dependencies object. + * + * @param deps the dependencies to copy + * + * @return the copied Dependencies as a DefaultDependencies object. + */ + protected DefaultDependencies copyDependencies(Dependencies deps) + { + return new DefaultDependencies(deps); + } + + /** + * DefaultDependencies provides a default implementation of the Dependencies + * interface. + */ + public static class DefaultDependencies + implements Dependencies + { + /** + * Produce a DefaultDependencies object initialized with all defaults. + */ + public DefaultDependencies() + { + this (null); + } + + /** + * Produce a copy based on the supplied Dependencies. + * + * @param deps the Dependencies to copy + */ + public DefaultDependencies(Dependencies deps) + { + if (deps != null) + { + m_provider = deps.getDelegateProvider(); + m_service = deps.getSelectionService(); + m_nBacklog = deps.getBacklog(); + m_cMillisIdentify = deps.getIdentificationTimeoutMillis(); + m_logger = deps.getLogger(); + } + } + + // ----- DefaultDependencies methods ---------------------------- + + /** + * Validate the dependencies object. + * + * @return this object + */ + protected DefaultDependencies validate() + { + return this; + } + + // ----- Dependencies methods ----------------------------------- + + @Override + public SocketProvider getDelegateProvider() + { + SocketProvider provider = m_provider; + if (provider == null) + { + m_provider = provider = TcpSocketProvider.INSTANCE; + } + return provider; + } + + /** + * Specify the SocketProvider to which the MultiplexedSocketProvider + * will delegate to. + * + * @param provider the provider to delegate to + * + * @return this object + */ + public DefaultDependencies setDelegateProvider(SocketProvider provider) + { + m_provider = provider; + return this; + } + + @Override + public SelectionService getSelectionService() + { + SelectionService service = m_service; + if (service == null) + { + m_service = service = SelectionServices.getDefaultService(); + } + return service; + } + + /** + * Specify the SelectionService to use for IO processing. + * + * @param service the SelectionService to use + * + * @return this object + */ + public DefaultDependencies setSelectionService(SelectionService service) + { + m_service = service; + return this; + } + + @Override + public int getBacklog() + { + return m_nBacklog; + } + + /** + * Specify the backlog to use when binding the underlying ServerSocket. + * + * @param nBacklog the backlog + * + * @return this object + */ + public DefaultDependencies setBacklog(int nBacklog) + { + m_nBacklog = nBacklog; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public long getIdentificationTimeoutMillis() + { + return m_cMillisIdentify; + } + + /** + * Specify the identification timeout in milliseconds. + * + * @param cMillis the identification timeout + * + * @return this object + */ + public DefaultDependencies setIdentificationTimeoutMillis(long cMillis) + { + m_cMillisIdentify = cMillis; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Logger getLogger() + { + Logger logger = m_logger; + return logger == null ? LOGGER : logger; + } + + /** + * Specify the Logger to use. + * + * @param logger the logger + * + * @return this object + */ + public DefaultDependencies setLogger(Logger logger) + { + m_logger = logger; + return this; + } + + // ----- constants ---------------------------------------------- + + /** + * The default backlog value. + */ + protected static final int s_nBacklogDefault; + + /** + * The default accept timeout. + */ + protected static final long s_cMillisIdentifyDefault; + + static + { + // we default the backlog to as high as the underlying OS will allow, as multiplexed sockets + // may see a significant connection rate, note that the Java translates a value of 0 to 50, and + // thus relying on the Java default is simply insufficient for many multiplexed cases + // on Linux the maximum allowed backlog is /proc/sys/net/core/somaxconn + // on Windows a value of SOMAXCONN will allow the winsock impl to choose a reasonable value + // while SOMAXCONN is not available in Java, the Windows value happens to be Integer.MAX_VALUE + int nBacklog = Integer.MAX_VALUE; + try + { + nBacklog = Integer.parseInt(System.getProperty( + MultiplexedSocketProvider.class.getName() + + ".server.backlog", Integer.toString(nBacklog))); + } + catch (Throwable t) {} + s_nBacklogDefault = nBacklog; + + // Set the default identification timeout. This value governs how long we will wait for an accepted + // connection to identify itself as multiplexed before assuming it is not. To identify itself + // as multiplexed it only needs to send the 8-byte protocol header, and thus it would seem + // that a very low timeout would be sufficient. Unfortunately though we have to account for + // the possibility that this initial packet gets dropped and must wait on the TCP retransmit + // timeout. This timeout is implementation dependent but appears to start at 3s in most OSs, + // and increase from there in response to packet loss, i.e. 3, 6, 12,... Based on how the TCP + // 3-way handshake works the client should always see the connection as being established + // before the server does. So the only dropped packet we need to worry about is the first + // user-data packet, i.e. the one with our protocol header. Given the above timings and assuming + // near-zero delivery time if this packet were to be dropped once it would take the server about + // 3s to see it, or 9s if it were dropped twice, or 21s if dropped thrice. Since we can't possibly + // rely on zero packet loss, it would seem our minimum safe default is going to be >3s, with >9s + // being the next safe option. Based on this we reluctantly have a default to a large timeout. + // This should only impose a negative impact on non-multiplexed "polite" clients, i.e. clients + // which connect and wait for the server to speak first. + // + // For more details on these timings see: + // http://www.ietf.org/proceedings/75/slides/tcpm-1.pdf + // http://us.generation-nt.com/answer/patch-tcp-expose-initial-rto-via-new-sysctl-help-203365542.html + long cMillisId = 0; + try + { + cMillisId = new Duration(System.getProperty( + MultiplexedSocketProvider.class.getName() + + ".server.identification.timeout", "1m")).as(Duration.Magnitude.MILLI); // default doc'd on getIdentificationTimeoutMillis + } + catch (Throwable t) {} + s_cMillisIdentifyDefault = cMillisId; + } + + // ----- data members ------------------------------------------- + + /** + * The SocketProvider to utilize. + */ + protected SocketProvider m_provider; + + /** + * The SelectionService to utilize. + */ + protected SelectionService m_service; + + /** + * The backlog. + */ + protected int m_nBacklog = s_nBacklogDefault; + + /** + * The accept timeout. + */ + protected long m_cMillisIdentify = s_cMillisIdentifyDefault; + + /** + * The Logger. + */ + protected Logger m_logger; + } + + + // ----- constants ------------------------------------------------------ + + /** + * WellKnownSubports are sub-ports that are reserved for use by components. + */ + public static enum WellKnownSubPorts + { + COHERENCE_TCP_RING (1), + COHERENCE_TCMP_DATAGRAM (2), + COHERENCE_NAME_SERVICE (3); + + /** + * Construct a well known subport for MultiplexedSocketProvider + * @param subPort + */ + WellKnownSubPorts(int subPort) + { + m_nSubPort = subPort; + } + + /** + * Return the sub-port. + * + * @return the sub-port + */ + public int getSubPort() + { + return m_nSubPort; + } + + /** + * Return the 32 bit port number consisting of the supplied base-port and this sub-port. + * + * @param nPortBase the base port + * + * @return the port + */ + public int getPort(int nPortBase) + { + return MultiplexedSocketProvider.getPort(nPortBase, m_nSubPort); + } + + // ----- data members ------------------------------------------- + + private final int m_nSubPort; + } + + /** + * The protocol identifier. + */ + protected static final int PROTOCOL_ID = ProtocolIdentifiers.MULTIPLEXED_SOCKET; + + /** + * The end of the well-known sub-port range. + */ + public static final int WELL_KNOWN_SUB_PORT_END = 1023; + + /** + * The start of the ephemeral sub-port range. + */ + protected static final int EPHEMERAL_SUB_PORT_START = 32768; + + /** + * The default Logger for the provider. + */ + private static Logger LOGGER = Logger.getLogger(MultiplexedSocketProvider.class.getName()); + + static final Set> SERVER_OPTIONS; + + static + { + Set> setOpt = new HashSet>(); + setOpt.add(StandardSocketOptions.SO_RCVBUF); + setOpt.add(StandardSocketOptions.SO_REUSEADDR); + SERVER_OPTIONS = Collections.unmodifiableSet(setOpt); + } + + + // ----- data members --------------------------------------------------- + + /** + * The provider's dependencies. + */ + protected final Dependencies m_dependencies; + + /** + * Map of Listener addresses to their corresponding Listener object + */ + protected final ConcurrentMap + m_mapListener = new ConcurrentHashMap(); + + /** + * The minimum base ephemeral port number which has at some point been + * allocated. + */ + protected int m_nPortEphemeralLow = Integer.MAX_VALUE; + + /** + * The maximum base ephemeral port number which has at some point been + * allocated. + */ + protected int m_nPortEphemeralHi = Integer.MIN_VALUE; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ProtocolIdentifiers.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ProtocolIdentifiers.java new file mode 100644 index 0000000000000..41dd6d01d1db9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ProtocolIdentifiers.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + +import com.oracle.coherence.common.internal.net.socketbus.SocketMessageBus; + +/** + * ProtocolIdentifiers serves as a single location to track the various protocol identifiers contained within this + * project. The intent of this singular location is to help to easily avoid having conflicting identifiers. Once + * an ID has been assigned to a protocol is should not be changed or recycled. + * + * The prescribed ID allocation pattern uses 32 bit ids in the range of 0x05AC1E000..0x05AC1EFFF, + * i.e. ORACLE000..ORACLEFFF. + * + * @author mf 2013.11.06 + */ +public class ProtocolIdentifiers + { + /** + * @see MultiplexedSocketProvider + */ + public static final int MULTIPLEXED_SOCKET = 0x05AC1E000; + + /** + * @see SocketMessageBus + */ + public static final int SOCKET_MESSAGE_BUS = 0x05AC1E001; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ResumableSelectionService.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ResumableSelectionService.java new file mode 100644 index 0000000000000..f03f398e998ab --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ResumableSelectionService.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.net.SelectionService; + +import java.io.IOException; +import java.nio.channels.SelectableChannel; +import java.util.concurrent.ThreadFactory; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * The ResumableSelectionService will automatically allocate and release + * threads to handle the SelectionService. When channels are first registered + * with the service a thread will be started. Once all channels are + * deregistered, the service will automatically release the thread once the + * service crosses its idle timeout. Any subsequent registration will + * allocate a new thread to handle the service. + * + * @author mf 2010.11.23 + */ +public class ResumableSelectionService + extends RunnableSelectionService + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a RestartableSelectionService. + * + * @param factory the ThreadFactory to use in creating threads + */ + public ResumableSelectionService(ThreadFactory factory) + { + m_factory = factory; + } + + // ----- SelectionService interface ------------------------------------- + + /** + * {@inheritDoc} + */ + public synchronized void register(SelectableChannel chan, SelectionService.Handler handler) + throws IOException + { + super.register(chan, handler); + + // ensure there is a thread running this + ensureThread(); + } + + /** + * {@inheritDoc} + */ + public synchronized void invoke(SelectableChannel chan, Runnable runnable, long cMillis) + throws IOException + { + super.invoke(chan, runnable, cMillis); + // ensure there is a thread running this + ensureThread(); + } + + /** + * Ensure that there is a service thread running. + * + * @return the running thread + */ + protected Thread ensureThread() + { + Thread thread = m_thread; + if (thread == null) + { + synchronized (this) + { + thread = m_thread; + if (thread == null) + { + // (re)start the service + thread = m_factory.newThread(this); + m_thread = thread; + thread.setName(toString()); + thread.start(); + } + } + } + return thread; + } + + @Override + protected boolean processRegistrations() + throws IOException + { + int cChanPre = getActiveChannelCount(); + boolean fSelect = super.processRegistrations(); + if (getActiveChannelCount() != cChanPre) + { + ensureThread().setName(toString()); + } + return fSelect; + } + + @Override + protected void wakeup() + { + if (Thread.currentThread() != ensureThread()) + { + super.wakeup(); + } + } + + // ----- Runnable interface --------------------------------------------- + + /** + * {@inheritDoc} + */ + public void run() + { + if (Thread.currentThread() != m_thread) + { + throw new UnsupportedOperationException(); + } + + Logger.getLogger(getClass().getName()).log(Level.FINER, (m_fUsed ? "Resuming " : "Starting ") + this); + m_fUsed = true; + + try + { + while (true) + { + try + { + super.run(); + } + catch (Throwable e) + { + // If we drop this thread the channels managed by this service + // will not get processed, we must continue + Logger.getLogger(getClass().getName()).log(Level.SEVERE, + "Unhandled exception in " + this + + ", attempting to continue", e); + + try + { + // in case the service is broken, avoid eating CPU by + // sleeping for a bit + Blocking.sleep(1000); + } + catch (InterruptedException e2) + { + Thread.currentThread().interrupt(); // eat it for now + } + } + + synchronized (this) + { + if (isIdle()) + { + m_thread = null; + return; + } + } + } + } + finally + { + Logger.getLogger(getClass().getName()).log(Level.FINER, "Suspending " + this); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The ThreadFactory to use for producing the threads to run the service. + */ + protected final ThreadFactory m_factory; + + /** + * The thread running the service. + */ + protected volatile Thread m_thread; + + /** + * Single transition from false to true to indicate if it has ever been used + */ + protected boolean m_fUsed; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/RoundRobinSelectionService.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/RoundRobinSelectionService.java new file mode 100644 index 0000000000000..81be4ada66ace --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/RoundRobinSelectionService.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + + +import com.oracle.coherence.common.base.Factory; +import com.oracle.coherence.common.net.SelectionService; + +import java.nio.channels.SelectableChannel; + + +/** + * The RoundRobinSelectionService load-balances channel registrations over a number + * of child SelectionServices in a round-robin fashion. + * + * @author mf 2013.08.15 + */ +public class RoundRobinSelectionService + extends AbstractStickySelectionService + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a RoundRobinSelectionService which delegates to the provided + * SelectionServices. + * + * @param cServices the maximum number of SelectionServices to use + * @param factory the factory for producing SelectionServices + */ + public RoundRobinSelectionService(int cServices, + Factory factory) + { + super(cServices, factory); + } + + @Override + protected SelectionService selectService(SelectableChannel chan) + { + return f_aServices[Math.abs(m_cReg++) % f_aServices.length]; + } + + + // ----- data members --------------------------------------------------- + + /** + * The registration counter. + */ + protected int m_cReg; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/RunnableSelectionService.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/RunnableSelectionService.java new file mode 100644 index 0000000000000..3766f7b6c6ce6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/RunnableSelectionService.java @@ -0,0 +1,579 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.base.NonBlocking; +import com.oracle.coherence.common.collections.ConcurrentLinkedQueue; +import com.oracle.coherence.common.net.SelectionService; +import com.oracle.coherence.common.util.Timers; + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.IllegalBlockingModeException; +import java.nio.channels.IllegalSelectorException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Queue; +import java.util.Set; + + +/** + * RunnableSelectionService is a single-threaded SelectionService implementation. + *

+ * RunnableSelectionService is Runnable but to be functional needs to be + * serviced by a thread. The {@link #run} method is not thread-safe, and + * should not be invoked by more then one thread. + *

+ * The service will create its Selector as part of the first registration, + * which will ultimately dictate the family of SelectableChannels which + * can be used with the service. + * + * @author mf 2010.10.29 + */ +public class RunnableSelectionService + implements SelectionService, Runnable + { + // ----- SelectionService interface ------------------------------------- + + /** + * {@inheritDoc} + */ + public synchronized void register(SelectableChannel chan, Handler handler) + throws IOException + { + if (chan.isBlocking()) + { + throw new IllegalBlockingModeException(); + } + + ensureSelector(chan); + + // add registration + f_mapRegistrations.put(chan, handler); + + // ensure selector thread sees the new registration + if (!m_fPendingRegistrations) + { + m_fPendingRegistrations = true; + wakeup(); + } + } + + /** + * {@inheritDoc} + */ + public void invoke(final SelectableChannel chan, final Runnable runnable, long cMillis) + throws IOException + { + ensureSelector(chan); + + if (cMillis == 0) + { + boolean fEmpty = f_tasks.isEmpty(); + f_tasks.add(runnable); + if (fEmpty) + { + wakeup(); + } + } + else + { + Timers.scheduleNonBlockingTask(new Runnable() + { + @Override + public void run() + { + try + { + invoke(chan, runnable, 0); + } + catch (IOException e) {} + } + }, cMillis); + } + } + + /** + * Invoke a wakeup on the selector. + */ + protected void wakeup() + { + Selector selector = m_selector; + if (selector != null) + { + selector.wakeup(); + } + } + + /** + * Ensure that the selector is available + * + * @param chan the channel which needs a selector + * + * @return the selector + * + * @throws IOException if an IO error occurs + */ + private Selector ensureSelector(SelectableChannel chan) + throws IOException + { + Selector selector = m_selector; + if (selector == null) + { + synchronized (this) + { + selector = m_selector; + if (selector == null) + { + m_selector = selector = chan.provider().openSelector(); + notifyAll(); // see run() + } + } + } + + if (!selector.isOpen()) + { + throw new ClosedSelectorException(); + } + else if (!chan.provider().equals(selector.provider())) + { + throw new IllegalSelectorException(); + } + + return selector; + } + + /** + * Return the number of channels currently managed by the service. + * + * @return the channel count. + */ + protected int getChannelCount() + { + Selector selector = m_selector; + return selector == null + ? f_mapRegistrations.size() + : Math.max(f_mapRegistrations.size(), selector.keys().size()); + } + + /** + * Return the number of active channels currently managed by the service. + * + * @return the channel count. + */ + protected int getActiveChannelCount() + { + Selector selector = m_selector; + return selector == null + ? 0 + : selector.keys().size(); + } + + @Override + public void associate(SelectableChannel chanParent, SelectableChannel chanChild) + throws IOException + { + register(chanChild, null); + } + + /** + * {@inheritDoc} + */ + public synchronized void shutdown() + { + Selector selector = m_selector; + if (selector != null) + { + try + { + selector.close(); + } + catch (IOException e) {} + } + } + + + // ----- RunnableSelectionService interface ----------------------------- + + /** + * Set the duration the {@link #run} method should block with no + * registered keys before returning. + *

+ * Upon timing out the {@link #run} method will return, but the service + * will not be shutdown. + *

+ * A timeout specified while {@link #run} is active may not immediately + * take effect. + * + * @param cMillis the idle timeout, or zero for infinite + * + * @return this object + */ + public RunnableSelectionService setIdleTimeout(long cMillis) + { + m_cMillisTimeout = cMillis; + return this; + } + + /** + * Return the duration the {@link #run} method should block with no + * registered keys before returning. + * + * @return the idle timeout, or zero for infinite + */ + public long getIdleTimeout() + { + return m_cMillisTimeout; + } + + /** + * Indicate if the service is idle (has no registered channels)). + * + * @return true iff the service is idle + */ + public synchronized boolean isIdle() + { + Selector selector = m_selector; + return selector == null || !selector.isOpen() || + (selector.keys().isEmpty() && f_mapRegistrations.isEmpty() && + f_tasks.isEmpty()); + } + + + // ----- Runnable interface --------------------------------------------- + + /** + * {@inheritDoc} + */ + public void run() + { + Selector selector = null; + try + { + // wait for the lazily set selector; see ensureSelector() + synchronized (this) + { + while ((selector = m_selector) == null) + { + long cWait = getIdleTimeout(); + Blocking.wait(this, cWait); + if (cWait != 0 && m_selector == null) + { + return; + } + } + } + + // perform service processing + process(); + + // try to close the selector to avoid fd leaks - COH-5911 + synchronized(this) + { + // ensure that there is no pending work for the selector. If there is + // pending work, ResumableSelectionService should call the run + // method again. + if (isIdle()) + { + // unlike shutdown, we close the selector but are + // still open for business + m_selector = null; + selector.close(); + } + } + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + catch (InterruptedIOException e) + { + Thread.currentThread().interrupt(); + } + catch (ClosedSelectorException e) + { + // we've been shutdown + } + catch (IOException e) + { + if (selector.isOpen()) + { + throw new RuntimeException(e); + } + } + } + + + // ----- Object interface ---------------------------------------------- + + /** + * {@inheritDoc} + */ + public synchronized String toString() + { + // TODO: include load (percentage of time awake)? + return "SelectionService(channels=" + getChannelCount() + ", selector=" + m_selector + ", id=" + System.identityHashCode(this) + ")"; + } + + + // ----- helper methods ------------------------------------------------ + + /** + * Perform service processing. + *

+ * It is unsafe to call this method outside of the service thread. + * + * @throws IOException on an I/O error which is not related to a specific + * channel + */ + protected void process() + throws IOException + { + Selector selector = m_selector; + Set setReg = selector.keys(); + Set setReady = selector.selectedKeys(); + boolean fImmediate = false; + + try (NonBlocking nonBlocking = new NonBlocking()) // mark thread as non-blocking during processing loop + { + for (int i = 0; ; ++i) + { + if (!fImmediate) // don't execute runnables or registrations while there are pending canceled keys + { + processRunnables(); + + if (m_fPendingRegistrations) + { + fImmediate |= processRegistrations(); + } + } + + // perform select + try + { + if (fImmediate || m_fPendingRegistrations || !f_tasks.isEmpty()) + { + selector.selectNow(); + fImmediate = false; + continue; // ensure any pending registrations get picked up so we don't risk using an old handler for new IO + } + else + { + // we split the idle timeout in half and do up to two timed selects. This is because + // select(timeout) == 0 doesn't necessarily mean that timeout time has passed, it could + // mean that no time has passed and that it just cleaned up some canceled keys. By breaking + // it in two we help ensure that on the second == 0 that some time has really passed + long cMillisTimeout = (getIdleTimeout() + 1) / 2; + if (Blocking.select(selector, cMillisTimeout) == 0 && cMillisTimeout != 0 && setReg.isEmpty() && isIdle() && + Blocking.select(selector, cMillisTimeout) == 0 && isIdle()) + { + return; + } + } + } + catch (CancelledKeyException e) + { + // This can happen if another thread concurrently closes + // a registered channel, during the start of the select + // operation. We need to force another selection. + fImmediate = true; + } + + // process selected keys + boolean fEager = true; + for (int iSpin = 0; fEager && iSpin < 4; ++iSpin) // TODO: identify the proper spin limiter; make configurable? + { + fEager = false; + for (Iterator iter = setReady.iterator(); iter.hasNext(); ) + { + SelectionKey key = iter.next(); + try + { + int nInterest = key.interestOps(); + int nReady = key.readyOps(); // nReady is a sub-set of nInterest + + int nInterestNew = ((Handler) key.attachment()).onReady( + iSpin > 0 + ? Handler.OP_EAGER | nInterest + : nReady); + + if ((nInterestNew & Handler.OP_EAGER) == 0) + { + // not eager; remove from ready set + iter.remove(); + } + else // eager + { + // leave eager channels in the ready set + fEager = true; + nInterestNew &= ~Handler.OP_EAGER; // hide eager from actual selector layer + } + + if (nInterestNew != nInterest) + { + // profiling has shown that setting interestOps is not "free", so avoid doing + // it unless necessary; note that it is cheaper to read it then to write it + key.interestOps(nInterestNew); + } + } + catch (Throwable t) + { + // on error, unregister the handler and force a + // immediate selection to clear the key from selector + // allowing for future registrations + key.cancel(); + fImmediate = true; + try + { + iter.remove(); + } + catch (IllegalStateException e) + { + // we may have already removed above; eat it + } + } + } + } + + if (fEager && (i & 0xF) == 0) // TODO: identify the proper eagerness limiter; make configurable? + { + // periodically clear any eager channels in case they are being over eager + setReady.clear(); + } + } + } + } + + /** + * Process any pending registrations. + *

+ * This is an internal operation which occurs as part of the + * {@link #process} operation. It is not safe to call it outside of the + * service thread. + * + * @return true if immediate selection is required + * + * @throws IOException on an I/O error which is not related to a specific + * channel + */ + protected synchronized boolean processRegistrations() + throws IOException + { + boolean fImmediate = false; + + // Note: I've evaluated using ConcurrentHashMap or + // ConcurrentLinkedQueue in place of a HashMap protected by service + // synchronization. The cost of an isEmpty() check on either of the + // concurrent options is more expensive then the single volatile + // read used in the synchronized solution. Since the majority of the + // time the registration map should be empty, the current approach + // seems to be a good choice. + Map mapReg = f_mapRegistrations; + + Selector selector = m_selector; + for (Map.Entry entry : mapReg.entrySet()) + { + SelectableChannel chan = entry.getKey(); + Handler handler = entry.getValue(); + + if (handler == null) + { + SelectionKey key = chan.keyFor(selector); + if (key != null) + { + fImmediate = true; + key.cancel(); + } + } + else + { + try + { + chan.register(selector, chan.validOps(), handler); + } + catch (IOException e) + { + fImmediate = true; + } + catch (CancelledKeyException e) + { + fImmediate = true; + } + } + } + + mapReg.clear(); + m_fPendingRegistrations = false; + + return fImmediate; + } + + /** + * Execute Runnable in the SelectionService thread + */ + protected void processRunnables() + { + // it is allowable for a task to add another task to the queue in which case we + // could loop forever (MultiplexedSocket accept can cause this), add a marker so we can + // avoid doing any more work then necessary. + // Note, if we have EOS followed by more work it simply means we'll do a selectNow and + // then come back here, so the impact is quite minimal, it just serves to allow for + // some task swapping + f_tasks.add(EOS); + for (Runnable task = f_tasks.poll(); task != EOS; task = f_tasks.poll()) + { + try + { + task.run(); + } + catch (Throwable thr) {} + } + } + + + // ----- data members -------------------------------------------------- + + /** + * Marker for end of task stream + */ + private static final Runnable EOS = () -> {}; + + /** + * The selector associated with this service. + */ + private volatile Selector m_selector; + + /** + * Map of pending (re)registrations. + *

+ * All access to the map is protected via synchronization on the service. + */ + private final Map f_mapRegistrations = new HashMap<>(); + + /** + * Queue of tasks to run. + */ + private final Queue f_tasks = new ConcurrentLinkedQueue<>(); + + /** + * Flag indicating if the pending registration map contains any values. + */ + private volatile boolean m_fPendingRegistrations; + + /** + * The selector idle timeout. + */ + private long m_cMillisTimeout; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/SignalEvent.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/SignalEvent.java new file mode 100644 index 0000000000000..a16f705e6111c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/SignalEvent.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + +import com.oracle.coherence.common.net.exabus.EndPoint; +import com.oracle.coherence.common.net.exabus.Event; + +/** + * An implementation of a SIGNAL event. + * + * @author mf 2014.03.24 + */ +public class SignalEvent + extends Number + implements Event + { + public SignalEvent(EndPoint point, long lContent) + { + f_point = point; + f_lContent = lContent; + } + + // ----- Event interface ----------------------------------- + + + @Override + public Type getType() + { + return Type.SIGNAL; + } + + @Override + public EndPoint getEndPoint() + { + return f_point; + } + + @Override + public Object getContent() + { + return this; + } + + @Override + public Object dispose(boolean fTakeContent) + { + return this; + } + + @Override + public void dispose() + { + } + + // ----- Number interface ---------------------------------- + + @Override + public int intValue() + { + return (int) f_lContent; + } + + @Override + public long longValue() + { + return f_lContent; + } + + @Override + public float floatValue() + { + return (float) f_lContent; + } + + @Override + public double doubleValue() + { + return (double) f_lContent; + } + + @Override + public byte byteValue() + { + return (byte) f_lContent; + } + + @Override + public short shortValue() + { + return (short) f_lContent; + } + + // ----- Object interface -------------------------------------- + + @Override + public String toString() + { + return "SignalEvent(" + f_point + ", content=" + f_lContent + ")"; + } + + + // ----- data members ------------------------------------------ + + /** + * The associated endpoint. + */ + protected final EndPoint f_point; + + /** + * The content. + */ + protected final long f_lContent; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/SocketChannelInputStream.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/SocketChannelInputStream.java new file mode 100644 index 0000000000000..fc829f9cee54a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/SocketChannelInputStream.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + +import java.io.IOException; +import java.io.InputStream; + +import java.nio.ByteBuffer; + +import java.nio.channels.IllegalBlockingModeException; +import java.nio.channels.SocketChannel; + +/** + * An InputStream implementation which delegates to a SocketChannel. + * + * @author mf 2016.05.02 + */ +public class SocketChannelInputStream + extends InputStream + { + public SocketChannelInputStream(SocketChannel channel) + { + f_channel = channel; + } + + @Override + public int read(byte[] ab, int off, int len) + throws IOException + { + synchronized (f_channel.blockingLock()) + { + if (f_channel.isBlocking()) + { + return f_channel.read(ByteBuffer.wrap(ab, off, len)); + } + else + { + throw new IllegalBlockingModeException(); + } + } + } + + @Override + public void close() + throws IOException + { + f_channel.close(); + } + + @Override + public int read() + throws IOException + { + byte[] ab = new byte[1]; + return read(ab) < 0 + ? -1 + : ab[0]; + } + + // ----- data members --------------------------------------------------- + + /** + * The underlying channel + */ + protected final SocketChannel f_channel; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/SocketChannelOutputStream.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/SocketChannelOutputStream.java new file mode 100644 index 0000000000000..ae5c2387d406c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/SocketChannelOutputStream.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + +import java.io.IOException; +import java.io.OutputStream; + +import java.nio.ByteBuffer; + +import java.nio.channels.IllegalBlockingModeException; +import java.nio.channels.SocketChannel; + +/** + * An OutputStream implementation which delegates to a SocketChannel. + * + * @author mf 2016.05.02 + */ +public class SocketChannelOutputStream + extends OutputStream + { + public SocketChannelOutputStream(SocketChannel channel) + { + f_channel = channel; + } + + @Override + public void write(int b) + throws IOException + { + write(new byte[]{(byte) b}); + } + + @Override + public void write(byte[] ab, int off, int len) + throws IOException + { + synchronized (f_channel.blockingLock()) + { + if (f_channel.isBlocking()) + { + f_channel.write(ByteBuffer.wrap(ab, off, len)); + } + else + { + throw new IllegalBlockingModeException(); + } + } + } + + @Override + public void close() + throws IOException + { + f_channel.close(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The underlying channel. + */ + protected final SocketChannel f_channel; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperSelector.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperSelector.java new file mode 100644 index 0000000000000..9fe4102232b31 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperSelector.java @@ -0,0 +1,527 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + + +import com.oracle.coherence.common.base.Blocking; + +import com.tangosol.util.ConverterCollections; + +import java.nio.channels.CancelledKeyException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; + +import java.nio.channels.spi.AbstractSelectableChannel; +import java.nio.channels.spi.AbstractSelector; +import java.nio.channels.spi.SelectorProvider; + +import java.io.IOException; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import java.lang.reflect.Method; + +import java.security.AccessController; +import java.security.PrivilegedAction; + +import java.util.Set; + + +/** +* WrapperSelector is a Selector implementation which delegates all calls to a +* delegate Selector. +* +* @author mf 2010.04.27 +* @since Coherence 3.6 +*/ +public class WrapperSelector + extends AbstractSelector + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a WrapperSelector + * + * @param selector the selector to wrap + * @param provider the WrapperSocketProvider + * + * @throws IOException if an I/O error occurs + */ + public WrapperSelector(Selector selector, SelectorProvider provider) + throws IOException + { + super(provider); + + m_delegate = selector; + m_setKeys = new KeySet(selector.keys()); + m_setSelectedKeys = new KeySet(selector.selectedKeys()); + } + + + // ----- WrapperSelector methods ---------------------------------------- + + /** + * Return the Selector to which this selector delegates. + * + * @return the Selector to which this selector delegates + */ + public Selector getDelegate() + { + return m_delegate; + } + + + // ----- Selector methods ----------------------------------------------- + + /** + * Unsupported. + * + * @return never + * + * @throws UnsupportedOperationException not supported + */ + public static Selector open() + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + public Set keys() + { + return m_setKeys; + } + + /** + * {@inheritDoc} + */ + public Set selectedKeys() + { + return m_setSelectedKeys; + } + + /** + * {@inheritDoc} + */ + public int select() + throws IOException + { + return select(0L); + } + + /** + * {@inheritDoc} + */ + public int selectNow() + throws IOException + { + return select(-1L); + } + + /** + * {@inheritDoc} + */ + public int select(long timeout) + throws IOException + { + // To fullfill the contract of thread-safety on the key sets we + // synchronize on the Selector, Keys, and then SelectedKeys. This is + // is only necessary so that an application doing external + // synchronization on any of these objects can block the selector from + // proceeeding as descripted in Selector JavaDoc. + synchronized (this) + { + synchronized (m_setKeys) + { + synchronized (m_setSelectedKeys) + { + try + { + return timeout > 0 ? Blocking.select(m_delegate, timeout) + : timeout == 0 ? Blocking.select(m_delegate) + : m_delegate.selectNow(); + } + finally + { + cleanupCancelledKeys(); + } + } + } + } + } + + /** + * {@inheritDoc} + */ + public Selector wakeup() + { + m_delegate.wakeup(); + return this; + } + + + // ---- AbstractSelector methods ---------------------------------------- + + /** + * {@inheritDoc} + */ + protected void implCloseSelector() + throws IOException + { + // write lock to prevent concurrent register. This is a *work-around* + // for the JDK bug described in Bug20721488. + f_lockWrite.lock(); + try + { + m_delegate.close(); + } + finally + { + f_lockWrite.unlock(); + } + } + + /** + * {@inheritDoc} + */ + protected SelectionKey register(final AbstractSelectableChannel ch, + int ops, Object att) + { + // read lock to prevent concurrent close. This is a *work-around* + // for the JDK bug described in Bug20721488. + f_lockRead.lock(); + try + { + return ((WrapperSelectableChannel) ch).registerInternal( + this, ops, att); + } + catch (IOException e) + { + // we must return a canceled invalid key + return new WrapperSelectionKey(this, null, att) + { + public SelectableChannel channel() + { + throw new CancelledKeyException(); + } + + public boolean isValid() + { + return false; + } + + public void cancel() + { + // already canceled + } + + public int interestOps() + { + throw new CancelledKeyException(); + } + + public SelectionKey interestOps(int ops) + { + throw new CancelledKeyException(); + } + + public int readyOps() + { + throw new CancelledKeyException(); + } + }; + } + finally + { + f_lockRead.unlock(); + } + } + + + // ------ helper methods -------------------------------------------- + + /** + * Cleanup any previously cancelled keys + * + * @throws IOException if an IO error occurs + */ + protected void cleanupCancelledKeys() + throws IOException + { + Set setCanceled = cancelledKeys(); + SelectionKey[] aKey = null; + synchronized (setCanceled) + { + if (!setCanceled.isEmpty()) + { + // first allow the delegates to finish cancellation + m_delegate.selectNow(); + + aKey = setCanceled.toArray(new SelectionKey[setCanceled.size()]); + + setCanceled.clear(); + } + } + + // now finish cancellation of the wrappers + if (aKey != null && METHOD_REMOVE_KEY != null) + { + for (SelectionKey key : aKey) + { + SelectableChannel chan = key.channel(); + if (chan instanceof AbstractSelectableChannel) + { + try + { + METHOD_REMOVE_KEY.invoke(chan, key); + } + catch (Throwable e) {} + } + } + } + } + + // ------ inner interface: WrapperSelectableChannel ----------------- + + /** + * An interface to be implemented by all channels which will be selectable + * using this Selector. + */ + public interface WrapperSelectableChannel + { + /** + * Register with the specified selector. + * + * @param selector the selector to register with + * @param ops the operations of interest + * @param att the attachment + * + * @return the wrapper selection key + * + * @throws IOException if an I/O error occurs + */ + WrapperSelectionKey registerInternal(WrapperSelector selector, int ops, + Object att) + throws IOException; + } + + + // ------ inner class: WrapperSelectionKey -------------------------- + + /** + * WraperSelectionKey which delegates to a real SelectionKey. + */ + public abstract static class WrapperSelectionKey + extends SelectionKey + { + // ----- constructor -------------------------------------------- + + public WrapperSelectionKey(WrapperSelector selector, SelectionKey key, Object att) + { + m_selector = selector; + m_delegate = key; + attach(att); + + if (key != null) + { + key.attach(this); + } + } + + + // ----- SelectionKey methods ----------------------------------- + + /** + * {@inheritDoc} + */ + public abstract SelectableChannel channel(); + + /** + * {@inheritDoc} + */ + public Selector selector() + { + return m_selector; + } + + /** + * {@inheritDoc} + */ + public boolean isValid() + { + return m_delegate.isValid(); + } + + /** + * {@inheritDoc} + */ + public void cancel() + { + Set setCancel = m_selector.cancelledKeys(); + synchronized (setCancel) + { + m_delegate.cancel(); + setCancel.add(this); + } + } + + /** + * {@inheritDoc} + */ + public int interestOps() + { + return m_delegate.interestOps(); + } + + /** + * {@inheritDoc} + */ + public SelectionKey interestOps(int ops) + { + m_delegate.interestOps(ops); + return this; + } + + /** + * {@inheritDoc} + */ + public int readyOps() + { + return m_delegate.readyOps(); + } + + /** + * Return a description of the SelectionKey. + * + * @param key the seleciton key + * @return the description + */ + protected String getKeyString(SelectionKey key) + { + return key.isValid() + ? "interest=" + key.interestOps() + ", ready=" + key.readyOps() + : "cancelled"; + } + + // ----- Object methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return getKeyString(this); + } + + // ----- data members ------------------------------------------- + + /** + * The associated WrapperSelector. + */ + protected WrapperSelector m_selector; + + /** + * The delegate SelectionKey. + */ + protected final SelectionKey m_delegate; + } + + + // ----- inner class: KeySet -------------------------------------------- + + /** + * A layered set implementation used for key sets. + */ + public class KeySet + extends ConverterCollections.ConverterSet + { + // ----- constructor -------------------------------------------- + + protected KeySet(Set setBack) + { + super(setBack, + key -> ((SelectionKey) key).attachment(), + key -> ((WrapperSelectionKey) key).m_delegate); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * Method which allows for cleanup of cached SelectionKeys from AbstractSelectableChannels. + */ + private static final Method METHOD_REMOVE_KEY; + + static + { + // Note: the removeKey method on AbstractSelectableChannels is package private and is meant to be + // accessed indirectly by having a Selector extend AbstractSelector and use + // AbstractSelector.deregister(AbstractSelectionKey). Unfortunately AbstractSelectionKey is largely + // final and is not a sufficient base for what is needed to produce a usable WrapperSelectionKey. + // Thus we are left relying on reflection. Without invoking removeKey a cancelled SelectionKey will + // remain cached within the SelectableChannel which ultimately prevents that channel from ever being + // re-registered with the same Selector. + // NOTE: Since this hack no longer works with Java9 we may eventually need to bite the bullet and work with + // AbstractSelectionKey, the primary issue is that ASK.cancel() is final, but the effect of calling it ultimate + // puts it into the AbstractSelector#cancelledKeys() which we do have access to, and thus could then call + // our extension to ASK, adding a special cancelInternal() or something. But until we have issues because of + // lack of multi-reg we'll just hold off + Method metRemove = null; + try + { + if (System.getProperty("java.vm.specification.version").startsWith("1.")) + { + metRemove = AccessController.doPrivileged( + (PrivilegedAction) () -> { + try + { + Method met = AbstractSelectableChannel.class.getDeclaredMethod("removeKey", SelectionKey.class); + met.setAccessible(true); + return met; + } + catch (Throwable e) + { + return null; + } + }); + } + // else; java9 blocks this unless --permit-illegal-access is on which will also issue an ugly warning + } + catch (Exception e) {} + + METHOD_REMOVE_KEY = metRemove; + } + + + // ----- data members --------------------------------------------------- + + /** + * The wrapped Selector. + */ + protected final Selector m_delegate; + + /** + * The selector's keys + */ + protected final Set m_setKeys; + + /** + * The selector's selected keys + */ + protected final Set m_setSelectedKeys; + + /** + * Lock used to protect concurrent register/close + */ + protected final ReadWriteLock f_lockSelector = new ReentrantReadWriteLock(); + protected final Lock f_lockRead = f_lockSelector.readLock(); + protected final Lock f_lockWrite = f_lockSelector.writeLock(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperServerSocket.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperServerSocket.java new file mode 100644 index 0000000000000..5cd39df04a57c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperServerSocket.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.internal.net; + + +import java.io.IOException; + +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.ServerSocket; + +import java.nio.channels.ServerSocketChannel; + + +/** +* Wrapper server socket which delegates all operations to a delegate socket. +* +* @author jh/mf 2010.04.27 +*/ +public class WrapperServerSocket + extends ServerSocket + { + // ----- constructor ---------------------------------------------------- + + /** + * Create a new ServerSocket that delegates all operations to the given + * server socket. + * + * @param socket the delegate server socket + * + * @throws IOException on error opening the socket + */ + public WrapperServerSocket(ServerSocket socket) + throws IOException + { + if (socket == null) + { + throw new IllegalArgumentException(); + } + m_delegate = socket; + } + + + // ----- ServerSocket methods ------------------------------------------- + + /** + * {@inheritDoc} + */ + public Socket accept() + throws IOException + { + return m_delegate.accept(); + } + + /** + * {@inheritDoc} + */ + public void bind(SocketAddress endpoint) + throws IOException + { + m_delegate.bind(endpoint); + } + + /** + * {@inheritDoc} + */ + public void bind(SocketAddress endpoint, int backlog) + throws IOException + { + m_delegate.bind(endpoint, backlog); + } + + /** + * {@inheritDoc} + */ + public void close() + throws IOException + { + super.close(); // just to free underlying FD + m_delegate.close(); + } + + /** + * {@inheritDoc} + */ + public ServerSocketChannel getChannel() + { + return m_delegate.getChannel(); + } + + /** + * {@inheritDoc} + */ + public InetAddress getInetAddress() + { + return m_delegate.getInetAddress(); + } + + /** + * {@inheritDoc} + */ + public int getLocalPort() + { + return m_delegate.getLocalPort(); + } + + /** + * {@inheritDoc} + */ + public SocketAddress getLocalSocketAddress() + { + return m_delegate.getLocalSocketAddress(); + } + + /** + * {@inheritDoc} + */ + public int getReceiveBufferSize() + throws SocketException + { + return m_delegate.getReceiveBufferSize(); + } + + /** + * {@inheritDoc} + */ + public boolean getReuseAddress() + throws SocketException + { + return m_delegate.getReuseAddress(); + } + + /** + * {@inheritDoc} + */ + public int getSoTimeout() + throws IOException + { + return m_delegate.getSoTimeout(); + } + + /** + * {@inheritDoc} + */ + public boolean isBound() + { + return m_delegate.isBound(); + } + + /** + * {@inheritDoc} + */ + public boolean isClosed() + { + return m_delegate.isClosed(); + } + + /** + * {@inheritDoc} + */ + public void setPerformancePreferences(int nConnectionTime, + int nLatency, int nBandwidth) + { + m_delegate.setPerformancePreferences(nConnectionTime, nLatency, + nBandwidth); + } + + /** + * {@inheritDoc} + */ + public void setReceiveBufferSize(int cb) + throws SocketException + { + m_delegate.setReceiveBufferSize(cb); + } + + /** + * {@inheritDoc} + */ + public void setReuseAddress(boolean fReuse) + throws SocketException + { + m_delegate.setReuseAddress(fReuse); + } + + /** + * {@inheritDoc} + */ + public void setSoTimeout(int cSecs) + throws SocketException + { + m_delegate.setSoTimeout(cSecs); + } + + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return m_delegate.toString(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The delegate socket. + */ + protected final ServerSocket m_delegate; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperServerSocketChannel.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperServerSocketChannel.java new file mode 100644 index 0000000000000..e54994239df93 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperServerSocketChannel.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net; + + +import java.net.SocketAddress; +import java.net.SocketOption; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.spi.SelectorProvider; +import java.net.ServerSocket; +import java.net.Socket; +import java.io.IOException; +import java.util.Set; + + +/** +* Wrapper ServerSocketChannel implementation that delegates all operations to +* a delegate ServerSocketChannel. +* +* @author mf 2010.05.19 +*/ +public class WrapperServerSocketChannel + extends ServerSocketChannel + implements WrapperSelector.WrapperSelectableChannel + { + // ----- constructors --------------------------------------------------- + + public WrapperServerSocketChannel(ServerSocketChannel channel, SelectorProvider provider) + throws IOException + { + super(provider); + + f_delegate = channel; + f_socket = wrapSocket(channel.socket()); + } + + + // ----- WrapperServerSocketChannel methods ----------------------------- + + /** + * Produce a wrapper around the specified socket. + * + * @param socket the socket to wrap + * @return the wrapper socket + * + * @throws IOException if an I/O error occurs + */ + protected ServerSocket wrapSocket(ServerSocket socket) + throws IOException + { + return new WrapperServerSocket(socket) + { + public Socket accept() + throws IOException + { + throw new UnsupportedOperationException(); + } + + public ServerSocketChannel getChannel() + { + return WrapperServerSocketChannel.this; + } + }; + } + + // ----- ServerSocketChannel methods ------------------------------------ + + /** + * Unsupported. + * + * @return never + * + * @throws UnsupportedOperationException not supported + */ + public static ServerSocketChannel open() + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + public ServerSocket socket() + { + return f_socket; + } + + /** + * {@inheritDoc} + */ + public SocketChannel accept() + throws IOException + { + SocketChannel channel = f_delegate.accept(); + return channel == null + ? null + : new WrapperSocketChannel(channel, provider()); + } + + /** + * {@inheritDoc} + */ + protected void implCloseSelectableChannel() + throws IOException + { + f_delegate.close(); + } + + /** + * {@inheritDoc} + */ + protected void implConfigureBlocking(boolean block) + throws IOException + { + f_delegate.configureBlocking(block); + } + + @Override + public ServerSocketChannel bind(SocketAddress local, int backlog) + throws IOException + { + return f_delegate.bind(local, backlog); + } + + @Override + public ServerSocketChannel setOption(SocketOption name, T value) + throws IOException + { + return f_delegate.setOption(name, value); + } + + + // ----- NetworkChannel methods ----------------------------------------- + + @Override + public SocketAddress getLocalAddress() + throws IOException + { + return f_delegate.getLocalAddress(); + } + + @Override + public T getOption(SocketOption name) + throws IOException + { + return f_delegate.getOption(name); + } + + @Override + public Set> supportedOptions() + { + return f_delegate.supportedOptions(); + } + + + // ----- WrapperSelectableChannel methods ------------------------------- + + /** + * {@inheritDoc} + */ + public WrapperSelector.WrapperSelectionKey registerInternal( + WrapperSelector selector, int ops, Object att) + throws IOException + { + return new WrapperSelector.WrapperSelectionKey(selector, + f_delegate.register(selector.getDelegate(), ops), att) + { + public SelectableChannel channel() + { + return WrapperServerSocketChannel.this; + } + }; + } + + // ----- data members --------------------------------------------------- + + /** + * The delegate channel. + */ + protected final ServerSocketChannel f_delegate; + + /** + * The associated ServerSocket. + */ + protected final ServerSocket f_socket; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperSocket.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperSocket.java new file mode 100644 index 0000000000000..2c7237b019bbe --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperSocket.java @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.internal.net; + + +import com.oracle.coherence.common.base.Blocking; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; + +import java.nio.channels.SocketChannel; + + +/** +* Wrapper socket implementation that delegates all operations to a delegate +* socket. +* +* @author jh/mf 2010.04.27 +*/ +public class WrapperSocket + extends Socket + { + // ----- constructor ---------------------------------------------------- + + /** + * Create a new Socket that delegates all operations to the given + * socket. + * + * @param socket the delegate socket + */ + public WrapperSocket(Socket socket) + { + if (socket == null) + { + throw new IllegalArgumentException(); + } + m_delegate = socket; + } + + + // ----- Socket methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public void bind(SocketAddress addr) + throws IOException + { + m_delegate.bind(addr); + } + + /** + * {@inheritDoc} + */ + public void close() + throws IOException + { + super.close(); // just to free underlying FD + m_delegate.close(); + } + + /** + * {@inheritDoc} + */ + public void connect(SocketAddress addr) + throws IOException + { + connect(addr, 0); + } + + /** + * {@inheritDoc} + */ + public void connect(SocketAddress addr, int cMillis) + throws IOException + { + Blocking.connect(m_delegate, addr, cMillis); + } + + /** + * {@inheritDoc} + */ + public SocketChannel getChannel() + { + return m_delegate.getChannel(); + } + + /** + * {@inheritDoc} + */ + public InetAddress getInetAddress() + { + return m_delegate.getInetAddress(); + } + + /** + * {@inheritDoc} + */ + public InputStream getInputStream() + throws IOException + { + return m_delegate.getInputStream(); + } + + /** + * {@inheritDoc} + */ + public boolean getKeepAlive() + throws SocketException + { + return m_delegate.getKeepAlive(); + } + + /** + * {@inheritDoc} + */ + public InetAddress getLocalAddress() + { + return m_delegate.getLocalAddress(); + } + + /** + * {@inheritDoc} + */ + public int getLocalPort() + { + return m_delegate.getLocalPort(); + } + + /** + * {@inheritDoc} + */ + public SocketAddress getLocalSocketAddress() + { + return m_delegate.getLocalSocketAddress(); + } + + /** + * {@inheritDoc} + */ + public boolean getOOBInline() + throws SocketException + { + return m_delegate.getOOBInline(); + } + + /** + * {@inheritDoc} + */ + public OutputStream getOutputStream() + throws IOException + { + return m_delegate.getOutputStream(); + } + + /** + * {@inheritDoc} + */ + public int getPort() + { + return m_delegate.getPort(); + } + + /** + * {@inheritDoc} + */ + public int getReceiveBufferSize() + throws SocketException + { + return m_delegate.getReceiveBufferSize(); + } + + /** + * {@inheritDoc} + */ + public SocketAddress getRemoteSocketAddress() + { + return m_delegate.getRemoteSocketAddress(); + } + + /** + * {@inheritDoc} + */ + public boolean getReuseAddress() + throws SocketException + { + return m_delegate.getReuseAddress(); + } + + /** + * {@inheritDoc} + */ + public int getSendBufferSize() + throws SocketException + { + return m_delegate.getSendBufferSize(); + } + + /** + * {@inheritDoc} + */ + public int getSoLinger() + throws SocketException + { + return m_delegate.getSoLinger(); + } + + /** + * {@inheritDoc} + */ + public int getSoTimeout() + throws SocketException + { + return m_delegate.getSoTimeout(); + } + + /** + * {@inheritDoc} + */ + public boolean getTcpNoDelay() + throws SocketException + { + return m_delegate.getTcpNoDelay(); + } + + /** + * {@inheritDoc} + */ + public int getTrafficClass() + throws SocketException + { + return m_delegate.getTrafficClass(); + } + + /** + * {@inheritDoc} + */ + public boolean isBound() + { + return m_delegate.isBound(); + } + + /** + * {@inheritDoc} + */ + public boolean isClosed() + { + return m_delegate.isClosed(); + } + + /** + * {@inheritDoc} + */ + public boolean isConnected() + { + return m_delegate.isConnected(); + } + + /** + * {@inheritDoc} + */ + public boolean isInputShutdown() + { + return m_delegate.isInputShutdown(); + } + + /** + * {@inheritDoc} + */ + public boolean isOutputShutdown() + { + return m_delegate.isOutputShutdown(); + } + + /** + * {@inheritDoc} + */ + public void sendUrgentData(int nData) + throws IOException + { + m_delegate.sendUrgentData(nData); + } + + /** + * {@inheritDoc} + */ + public void setKeepAlive(boolean fKeepAlive) + throws SocketException + { + m_delegate.setKeepAlive(fKeepAlive); + } + + /** + * {@inheritDoc} + */ + public void setOOBInline(boolean fInline) + throws SocketException + { + m_delegate.setOOBInline(fInline); + } + + /** + * {@inheritDoc} + */ + public void setPerformancePreferences(int nConnectionTime, int nLatency, + int nBandwidth) + { + m_delegate.setPerformancePreferences(nConnectionTime, nLatency, + nBandwidth); + } + + /** + * {@inheritDoc} + */ + public void setReceiveBufferSize(int cb) + throws SocketException + { + m_delegate.setReceiveBufferSize(cb); + } + + /** + * {@inheritDoc} + */ + public void setReuseAddress(boolean fReuse) + throws SocketException + { + m_delegate.setReuseAddress(fReuse); + } + + /** + * {@inheritDoc} + */ + public void setSendBufferSize(int cb) + throws SocketException + { + m_delegate.setSendBufferSize(cb); + } + + /** + * {@inheritDoc} + */ + public void setSoLinger(boolean fLinger, int cSecs) + throws SocketException + { + m_delegate.setSoLinger(fLinger, cSecs); + } + + /** + * {@inheritDoc} + */ + public void setSoTimeout(int cMillis) + throws SocketException + { + m_delegate.setSoTimeout(cMillis); + } + + /** + * {@inheritDoc} + */ + public void setTcpNoDelay(boolean fNoDelay) + throws SocketException + { + m_delegate.setTcpNoDelay(fNoDelay); + } + + /** + * {@inheritDoc} + */ + public void setTrafficClass(int nClass) + throws SocketException + { + m_delegate.setTrafficClass(nClass); + } + + /** + * {@inheritDoc} + */ + public void shutdownInput() + throws IOException + { + m_delegate.shutdownInput(); + } + + /** + * {@inheritDoc} + */ + public void shutdownOutput() + throws IOException + { + m_delegate.shutdownOutput(); + } + + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return m_delegate.toString(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The delegate socket. + */ + protected Socket m_delegate; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperSocketChannel.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperSocketChannel.java new file mode 100644 index 0000000000000..2b2db90aad63a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/WrapperSocketChannel.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.internal.net; + +import java.io.IOException; + +import java.net.SocketOption; +import java.nio.ByteBuffer; + +import java.nio.channels.SocketChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.spi.SelectorProvider; + +import java.net.Socket; +import java.net.SocketAddress; +import java.util.Set; + + +/** +* Wrapper SocketChannel implementation that delegates all operations to a +* delegate SocketChannel. +* +* @author mf 2010.05.19 +*/ +public class WrapperSocketChannel + extends SocketChannel + implements WrapperSelector.WrapperSelectableChannel + { + // ----- constructors --------------------------------------------------- + + public WrapperSocketChannel(SocketChannel channel, SelectorProvider provider) + { + super(provider); + + f_delegate = channel; + f_socket = wrapSocket(channel.socket()); + } + + + // ----- WrapperSocketChannel methods ----------------------------------- + + /** + * Produce a wrapper around the specified socket. + * + * @param socket the socket to wrap + * @return the wrapper socket + */ + protected Socket wrapSocket(Socket socket) + { + return new WrapperSocket(socket) + { + public SocketChannel getChannel() + { + return WrapperSocketChannel.this; + } + }; + } + + + // ----- SocketChannel methods ------------------------------------------ + + /** + * Unsupported. + * + * @return never + * + * @throws UnsupportedOperationException not supported + */ + public static SocketChannel open() + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + public Socket socket() + { + return f_socket; + } + + /** + * {@inheritDoc} + */ + public boolean isConnected() + { + return f_delegate.isConnected(); + } + + /** + * {@inheritDoc} + */ + public boolean isConnectionPending() + { + return f_delegate.isConnectionPending(); + } + + /** + * {@inheritDoc} + */ + public boolean connect(SocketAddress remote) + throws IOException + { + return f_delegate.connect(remote); + } + + /** + * {@inheritDoc} + */ + public boolean finishConnect() + throws IOException + { + return f_delegate.finishConnect(); + } + + /** + * {@inheritDoc} + */ + public int read(ByteBuffer dst) + throws IOException + { + return f_delegate.read(dst); + } + + /** + * {@inheritDoc} + */ + public long read(ByteBuffer[] dsts, int offset, int length) + throws IOException + { + return f_delegate.read(dsts, offset, length); + } + + /** + * {@inheritDoc} + */ + public int write(ByteBuffer src) + throws IOException + { + return f_delegate.write(src); + } + + /** + * {@inheritDoc} + */ + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException + { + return f_delegate.write(srcs, offset, length); + } + + /** + * {@inheritDoc} + */ + protected void implCloseSelectableChannel() + throws IOException + { + f_delegate.close(); + } + + /** + * {@inheritDoc} + */ + protected void implConfigureBlocking(boolean block) + throws IOException + { + f_delegate.configureBlocking(block); + } + + @Override + public SocketChannel bind(SocketAddress local) + throws IOException + { + return f_delegate.bind(local); + } + + @Override + public SocketChannel setOption(SocketOption name, T value) + throws IOException + { + return f_delegate.setOption(name, value); + } + + @Override + public SocketChannel shutdownInput() + throws IOException + { + return f_delegate.shutdownInput(); + } + + @Override + public SocketChannel shutdownOutput() + throws IOException + { + return f_delegate.shutdownOutput(); + } + + @Override + public SocketAddress getRemoteAddress() + throws IOException + { + return f_delegate.getRemoteAddress(); + } + + // ----- NetworkChannel methods ----------------------------------------- + + @Override + public SocketAddress getLocalAddress() + throws IOException + { + return f_delegate.getLocalAddress(); + } + + @Override + public T getOption(SocketOption name) + throws IOException + { + return f_delegate.getOption(name); + } + + @Override + public Set> supportedOptions() + { + return f_delegate.supportedOptions(); + } + + + // ----- WrapperSelectableChannel methods ------------------------------- + + /** + * {@inheritDoc} + */ + public WrapperSelector.WrapperSelectionKey registerInternal( + WrapperSelector selector, int ops, Object att) + throws IOException + { + return new WrapperSelector.WrapperSelectionKey(selector, f_delegate.register( + selector.getDelegate(), ops), att) + { + public SelectableChannel channel() + { + return WrapperSocketChannel.this; + } + }; + } + + + // ----- data memberse -------------------------------------------------- + + /** + * The delegate SocketChannel. + */ + protected final SocketChannel f_delegate; + + /** + * The associated WrapperSocket. + */ + protected final Socket f_socket; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/AbstractSocketBus.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/AbstractSocketBus.java new file mode 100644 index 0000000000000..f216a3cb3876b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/AbstractSocketBus.java @@ -0,0 +1,3524 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net.socketbus; + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.base.Collector; +import com.oracle.coherence.common.base.Continuation; +import com.oracle.coherence.common.base.Disposable; +import com.oracle.coherence.common.collections.Arrays; +import com.oracle.coherence.common.internal.continuations.AbstractContinuationFrame; +import com.oracle.coherence.common.internal.continuations.Continuations; +import com.oracle.coherence.common.internal.continuations.WrapperContinuation; +import com.oracle.coherence.common.net.SafeSelectionHandler; +import com.oracle.coherence.common.net.SelectionService; +import com.oracle.coherence.common.net.Sockets; +import com.oracle.coherence.common.net.exabus.Bus; +import com.oracle.coherence.common.net.exabus.EndPoint; +import com.oracle.coherence.common.net.exabus.Event; +import com.oracle.coherence.common.net.exabus.util.SimpleEvent; +import com.oracle.coherence.common.net.exabus.util.UrlEndPoint; + +import java.io.IOException; + +import java.net.*; + +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import java.util.*; + +import com.oracle.coherence.common.util.SafeClock; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.Lock; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import javax.net.ssl.SSLException; + +import java.util.zip.CRC32; + + +/** + * AbstractSocketBus provides a common base class for Socket based Bus + * implementations. + *

+ * This abstract implementation handles the underlying connection management. + * + * @author mf 2010.11.2 + */ +public abstract class AbstractSocketBus + implements Bus + { + // ----- constructors --------------------------------------------------- + + /** + * Create an AbstractSocketBus around a ServerSocketChannel + * + * @param driver the SocketDriver used to produce the bus sockets + * @param pointLocal the local EndPoint + * + * @throws IOException if an I/O error occurs + */ + public AbstractSocketBus(SocketBusDriver driver, UrlEndPoint pointLocal) + throws IOException + { + f_driver = driver; + + String sProtocol = getProtocolName(); + if (!sProtocol.equals(pointLocal.getProtocol())) + { + throw new IllegalArgumentException("unsupported protocol: " + + pointLocal.getProtocol()); + } + + ServerSocketChannel chan = driver.getDependencies().getSocketProvider() + .openServerSocketChannel(); + Sockets.configureBlocking(chan, false); + configureSocket(chan.socket()); + + chan.socket().bind(pointLocal.getAddress()); + + f_nDropRatio = driver.getDependencies().getDropRatio(); + f_nCorruptionRatio = driver.getDependencies().getCorruptionRatio(); + f_fCrc = driver.getDependencies().isCrcEnabled(); + f_channelServer = chan; + + // as the supplied endpoint may have been wildcard and/or ephemeral, + // re-resolve based on what we actually bound to + m_pointLocal = driver.resolveBindPoint(pointLocal, chan.socket()); + } + + + // ----- Bus interface -------------------------------------------------- + + /** + * {@inheritDoc} + */ + public EndPoint getLocalEndPoint() + { + return m_pointLocal; + } + + /** + * {@inheritDoc} + */ + public void open() + { + Lock lock = f_lockState.writeLock(); + lock.lock(); + boolean fInterrupted = isInterrupted(); + try + { + verifyState(BusState.INITIAL); + m_nState = BusState.OPEN; + + onOpen(); + + try + { + ServerSocketChannel channel = f_channelServer; + getSelectionService().register(f_channelServer, new AcceptHandler(channel)); + } + catch (IOException e) + { + // We can't just close the bus, there is no concept of an unsolicited close, we are still bound to + // the port, just leave it be. + getLogger().log(makeExceptionRecord(Level.SEVERE, e, + "{0} ServerSocket failure; no new connection will be accepted", getLocalEndPoint())); + } + } + finally + { + lock.unlock(); + if (fInterrupted) + { + Thread.currentThread().interrupt(); + } + } + } + + /** + * {@inheritDoc} + */ + public void close() + { + Lock lock = f_lockState.writeLock(); + lock.lock(); + boolean fInterrupted = isInterrupted(); + try + { + if (m_nState.ordinal() >= BusState.CLOSING.ordinal()) + { + return; // close is idempotent + } + verifyState(BusState.OPEN); + + // stop allowing new connections + m_nState = BusState.CLOSING; + + Map mapConn = f_mapConnections; + + // identify how many things we have to "close" before we can put out the + // CLOSE event + final AtomicInteger cConnections = new AtomicInteger(mapConn.size() + 1); + + // close the server socket + getSelectionService().invoke(f_channelServer,new Runnable() + { + public void run() + { + try + { + f_channelServer.close(); + } + catch (IOException e) {} + + if (cConnections.decrementAndGet() == 0) + { + onClose(); + } + } + }, /*cMillisDelay*/ 0); + + // release all open connections + for (Connection conn : mapConn.values()) + { + synchronized (conn) + { + conn.scheduleShutdown(null, /*fRelease*/ true, new Continuation() + { + @Override + public void proceed(Void v) + { + if (cConnections.decrementAndGet() == 0) + { + onClose(); + } + } + }); + } + } + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + finally + { + lock.unlock(); + if (fInterrupted) + { + Thread.currentThread().interrupt(); + } + } + } + + /** + * {@inheritDoc} + */ + public void connect(EndPoint peer) + { + Lock lock = f_lockState.readLock(); + lock.lock(); + boolean fInterrupted = isInterrupted(); + try + { + verifyState(BusState.OPEN); + + if (getLocalEndPoint().equals(peer)) + { + // Support for self-connect if desired needs to be added in + // derived classes, there is no reason to push those bits through + // the network stack + throw new IllegalArgumentException("SocketBus does not support connections to self"); + } + + Connection conn = makeConnection(verifyEndPoint(peer)); + synchronized (conn) + { + if (f_mapConnections.putIfAbsent(peer, conn) == null) + { + conn.connect(); + } + else + { + // there is already an existing Connection for this peer + // and it has not reached the RELEASE state in a visible way + // so we can't replace it. This is not an error, it is the + // equivalent, of a double connect, i.e. a no-op + conn.dispose(); + } + } + } + finally + { + lock.unlock(); + if (fInterrupted) + { + Thread.currentThread().interrupt(); + } + } + } + + /** + * {@inheritDoc} + */ + public void disconnect(EndPoint peer) + { + Connection conn = ensureConnection(peer); + synchronized (conn) + { + conn.ensureValid().scheduleDisconnect(null); + } + } + + /** + * {@inheritDoc} + */ + public void release(EndPoint peer) + { + Connection conn = ensureConnection(peer); + synchronized (conn) + { + conn.ensureValid().scheduleShutdown(null, /*fRelease*/ true, /*continuation*/ null); + } + } + + @Override + public String toString(EndPoint peer) + { + try + { + return ensureConnection(peer).toString(); + } + catch (Throwable e) + { + return "unknown peer " + peer; + } + } + + /** + * {@inheritDoc} + */ + public void flush() + { + BusState nState = m_nState; + if (nState != BusState.OPEN && nState != BusState.CLOSING) + { + throw new IllegalStateException("invalid bus state: " + nState); + } + + boolean fInterrupted = isInterrupted(); + try + { + // flush the connections in random order, this helps prevent one heavily used connection + // from starving others if it happens to exist earlier in the unordered set. + Connection[] aConn = f_setFlush.toArray(EMPTY_CONNECTION_ARRAY); + Arrays.shuffle(aConn); + + for (Connection conn : aConn) + { + conn.optimisticFlush(); // it is up to the connection to remove itself from the flush set + } + } + finally + { + if (fInterrupted) + { + Thread.currentThread().interrupt(); + } + } + } + + /** + * {@inheritDoc} + */ + public void setEventCollector(Collector collector) + { + Lock lock = f_lockState.readLock(); + lock.lock(); + try + { + verifyState(BusState.INITIAL); + m_collectorEvent = collector; + } + finally + { + lock.unlock(); + } + } + + /** + * {@inheritDoc} + */ + public Collector getEventCollector() + { + return m_collectorEvent; + } + + + + // ----- AbstractSocketBus interface ------------------------------------ + + /** + * Schedule a task for future execution. + * + * @param proc the task to run + * @param cMillis the delay before execution, or 0 for immediate + * + * @throws IllegalStateException if the Bus is not open + */ + protected void scheduleTask(final Runnable proc, long cMillis) + { + scheduleTask(f_channelServer, proc, cMillis); + } + + /** + * Schedule a task for future execution. Unlike scheduleTask, this variant will run the task even + * if the bus has been closed + * + * @param proc the task to run + * @param cMillis the delay before execution, or 0 for immediate + * + * @throws IllegalStateException if the Bus is not open + */ + protected void scheduleUnsafeTask(final Runnable proc, long cMillis) + { + scheduleUnsafeTask(f_channelServer, proc, cMillis); + } + + /** + * Schedule a task for future execution. + * + * @param chan the channel that the task is associated with + * @param proc the task to run + * @param cMillis the delay before execution, or 0 for immediate + * + * @throws IllegalStateException if the Bus is not open + */ + protected void scheduleTask(final SelectableChannel chan, final Runnable proc, long cMillis) + { + BusState nState = m_nState; + if (nState != BusState.OPEN && nState != BusState.CLOSING) + { + throw new IllegalStateException("invalid bus state: " + nState); + } + + scheduleUnsafeTask(chan, new Runnable() + { + @Override + public void run() + { + if (m_nState != BusState.CLOSED) + { + proc.run(); + } + } + }, cMillis); + } + + /** + * Schedule a task for future execution. Unlike scheduleTask, this variant will run the task even + * if the bus has been closed + * + * @param chan the channel that the task is associated with + * @param proc the task to run + * @param cMillis the delay before execution, or 0 for immediate + * + * @throws IllegalStateException if the Bus is not open + */ + protected void scheduleUnsafeTask(final SelectableChannel chan, final Runnable proc, long cMillis) + { + try + { + getSocketDriver().getDependencies().getSelectionService().invoke(chan, + proc, cMillis); + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + } + + /** + * Aggressively tear stop the bus instance. + *

+ * The intent of this method is not to properly shutdown (close) the bus, but stop a bus which + * has entered an "unrecoverable" state. Specifically we are trying to protect our peers not us. + */ + protected void halt() + { + try + { + f_channelServer.close(); + } + catch (Exception e) {} + + for (Connection conn : getConnections()) + { + try + { + conn.m_channel.close(); + } + catch (Exception e) {} + } + } + + /** + * Determine if a connection drop should be simulated + * + * @return true if the connection has been dropped + */ + private boolean checkDrop(SocketChannel channel) + { + if (f_nDropRatio != 0 && ThreadLocalRandom.current().nextInt(Math.abs(f_nDropRatio)) == 0) + { + closeChannel(channel); + return true; + } + return false; + } + + /** + * Determine if a data corruption should be simulated, if so force it. + */ + private void checkForceCorruption(ByteBuffer buffer, int cb) + { + Random random = ThreadLocalRandom.current(); + + if (f_nCorruptionRatio != 0 && random.nextInt(Math.abs(f_nCorruptionRatio)) == 0) + { + int nCorrupt = buffer.position() - (random.nextInt(cb) + 1); + + buffer.put(nCorrupt, (byte) random.nextInt()); + } + } + + /** + * Return the first buffer that has bytes remaing, or null if none match. + * + * @return the first buffer that has bytes remaing, or null if none match. + */ + private ByteBuffer getFirstAvailableForCorruption(ByteBuffer[] aBuffer, int of) + { + int c = aBuffer.length; + + for (; of < c && !aBuffer[of].hasRemaining(); ++of) + {} + + return of < c ? aBuffer[of] : null; + } + + /** + * For debugging purposes only. + * + * Close the underlying TCP socket associated with the specified peer. + * This allows for testing of connection reestablishment. + * + * @param sPeerName the peer to act on, or null for all + * @param nClose the type of close to perform, see CLOSE_SOCKET* + */ + public void sever(String sPeerName, int nClose) + { + Connection[] aConn; + + if (sPeerName == null) + { + aConn = f_mapConnections.values().stream().toArray(Connection[]::new); + } + else + { + aConn = new Connection[] {ensureConnection(f_driver.getDepot().resolveEndPoint(sPeerName))}; + } + + for (Connection conn : aConn) + try + { + switch (nClose) + { + case SOCKET_SHUTDOWN_INPUT: + conn.m_channel.shutdownInput(); + break; + + case SOCKET_SHUTDOWN_OUTPUT: + conn.m_channel.shutdownOutput(); + break; + + case SOCKET_SHUTDOWN_INPUT_OUTPUT: + conn.m_channel.shutdownInput(); + conn.m_channel.shutdownOutput(); + break; + + case SOCKET_DROP_OUTPUT: + conn.m_fDropOutput = true; + // Note, to safely set this back to false we'd need to ensure we could do it in such a + // way that we close the current channel, then set to false, then migrate, otherwise + // some random bytes could make it on the wire + break; + + case CLOSE_SOCKET: + default: + conn.m_channel.close(); + break; + } + } + catch (IOException e) {} + } + + public static final int CLOSE_SOCKET = 0; + public static final int SOCKET_SHUTDOWN_INPUT = 1; + public static final int SOCKET_SHUTDOWN_OUTPUT = 2; + public static final int SOCKET_SHUTDOWN_INPUT_OUTPUT = 3; + public static final int SOCKET_DROP_OUTPUT = 4; + + /** + * Close the specified channel + * + * @param chan the channel to close + */ + protected static void closeChannel(SelectableChannel chan) + { + try + { + chan.close(); + } + catch (IOException e) {} + } + + /** + * Called once a bus has been opened. + * + * AbstractSocketBus.onOpen will emit the OPEN event and must be called. + */ + protected void onOpen() + { + final long cMillisHeartbeat = f_driver.getDependencies().getHeartbeatMillis(); + if (cMillisHeartbeat > 0) + { + scheduleTask(new Runnable() + { + @Override + public void run() + { + // reschedule self for next periodic heartbeat + scheduleTask(this, cMillisHeartbeat); + + getConnections().forEach(Connection::heartbeat); + } + }, cMillisHeartbeat); + } + + final long cMillisAckTimeout = f_driver.getDependencies().getAckTimeoutMillis(); + final long cMillisAckFatalTimeout = f_driver.getDependencies().getAckFatalTimeoutMillis(); + if (cMillisAckTimeout > 0 || cMillisAckFatalTimeout > 0) + { + long cIntervalMillis = Math.max(100, + (cMillisAckTimeout == 0 + ? cMillisAckFatalTimeout + : cMillisAckFatalTimeout == 0 + ? cMillisAckTimeout + : Math.min(cMillisAckTimeout, cMillisAckFatalTimeout)) / 20); + + scheduleTask(new Runnable() + { + @Override + public void run() + { + // reschedule self for next periodic health check + scheduleTask(this, cIntervalMillis); + + long ldtNow = SafeClock.INSTANCE.getSafeTimeMillis(); + + getRegisteredConnections().forEach(conn -> conn.checkHealth(ldtNow)); + } + }, cIntervalMillis); + } + + EndPoint epThis = getLocalEndPoint(); + getLogger().log(makeRecord(Level.FINER, "{0} opened using {1}", + epThis, f_channelServer.socket())); + + emitEvent(new SimpleEvent(Event.Type.OPEN, epThis)); + } + + /** + * Called as part of the closing the bus. + * + * AbstractSocketBus.onClose will emit the CLOSE event and must be called + */ + protected void onClose() + { + EndPoint epThis = getLocalEndPoint(); + getLogger().log(makeRecord(Level.FINER, "{0} closed using {1}", + epThis, f_channelServer.socket())); + + m_nState = BusState.CLOSED; + emitEvent(new SimpleEvent(Event.Type.CLOSE, epThis)); + } + + /** + * Return a collection containing all the currently registered connections. + * + * @return the current connections + */ + protected Collection getRegisteredConnections() + { + return f_mapConnections.values(); + } + + protected String getDescription() + { + StringBuilder sb = new StringBuilder() + .append(getLocalEndPoint()) + .append(", state=").append(m_nState); + + ConcurrentMap mapCon = f_mapConnections; + int cConnections = 0; + int cActive = 0; + + for (Connection conn : mapCon.values()) + { + ++cConnections; + if (conn.m_state == ConnectionState.ACTIVE) + { + ++cActive; + } + } + + sb.append(", connections "); + int cCon = mapCon.size(); + sb.append("["); + for (Connection conn : mapCon.values()) + { + sb.append("\n\t").append(conn); + } + sb.append("]\n"); + sb.append("active=").append(cActive).append('/').append(cConnections); + + return sb.toString(); + } + + // ----- Object interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return new StringBuilder() + .append(getClass().getSimpleName()).append('(') + .append(getDescription()) + .append(')').toString(); + } + + + // ----- AbstractSocketBus helpers -------------------------------------- + + /** + * Return true if the calling thread is currently interrupted, and clear that state. + * + * @return true if the calling thread is currently interrupted. + */ + protected boolean isInterrupted() + { + return Blocking.interrupted(); + } + + /** + * Return the bus's logger. + * + * @return the bus's logger + */ + protected Logger getLogger() + { + return getSocketDriver().getDependencies().getLogger(); + } + + /** + * Construct a log record. + * + * @param level the log level + * @param sMsg the message + * @param oaParams the parameters + * + * @return the record + */ + protected LogRecord makeRecord(Level level, String sMsg, Object ... oaParams) + { + LogRecord rec = new LogRecord(level, sMsg); + rec.setParameters(oaParams); + return rec; + } + + /** + * Construct a log record with an exception. + * + * @param level the log level + * @param t the exception + * @param sMsg the message + * @param oaParams the parameters + * + * @return the record + */ + protected LogRecord makeExceptionRecord(Level level, Throwable t, String sMsg, Object ... oaParams) + { + LogRecord rec = makeRecord(level, sMsg, oaParams); + rec.setThrown(t); + return rec; + } + + /** + * Add a Connection to the set of connections awaiting a flush. + * + * @param conn the connection to add + */ + protected void addFlushable(Connection conn) + { + f_setFlush.add(conn); + } + + /** + * Remove a Connection from the flushable set. + * + * @param conn the connection to remove + */ + protected void removeFlushable(Connection conn) + { + f_setFlush.remove(conn); + } + + /** + * Return true iff the specified connection is currently in the flushable set. + * + * @param conn the connection + * + * @return true iff the connection is in the flushable set + */ + protected boolean isFlushable(Connection conn) + { + return f_setFlush.contains(conn); + } + + /** + * Emit the specified Event to the Event Collector. + * + * @param event the event to emit + */ + protected void emitEvent(Event event) + { + Collector coll = m_collectorEvent; + if (coll != null) + { + try + { + coll.add(event); + coll.flush(); + } + catch (Throwable t) + { + // TODO: disconnect?, log? + } + } + } + + /** + * Add the specified Event to the Event Collector. + *

+ * This method does not flush the collector. + * + * @param event the event to add + */ + protected void addEvent(Event event) + { + Collector coll = m_collectorEvent; + if (coll != null) + { + try + { + coll.add(event); + } + catch (Throwable t) + { + // TODO: disconnect?, log? + } + } + } + + /** + * Flush the event collector. + */ + protected void flushEvents() + { + Collector coll = m_collectorEvent; + if (coll != null) + { + try + { + coll.flush(); + } + catch (Throwable t) + { + // TODO: disconnect?, log? + } + } + } + + /** + * Return the currently managed connections. + * + * @return the currently managed connections + */ + protected Collection getConnections() + { + return f_mapConnections.values(); + } + + /** + * Configure the specified Socket + * + * @param socket the socket to configure + * + * @throws IOException on an I/O error + */ + protected void configureSocket(Socket socket) + throws IOException + { + Sockets.configure(socket, getSocketDriver().getDependencies().getSocketOptions()); + } + + /** + * Configure the specified ServerSocket + * + * @param socket the ServerSocket to configure + * + * @throws IOException on an I/O error + */ + protected void configureSocket(ServerSocket socket) + throws IOException + { + Sockets.configure(socket, getSocketDriver().getDependencies().getSocketOptions()); + } + + /** + * Verify that the bus is in a given state. + * + * @param nState the requite state + * + * @throws IllegalStateException if the bus is not in the specified state + */ + protected void verifyState(BusState nState) + { + BusState nStateCurr = m_nState; + if (nStateCurr != nState) + { + throw new IllegalStateException("invalid bus state: required " + nState + + ", actual " + nStateCurr); + } + } + + /** + * Verify that the supplied EndPoint is a usable EndPoint + * + * @param peer the EndPoint to verify + * + * @return the UrlEndPoint + */ + protected UrlEndPoint verifyEndPoint(EndPoint peer) + { + if (f_driver.isSupported(peer) && ((UrlEndPoint) peer).getProtocol().equals(getProtocolName())) + { + return (UrlEndPoint) peer; + } + throw new IllegalArgumentException("unsupported EndPoint " + peer); + } + + /** + * Return the SelectionService for this Bus. + * + * @return the SelectionService for this Bus. + */ + protected SelectionService getSelectionService() + { + return getSocketDriver().getDependencies().getSelectionService(); + } + + /** + * Return the SocketDriver for this bus. + * + * @return the SocketDriver + */ + protected SocketBusDriver getSocketDriver() + { + return f_driver; + } + + /** + * Return the bus connection for the specified EndPoint. + * + * @param peer the EndPoint to ensure + * + * @return the Connection + * + * @throws IllegalStateException if the bus is not open + * @throws IllegalArgumentException if the connection does not exist + */ + protected Connection ensureConnection(EndPoint peer) + { + Connection conn = f_mapConnections.get(peer); + if (conn == null) + { + verifyState(BusState.OPEN); + throw new IllegalArgumentException("unknown peer " + peer); + } + return conn; + } + + /** + * Return the protocol identifier. + * + * @return the protocol identifier + */ + protected int getProtocolIdentifier() + { + return getClass().getName().hashCode() ^ + getSocketDriver().getClass().getName().hashCode(); + } + + /** + * Return the protocol name. + * + * @return the protocol name + */ + protected String getProtocolName() + { + return getClass().getSimpleName(); + } + + /** + * Return minimum protocol version understood by this implementation. + * + * @return the minimum protocol version + */ + protected short getMinimumProtocolVersion() + { + return 0; + } + + /** + * Return maximum protocol version understood by this implementation. + * + * @return the maximum protocol version + */ + protected short getMaximumProtocolVersion() + { + // version 1 adds reconnect support + // version 2 adds identity + // version 3 adds ability to request a remote heap dump on sync + // version 4 sends local and remote ID for CONNECT_MIGRATE, and pads an ID spot for CONNECT_NEW but always sends 0 + // version 5 change the message size to long, add checksum for message body and message header + return 5; + } + + // ----- ConnectionState ------------------------------------------------ + + /** + * ConnectionState represents the state of the underlying Connection. + */ + protected enum ConnectionState + { + /** + * CONNECT event emitted but socket has yet to connect (may not even exist) + */ + OPEN, + + /** + * Indicates a usable connection, we can exchange messages. + */ + ACTIVE, + + /** + * DISCONNECT event emitted, no more exchanges allowed, SelectionService + * is no longer managing the channel. + */ + DEFUNCT, + + /** + * RELEASE event emitted, all done. + */ + FINAL + } + + // ----- HandshakePhase -------------------------------------------------- + + /** + * HandshakePhase represents the state of the handshake protocol. + */ + protected enum HandshakePhase + { + /** + * The negotiation phase of the handshake identifies the basic + * protocol, ensuring that before reading any further into the stream + * that the other end speaks the same language, i.e. we've connected + * to another SocketBus. + *

+ * The format is: + *

    + *
  • 4B (int) protocol identifier
  • + *
  • 2B (short) min version
  • + *
  • 2B (short) max version
  • + *
  • 2B (short) name char length
  • + *
  • ?B (char[]) name
  • + *
+ */ + NEGOTIATE, + + /** + * The introduction phase waits for the canonical name of the peer. + * The length of which was obtained in the identification phase. + */ + INTRODUCE, + + /** + * The accept phase involves each peer sending a single (otherwise + * useless) byte to accept the connection. Until this byte has been + * received the connection has not been accepted, and additional data + * should not be sent. + */ + ACCEPT, + + /** + * The abandon phase is entered only if the bus decides to not + * pursue a connection with the peer. Once all IO required for this + * phase is completed the channel will be closed. + * + * This can happen because it already is the the process of opening + * the same, and it is of higher priority. Rather then rejecting the + * connection by closing it, it simply waits for the peer, to also + * realize the collision, and to accept the higher priority EndPoint's + * connection. At this point it will close its connection, allowing the + * higher priority peer to do the same. If we were to actively reject + * the connection, the peer could see this before realizing the collision + * and emit a DISCONNECT event which is not what we want. + * + * This can also happen as a result of a peer trying to reconnect a + * connection which is unknown to this bus. + */ + ABANDON + } + + + // ----- Connection ----------------------------------------------------- + + /** + * Factory pattern method for instantiating Connection objects. + * + * @param peer the peer + * + * @return the Connection + */ + protected abstract Connection makeConnection(UrlEndPoint peer); + + /** + * Connection contains the state associated with the connection to each + * connected peer. + */ + protected abstract class Connection + implements SelectionService.Handler, GatheringByteChannel, + ScatteringByteChannel, Disposable + { + // ----- constructors ------------------------------------------- + + /** + * Construct a Connection for the specified EndPoint. + * + * @param peer the remote end of the connection + */ + public Connection(UrlEndPoint peer) + { + this.f_peer = peer; + if (f_fCrc) + { + f_crcRx = new CRC32(); + f_crcTx = new CRC32(); + getLogger().log(makeRecord(Level.FINER, "Packet corruption detection enabled for connection {0} to {1}", + getLocalEndPoint(), peer)); + } + } + + + // ----- Connection interface ----------------------------------- + + /** + * Open the connection. + * + * @throws IOException if an IO error occurs + */ + protected void open() + throws IOException + { + if (m_state != null) + { + throw new IllegalStateException("state = " + m_state); + } + + m_state = ConnectionState.OPEN; + + EndPoint peer = f_peer; + getLogger().log(makeRecord(Level.FINER, "{0} opening connection with {1} using {2}", getLocalEndPoint(), peer, + m_channel.socket())); + emitEvent(new SimpleEvent(Event.Type.CONNECT, peer)); + } + + /** + * Return if the connection is valid + * + * @return true iff the connection is valid + */ + protected boolean isValid() + { + ConnectionState state = m_state; + if (state == null) + { + // we can get here for LightMessageBus if there is a concurrent connect and the remote side + // wins; since LWMB doesn't synchronized in send we have to double check before declaring + // a failure before open; simply making m_state volatile would not be sufficient because when + // the connection comes from the remote side it is inserted into the map before it is opened + // but is inserted while under sync. It still must be volatile though for the double-check + // to be safe + synchronized (this) + { + state = m_state; + if (state == null) + { + // ok, this really is an invalid connection + return false; + } + // else; now we have a stable view of the state + } + } + + return state != ConnectionState.FINAL; + } + + /** + * Ensure that the connection is usable. + * + * @return this + */ + public Connection ensureValid() + { + if (isValid()) + { + return this; + } + + throw new IllegalArgumentException("connection to " + f_peer + " is not open, in state " + m_state); + } + + /** + * Perform the actual connect to the peer + * + * @throws IOException on an I/O error + */ + private void connect() + { + ConnectionState state = m_state; + if (state != null && state.ordinal() >= ConnectionState.DEFUNCT.ordinal()) + { + throw new IllegalStateException("state = " + m_state); + } + else if (m_channel != null && m_channel.isOpen()) + { + // reconnect of open channel + throw new IllegalStateException(); + } + + SocketChannel channel; + try + { + m_channel = channel = getSocketDriver().getDependencies() + .getSocketProvider().openSocketChannel(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + + // configure and connect + try + { + Sockets.configureBlocking(channel, false); + configureSocket(channel.socket()); + channel.connect(f_peer.getAddress()); + } + catch (IOException e) + { + onException(e); + } + + try + { + // emit connect event; allowing operations to be scheduled against the endpoint + if (state == null) + { + open(); + } + // else this is a re-connect and open has already been issued + + // register initial I/O handler + SelectionService.Handler handler = new HandshakeHandler(channel, this); + getSelectionService().register(channel, handler); + m_handler = handler; + } + catch (IOException e) + { + scheduleDisconnect(e); + } + } + + /** + * Schedule a disconnect. + * + * @param eReason the reason for the disconnect + */ + public void scheduleDisconnect(Throwable eReason) + { + scheduleShutdown(eReason, false, /*continuation*/ null); + } + + /** + * Register a Runnable with the SelectionService + * to perform the disconnect logic and optionally release it. It schedules + * the disconnect to happen on the SelectionService thread where the + * connection is registered. + * + * @param eReason the reason for the disconnect or null + * @param fRelease true iff the connection should also be released + * @param continuation continuation once the operation has completed + */ + public void scheduleShutdown(final Throwable eReason, + final boolean fRelease, Continuation continuation) + { + invoke(new AbstractContinuationFrame(continuation) + { + @Override + public Void call() + { + boolean fEmitDisconnect = false; + HandshakeHandler handlerNext; + + synchronized (Connection.this) + { + handlerNext = m_next; + switch (m_state) + { + case OPEN: + case ACTIVE: + fEmitDisconnect = true; // emit below; outside of sync block + m_state = ConnectionState.DEFUNCT; + //fall through + + case DEFUNCT: + if (fRelease) + { + m_next = null; + m_state = ConnectionState.FINAL; + } + break; + + case FINAL: + // this can occur if there were pending + // jobs when we processed the release + // job + return null; + + default: + throw new IllegalStateException("state = " + m_state); + } + } + + final Continuation continuation = new WrapperContinuation(getContinuation()) + { + @Override + public void proceed(Void v) + { + flushEvents(); + super.proceed(v); + } + }; + + if (fRelease) + { + if (fEmitDisconnect) + { + final HandshakeHandler handlerRelease = handlerNext; + doDisconnect(fEmitDisconnect, eReason, new Continuation() + { + @Override + public void proceed(Void v) + { + doRelease(handlerRelease, continuation); + } + }); + } + else + { + doRelease(handlerNext, continuation); + } + } + else + { + doDisconnect(fEmitDisconnect, eReason, continuation); + } + + return continueAsync(); + } + }); + } + + /** + * Emit any and all receipts + */ + protected abstract void drainReceipts(); + + /** + * Perform disconnect processing. + *

+ * This may be called multiple times. + * + * @param fFirst true iff this is the first disconnect + * @param eReason the reason (if any) for the disconnect + * @param continuation the continuation to run after the disconnect has been performed + */ + protected void doDisconnect(boolean fFirst, Throwable eReason, Continuation continuation) + { + try + { + if (fFirst) + { + onDisconnected(eReason); + } + + try + { + m_channel.close(); // may already be closed + } + catch (IOException e) {} + + + // in case of multiple disconnects force drain of receipts on each pass + // while not required this helps from leaving receipts queue'd until + // release + drainReceipts(); + } + finally + { + Continuations.proceed(continuation, null); + } + } + + /** + * Perform release processing. + * + * @param handlerNext the handler for the next connection + * @param continuation the continuation to run after the release has been performed + */ + protected void doRelease(HandshakeHandler handlerNext, Continuation continuation) + { + try + { + EndPoint peer = getPeer(); + onReleased(); + + // emit queue'd receipts + drainReceipts(); + + // we take out a write lock to handle the cases where a new connection is concurrently + // made for the one we are actively releasing + Lock lockWrite = AbstractSocketBus.this.f_lockState.writeLock(); + lockWrite.lock(); + try + { + if (handlerNext != null && !handlerNext.getChannel().socket().isInputShutdown() && + AbstractSocketBus.this.m_nState == BusState.OPEN) + { + // replace with pending connection + synchronized (handlerNext.m_connection) + { + // In order to ensure that the connection is visible once we emit the + // CONNECT event, the connection must be in the connection map before + // emitting the even. Of course until the CONNECT event is emitted the + // connection isn't usable. To make these two operations atomic we must + // sync on the new connection, put it in the map, and then emit the event. + + // We also don't want to emit the RELEASE event for the old connection + // while it is still in the map, because once RELEASEd new connects are + // allowable. The net result is that we must do a replace, RELEASE, + // open all while sync'd on the new connection + AbstractSocketBus.this.f_mapConnections.replace(peer, handlerNext.m_connection); + getLogger().log(makeRecord(Level.FINER, "{0} releasing connection with {1}", + getLocalEndPoint(), peer)); + addEvent(new SimpleEvent(Event.Type.RELEASE, peer)); + + try + { + handlerNext.m_connection.open(); // emits CONNECT event + getSelectionService().register(handlerNext.getChannel(), handlerNext); + handlerNext.m_connection.m_handler = handlerNext; + } + catch (IOException e) + { + handlerNext.m_connection.scheduleDisconnect(e); + } + } + handlerNext = null; + } + else + { + if (handlerNext != null) + { + handlerNext.close(null); + } + + AbstractSocketBus.this.f_mapConnections.remove(peer); + getLogger().log(makeRecord(Level.FINER, "{0} releasing connection with {1}", + getLocalEndPoint(), peer)); + addEvent(new SimpleEvent(Event.Type.RELEASE, peer)); + } + } + finally + { + lockWrite.unlock(); + } + + if (handlerNext != null) + { + // we didn't proceed with the pending connection + try + { + handlerNext.getChannel().close(); + } + catch (Exception e) {} + } + } + finally + { + Continuations.proceed(continuation, null); + } + } + + /** + * Called as a connection is being disconnected. + * + * @param eReason the cause of the disconnect + */ + public void onDisconnected(Throwable eReason) + { + getLogger().log(makeExceptionRecord( + eReason instanceof SSLException ? Level.WARNING : Level.FINER, eReason, + "{0} disconnected connection with {1}", + getLocalEndPoint(), this)); + + addEvent(new SimpleEvent(Event.Type.DISCONNECT, getPeer(), eReason)); + } + + /** + * Called as a connection is being released. + */ + public void onReleased() + { + removeFlushable(this); + dispose(); + } + + /** + * Called as part of migrating a connection. + * + * The caller must hold synchronization on the Connection while calling this method and + * this method must be run on the SS thread associated with the connection. + */ + public void onMigration() + { + ++m_cMigrations; + } + + @Override + public void dispose() + { + } + + /** + * Flush the connection. + */ + protected abstract void flush(); + + /** + * Issue a heartbeat if necessary + * + * @return true if a heartbeat was issued, false if it was determined one wasn't needed + */ + protected abstract boolean heartbeat(); + + /** + * Check the connection for any ack timeouts. + * + * @param ldtNow the current safe time + */ + protected void checkHealth(long ldtNow) + { + } + + /** + * Perform an optimistic flush, i.e. flush only if the connection is not already being flushed. + * + * Caller's need not be synchronized on the connection when calling this method, though the method + * will synchronize if it determines that it will flush. + */ + public void optimisticFlush() + { + AtomicBoolean lockFlush = f_lockFlush; + + if (lockFlush.compareAndSet(false, true)) + { + synchronized (this) // wait for any concurrent send to complete + { + try + { + ensureValid().flush(); + } + catch (IllegalArgumentException e) + { + // connection may have been released + } + finally + { + // we must unlock while still sync'd to ensure that no thread can add to the send queue + // while we hold the flush lock + lockFlush.set(false); + } + } + } + // else; another thread is actively flushing this connection. Because both conn.send and conn.flush + // are performed while sync'd on the conn we know the other thread's flush will include all data on + // the connection and thus this thread can skip over that connection. The intent of this optimization + // is to avoid blocking on flush when there will be nothing left to flush anyway. This is especially + // important as SocketBus scalability comes primarily from having many connections, with the idea + // that threads are less likely to contend on any given connection. Flushing however involves all + // used threads and would become a high contention point, thus we need to avoid needlessly blocking + // here. + } + + /** + * Return true if some thread is actively waiting to flush this connection. + * + * @return true if some thread is actively waiting to flush this connection + */ + public final boolean isFlushInProgress() + { + return f_lockFlush.get(); + } + + /** + * Force the SelectionService to process this channel. + * + * @throws IOException if an I/O error occurs + * + * @return true iff the wakeup was scheduled + * + * @throws IOException if the connection has been closed + */ + protected boolean wakeup() + throws IOException + { + //COH-19338: m_state is null for new connection before open is called + if (m_state == null) + { + return false; + } + + switch (m_state) + { + case OPEN: + return false; + + case ACTIVE: + synchronized (Connection.this) + { + getSelectionService().register(m_channel, m_handler); + } + return true; + + case DEFUNCT: + case FINAL: + default: + throw new ClosedChannelException(); + } + } + + /** + * Return the send buffer size for the underlying socket. + * + * @return the send buffer size, or -1 if not connected + * + * @throws SocketException if an I/O error occurs + */ + protected int getSendBufferSize() + throws SocketException + { + SocketChannel chan = m_channel; + if (chan == null) + { + return -1; + } + return chan.socket().getSendBufferSize(); + } + + /** + * Return the packet size for this connection. + * + * @return the packet size for this connection, or -1 if not connected + */ + protected int getPacketSize() + { + int cbPacket = m_cbPacket; + if (cbPacket <= 0) + { + SocketChannel chan = m_channel; + if (chan != null) + { + Socket socket = chan.socket(); + if (socket.isBound()) + { + m_cbPacket = cbPacket = Sockets.getMTU(socket); + } + } + } + + return cbPacket; + } + + /** + * Return the receive buffer size for the underlying socket. + * + * @return the receive buffer size, or -1 if not connected + * + * @throws SocketException if an I/O error occurs + */ + protected int getReceiveBufferSize() + throws SocketException + { + SocketChannel chan = m_channel; + if (chan == null) + { + return -1; + } + return chan.socket().getReceiveBufferSize(); + } + + /** + * Return the peer associated with this connection. + * + * @return the peer + */ + public EndPoint getPeer() + { + return f_peer; + } + + /** + * Return the protocol version for this connection. + * + * @return the protocol version or -1 if not net negotiated + */ + public int getProtocolVersion() + { + return m_nProtocol; + } + + /** + * Schedule an invocation against this channel on the SelectionService. + * + * @param runnable the runnable to invoke + */ + protected synchronized void invoke(Runnable runnable) + { + Queue queueDeferred = m_queueDeferred; + if (queueDeferred == null) + { + try + { + getSelectionService().invoke(m_channel, runnable, /*cMillisDelay*/ 0); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + else + { + queueDeferred.add(runnable); + } + } + + // ----- Channel interface ------------------------------------------ + + /** + * Register a new SelectionService.Handler for this connection. + * + * @param handler the handler or null to unregister + * + * @throws IOException if an IO error occurs + */ + public void registerHandler(SelectionService.Handler handler) + throws IOException + { + getSelectionService().register(m_channel, handler); + } + + /** + * {@inheritDoc} + */ + public void close() + { + scheduleDisconnect(null); + } + + /** + * Return true iff the connection has not been released. + * + * @return true iff the connection has not been released + */ + public boolean isOpen() + { + return m_state.ordinal() < ConnectionState.FINAL.ordinal(); + } + + /** + * {@inheritDoc} + */ + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException + { + switch (m_state) + { + case OPEN: + return 0; // not ready yet + + case ACTIVE: + { + SocketChannel chan = m_channel; + long cbWrite = 0; + long cb; + + long cbPre = 0; + for (int i = offset, e = offset + length; i < e; ++i) + { + cbPre += srcs[i].remaining(); + } + + if (m_fDropOutput) // for testing purposes only + { + for (int i = offset, e = offset + length; i < e; ++i) + { + srcs[i].position(srcs[i].limit()); + } + + m_cbWrite += cbPre; + return cbPre; + } + + try + { + int i = offset; + int c = length; + do + { + cbWrite += cb = chan.write(srcs, i, c); + + // According the the JRockit team the underlying + // OS will generally only support a maximum number + // of gather buffers (they said 16). So it is + // possible that an incomplete write was not do to + // lack of buffer space but do to this OS issue. + + // so if we wrote something, and have lots of buffers + // advance through the written ones and try again + while (cb != 0 && c > 0 && !srcs[i].hasRemaining()) + { + ++i; + --c; + } + } + while (cb != 0 && c > 0); + } + catch (IOException ex) + { + // out of paranoia (chan.write doc isn't specific) test to see if any buffer positions were + // updated, we need cbWrite to be correct + long cbPost = 0; + for (int i = offset, e = offset + length; i < e; ++i) + { + cbPost += srcs[i].remaining(); + } + + cbWrite = cbPre - cbPost; + if (cbWrite == 0) + { + throw ex; + } + + // apparently we managed to do some writes before the socket was closed. We either need to + // rewind the buffers we wrote or pretend that we didn't see that the socket was closed. The + // latter is far easier so we take that approach. A subsequent write will of course fail without + // sending anything and then we'll surface the exception + } + + m_cbWrite += cbWrite; + + return cbWrite; + } + + default: + throw new ClosedChannelException(); + } + } + + /** + * {@inheritDoc} + */ + public long write(ByteBuffer[] srcs) + throws IOException + { + switch (m_state) + { + case OPEN: + return 0; // not ready yet + + case ACTIVE: + return write(srcs, 0, srcs.length); + + default: + throw new ClosedChannelException(); + } + } + + /** + * {@inheritDoc} + */ + public int write(ByteBuffer src) throws IOException + { + switch (m_state) + { + case OPEN: + return 0; // not ready yet + + case ACTIVE: + int cb = m_channel.write(src); + m_cbWrite += cb; + return cb; + + default: + throw new ClosedChannelException(); + } + } + + /** + * {@inheritDoc} + */ + public long read(ByteBuffer[] dsts, int offset, int length) + throws IOException + { + switch (m_state) + { + case OPEN: + return 0; // not ready yet + + case ACTIVE: + // TODO: consider insulating against scatter/gather issue + // see write above + + ByteBuffer bufFirstCorrupt = null; + int cbFirst = 0; + + if (f_nCorruptionRatio != 0) + { + bufFirstCorrupt = getFirstAvailableForCorruption(dsts, offset); + cbFirst = bufFirstCorrupt.remaining(); + } + + long cb = m_channel.read(dsts, offset, length); + + if (cb >= 0) + { + if (f_nCorruptionRatio != 0) + { + checkForceCorruption(bufFirstCorrupt, (int) Math.min(cb, cbFirst)); + } + + if (f_nDropRatio == 0 || !checkDrop(m_channel)) + { + m_cbRead += cb; + return cb; + } + } + // else; fall through + + default: + return -1; + } + } + + /** + * {@inheritDoc} + */ + public long read(ByteBuffer[] dsts) throws IOException + { + return read(dsts, 0, dsts.length); + } + + /** + * {@inheritDoc} + */ + public int read(ByteBuffer dst) throws IOException + { + switch (m_state) + { + case OPEN: + return 0; // not ready yet + + case ACTIVE: + int cb = m_channel.read(dst); + if (cb >= 0) + { + if (f_fCrc && f_nCorruptionRatio != 0) + { + checkForceCorruption(dst, cb); + } + + if (f_nDropRatio == 0 || !checkDrop(m_channel)) + { + m_cbRead += cb; + return cb; + } + } + // else; fall through + + default: + return -1; + } + } + + /** + * Migrate this bus connection to a new socket connection. + * + * @param eReason optional exception describing why current connection needs to be replaced + */ + public synchronized void migrate(Throwable eReason) + { + if (getProtocolVersion() == 0) + { + scheduleDisconnect(eReason); + } + else // protocol not yet negotiated or >= 1, in either case we can attempt a migration + { + if (eReason instanceof ConnectException && + ++m_cReconnectAttempts > f_driver.getDependencies().getSocketReconnectLimit()) + { + // we've exhausted our reconnect attempts, disconnect + scheduleDisconnect(eReason); + return; + } + + // delay reconnect on initial connect and to also help avoid an endless conflict + // if both sides were to keep trying to simultaneously reconnect and in doing so invalidate the other + // side's reconnect attempt. + long cMillisDelay = eReason instanceof ConnectException || + getLocalEndPoint().getCanonicalName().compareTo(getPeer().getCanonicalName()) > 0 + ? f_driver.getDependencies().getSocketReconnectDelayMillis() + : 0; + + SocketChannel chan = m_channel; + String sChan = chan.toString(); // to preserve port info for subsequent logging + + closeChannel(chan); + + scheduleUnsafeTask(chan, new Runnable() + { + @Override + public void run() + { + synchronized (Connection.this) + { + if (m_state.ordinal() < ConnectionState.DEFUNCT.ordinal() && chan == m_channel) + { + getLogger().log(makeExceptionRecord(Level.FINER, eReason, + "{0} migrating connection with {1} off of {2} on {3}", + getLocalEndPoint(), getPeer(), sChan, Connection.this)); + + m_eMigrationCause = eReason; + onMigration(); + + // we're sync'd on the connection so nothing new can be scheduled + try + { + getSelectionService().register(chan, null); + m_handler = null; + } + catch (IOException e) {} + + // replaces m_channel, so any subsequent exceptions on the old channel won't make it into this if block + // schedule task to ensure register and connect are processed sequentially + scheduleUnsafeTask(chan, ()-> connect(), cMillisDelay); + } + } + } + }, cMillisDelay); + } + } + + /** + * Called when the channel has been selected. + *

+ * If this method throws an exception it will be handled by {@link + * #onException} + * + * @param nOps the selected ops + * + * @return the new interest set + * + * @throws IOException on an I/O error + */ + protected abstract int onReadySafe(int nOps) + throws IOException; + + /** + * Called in the event that {@link #onReadySafe} resulted in an + * exception. + *

+ * The default implementation simply disconnects the connection. + * + * @param t the exception + * + * @return the new interest set, the default implementation returns 0 + */ + protected int onException(Throwable t) + { + ConnectionState state = m_state; + if (t instanceof IOException && !(t instanceof SSLException) && // don't migrate if the failure was due to security + (state == null || state.ordinal() < ConnectionState.DEFUNCT.ordinal())) + { + migrate(t); + } + else + { + scheduleDisconnect(t); + } + + return 0; + } + + // ----- Handler interface -------------------------------------- + + /** + * {@inheritDoc} + */ + public final int onReady(int nOps) + { + try + { + return onReadySafe(nOps); + } + catch (Throwable t) + { + return onException(t); + } + } + + /** + * Set the protocol version. + * + * @param nProt the version to set + */ + protected void setProtocolVersion(int nProt) + { + int nProtocol = m_nProtocol; + if (nProtocol == -1 || nProtocol == nProt) + { + m_nProtocol = nProt; + } + else + { + throw new IllegalStateException(); + } + } + + + // ----- Object interface --------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + SocketChannel channel = m_channel; + Socket socket = channel == null ? null : channel.socket(); + int cMigrations = m_cMigrations; + return "peer=" + getPeer() + ", state=" + m_state + + ", socket=" + socket + + (cMigrations == 0 ? "" : ", migrations=" + cMigrations) + + ", bytes(in=" + m_cbRead + ", out=" + m_cbWrite + ")" + + ", flushlock " + f_lockFlush.get(); + } + + @Override + public int hashCode() + { + return f_peer.hashCode(); // only because profiler says Object.hashCode is expensive here + } + + // ----- data members ------------------------------------------- + + /** + * The peer's EndPoint. + */ + private final UrlEndPoint f_peer; + + /** + * Atomic indicating if the connection is being flushed. + */ + private final AtomicBoolean f_lockFlush = new AtomicBoolean(); + + /** + * The connect state of this connection. + * + * volatile for LightBus usage see isValid + */ + protected volatile ConnectionState m_state; + + /** + * The identity of this side of the logical connection. The identity is used during reconnects + * to ensure we aren't connecting to a different connection which is simply reusing the ip:port + * + * This is a non-zero value, which is unique across usages of this ip:port. Note it is not unique + * otherwise, and multiple peer connections may share the same identity. + */ + protected final long f_lIdentity = f_atomicIdGenerator.incrementAndGet(); + + /** + * Our peer's identity. Note this must not be changed once set to a non-zero value. + */ + protected long m_lIdentityPeer; + + /** + * The channel connecting this bus to the peer. + */ + private SocketChannel m_channel; + + /** + * For testing purposes only, force the connection to drop all output traffic. + */ + private boolean m_fDropOutput; + + /** + * The packet size for this connection + */ + private int m_cbPacket = -1; + + /** + * A handshaking connection waiting for this connection to be released + */ + private HandshakeHandler m_next; + + /** + * If non-null then any invocations must inserted into the queue rather then + * scheduled with the SelectionService + */ + private Queue m_queueDeferred; + + /** + * The total number of bytes read from the socket. + */ + protected long m_cbRead; + + /** + * The total number of bytes written to the socket. + */ + protected long m_cbWrite; + + /** + * The negotiated protocol version, or -1 if not yet known. + */ + protected int m_nProtocol = -1; + + /** + * The number of connection migrations that have occured. + */ + protected int m_cMigrations; + + /** + * The cause of the last initiated migration. + */ + protected Throwable m_eMigrationCause; + + /** + * The number of sequential reconnect attempts which have been made on this connection. + */ + protected int m_cReconnectAttempts; + + /** + * CRC32 for read thread. + */ + protected CRC32 f_crcRx; + + /** + * CRC32 for write threads. + */ + protected CRC32 f_crcTx; + + /** + * Current HandShakeHandler for this connection. + */ + protected volatile SelectionService.Handler m_handler; + + } + + // ---- HandshakeHandler ------------------------------------------------ + + /** + * HandshakeHandler handles the initial transmissions on a SocketChannel + * as two buses handshake. + */ + protected class HandshakeHandler + extends SafeSelectionHandler + { + /** + * Construct a HandshakeHandler for the give SocketChannel. + * + * @param channel the socket channel + * @param connection the optional connection + */ + public HandshakeHandler(SocketChannel channel, Connection connection) + { + super(channel); + this.m_connection = connection; + + int cbNegotiate = 4 + // (int) protocol id + 2 + // (short) min ver + 2 + // (short) max ver + 2; // (short) name char length + + m_headerIn = ByteBuffer.allocate(cbNegotiate); + + ByteBuffer headerOut = m_headerOut = ByteBuffer.allocate(cbNegotiate); + headerOut.putInt(getProtocolIdentifier()) + .putShort(getMinimumProtocolVersion()) + .putShort(getMaximumProtocolVersion()) + .putShort((short) getLocalEndPoint().getCanonicalName().length()); + headerOut.flip(); + } + + /** + * {@inheritDoc} + */ + public int onReadySafe(int nOps) + throws IOException + { + SocketChannel channel = getChannel(); + Connection connection = m_connection; + ByteBuffer headerOut = m_headerOut; + ByteBuffer headerIn = m_headerIn; + HandshakePhase phase = m_phase; + int nInterest = 0; + + if (channel.isConnectionPending()) + { + if (!channel.finishConnect()) + { + getLogger().log(makeRecord(Level.FINEST, "{0} finishConnect pending for {1} on {2}", + AbstractSocketBus.this.getLocalEndPoint(), + connection.getPeer(), + channel.socket())); + return OP_CONNECT; + } + + connection.m_cReconnectAttempts = 0; + + getLogger().log(makeRecord(Level.FINEST, "{0} socket connected for {1} on {2}", + AbstractSocketBus.this.getLocalEndPoint(), + connection.getPeer(), + channel.socket())); + } + + if (f_nDropRatio > 0 && checkDrop(channel)) + { + throw new IOException("test drop; " + phase); + } + + channel.write(headerOut); + + if ((nOps & OP_READ) != 0 && channel.read(headerIn) < 0) + { + throw new IOException("InputShutdown during handshake " + phase + " in " + headerIn + " out " + headerOut); + } + + if (headerIn.hasRemaining()) + { + nInterest = OP_READ; + } + + if (headerOut.hasRemaining()) + { + nInterest |= OP_WRITE; + } + + if (nInterest == 0) + { + // end of read & write means we're ready for the next phase + getLogger().log(makeRecord(Level.FINEST, "{0} processing {1} handshake for {2} on {3}", + AbstractSocketBus.this.getLocalEndPoint(), + phase, + connection == null ? null : connection.getPeer(), + channel.socket())); + + switch (phase) + { + case NEGOTIATE: + nInterest = onNegotiate(); + break; + + case INTRODUCE: + nInterest = onIntroduce(); + break; + + case ACCEPT: + nInterest = onAccept(); + break; + + case ABANDON: + default: + nInterest = onAbandon(); + break; + } + + if (m_phase != phase) + { + getLogger().log(makeRecord(Level.FINEST, "{0} waiting for {1} handshake for {2} on {3} with interest {4}, {5}B to read, {6}B to write", + AbstractSocketBus.this.getLocalEndPoint(), + m_phase, + m_connection == null ? null : m_connection.getPeer(), + getChannel().socket(), nInterest, m_headerIn.remaining(), m_headerOut.remaining())); + } + } + + return nInterest; + } + + /** + * {@inheritDoc} + */ + public int onException(Throwable eReason) + { + Connection connection = m_connection; + + if (connection == null) + { + close(eReason); + } + else + { + ConnectionState state = connection.m_state; + if (connection.m_channel == getChannel() && + state != null && state.ordinal() < ConnectionState.DEFUNCT.ordinal()) + { + connection.onException(eReason); + } + } + + return 0; + } + + /** + * Process the peer's protocol identification. + * + * @return the new interest set + */ + public int onNegotiate() + { + Connection connection = m_connection; + ByteBuffer headerIn = m_headerIn; + headerIn.flip(); + + // verify protocol + int nId = headerIn.getInt(); + int nIdReq = getProtocolIdentifier(); + if (nId != nIdReq) + { + if (nId >>> 8 == (nIdReq & 0x00FFFFFF)) + { + // Note: very rarely (about one in a million attempts) + // on OS X 10.6 we'll hit a OS X bug which results in the + // first byte off the connection being missing. This has + // been verified in small provably correct tests as well. + nId = nIdReq; + + // reset header to parse the remainder + headerIn.position(3); + } + else // unknown protocol + { + getLogger().log(makeRecord(Level.WARNING, "{0} rejecting connection from {1}" + + " using incompatible protocol id {2}, required {3}", + getLocalEndPoint(), + getChannel().socket().getInetAddress(), + nId, nIdReq)); + close(new IOException("incompatible protocol")); + return 0; + } + } + + // verify version + short nMin = headerIn.getShort(); + short nMax = headerIn.getShort(); + if (nMin > getMaximumProtocolVersion() || + nMax < getMinimumProtocolVersion()) + { + // no overlapping version + getLogger().log(makeRecord(Level.WARNING, "{0} rejecting connection from {1}" + + " using unsupported protocol {2} version " + + "({3} ... {4}), supported ({5} ... {6})", + getLocalEndPoint(), + getChannel().socket().getInetAddress(), + nId, nMin, nMax, getMinimumProtocolVersion(), + getMaximumProtocolVersion())); + close(new IOException("protocol version mismatch")); + return 0; + } + + // we support overlappying versions; select the highest shared version + int nProt = m_nProtocol = Math.min(getMaximumProtocolVersion(), nMax); + + getLogger().log(makeRecord(Level.FINEST, "{0} handshaking with {1}" + + " using protocol {2} version {3}", + getLocalEndPoint(), + getChannel().socket().getInetAddress(), + nId, nProt)); + + m_phase = HandshakePhase.INTRODUCE; + + // prepare outbound introduction + String sName = getLocalEndPoint().getCanonicalName(); + boolean fSendConnect = nProt > 0 && connection != null; // connect type only sent starting with v1 + boolean fSendIdentity = nProt > 1 && fSendConnect; // id only sent starting with v2 + ByteBuffer bufOut = m_headerOut = ByteBuffer.allocate(sName.length() * 2 + + (fSendConnect ? 1 : 0) + + (fSendIdentity ? nProt > 3 ? 16 : 8 : 0)); + for (int i = 0, c = sName.length(); i < c; ++i) + { + bufOut.putChar(sName.charAt(i)); + } + + if (fSendConnect) + { + // note: accepting side doesn't have sufficient information to send the connect type until + // it has received the initiators full introduction. + byte nConnect = connection.m_state == ConnectionState.OPEN && connection.m_lIdentityPeer == 0 + ? CONNECT_NEW : CONNECT_MIGRATE; + bufOut.put(nConnect); + if (fSendIdentity) + { + if (nProt > 3) + { + bufOut.putLong(connection.f_lIdentity) + .putLong(connection.m_lIdentityPeer); + } + else + { + bufOut.putLong(nConnect == CONNECT_NEW ? connection.f_lIdentity : connection.m_lIdentityPeer); + } + } + } + bufOut.flip(); + + // prepare for inbound introduction + m_headerIn = ByteBuffer.allocate(headerIn.getShort() * 2 + + (nProt > 0 ? 1 : 0) + // see connect type note above + (nProt > 3 ? 16 : + nProt > 1 ? 8 : 0)); + + return OP_READ | OP_WRITE; + } + + /** + * Evaluate the introduction. + * + * @return the new interest set + * + * @throws IOException on an I/O error + */ + public int onIntroduce() + throws IOException + { + // We've verified that we can communicate, but we now need to + // ensure that we don't allow multiple concurrent connections + // between two peers at once. Now that we have the protocol + // header we know we've ensured that both sides will know who + // is at the other end of the socket. Each side will ensure that + // they aren't already opening up another socket in the reverse + // direction, and that they don't already have an existing socket. + // In the case that both sides simultaneous open sockets to one + // another the peer with the lower canonical name will win. So here + // we will do one of the following: + // a. Identify that we've initiated this connection and move to + // the accept phase. + // b. Identify that we did not initiate this connection, but that + // we have no outgoing connection to the same peer, and move + // to the accept phase + // c. Identify that we did not initiate this connection, and that + // we do have an outgoing connection to a lower peer. We will + // close our existing socket, and replace it with this one, on + // the same Connection object. + // d. Identify that we did not initiate this connection, and that + // we do have an outgoing connection to the same peer. In this + // case we will "abandon" this connection. It is important + // that we don't close the connection until we know that peer + // has performed option "c" from above. + // e. Identify that we have an active connection to this peer. + // While this may seem like an illegal state it can happen for + // one legitimate reasons. The existing connection may + // have been disconnected on the remote end, and we just haven't + // received the "close" packet yet, and the new "open" packet + // which isn't required to appear in order wrt the close arrived + // first. + + // TODO: now that we have migration support we don't technically + // need to worry about seniority during simultaneous connect, we + // can let any new connection simply close any existing connection + // this will simply trigger a migration. Note it is possible that + // two peers could do this indefinitely, i.e. closing each others + // connections, we may need to add in some migration backoff + ByteBuffer headerIn = m_headerIn; + + headerIn.flip(); + + int nProt = m_nProtocol; + int cbName = headerIn.limit() - + ((nProt > 0 ? 1 : 0) + // connect type + (nProt > 3 ? 16 : + nProt > 1 ? 8 : 0)); // ID + + char[] achName = new char[cbName / 2]; + for (int i = 0, c = achName.length; i < c; ++i) + { + achName[i] = headerIn.getChar(); + } + + final UrlEndPoint peer = f_driver.resolveSocketEndPoint(new String(achName)); + + int nInterest = OP_READ | OP_WRITE; + Connection connection = m_connection; + boolean fInbound = connection == null; + byte nConnect = nProt > 0 ? headerIn.get() : CONNECT_NEW; + long lIdThatRx; + long lIdThisRx; + + if (nProt > 3) + { + lIdThatRx = headerIn.getLong(); + lIdThisRx = headerIn.getLong(); + } + else if (nProt > 1) + { + switch (nConnect) + { + case CONNECT_NEW: + lIdThatRx = headerIn.getLong(); + lIdThisRx = 0; + break; + + case CONNECT_MIGRATE: + lIdThisRx = headerIn.getLong(); + lIdThatRx = 0; + break; + + default: + throw new IOException("protocol error"); + } + } + else + { + lIdThatRx = lIdThisRx = 0; + } + + if (fInbound) + { + // in-bound connection + + // check if we already have an existing connection for this + // peer, this is unlikely, so we will attempt to register + // our new connection at the same time + + final Connection connNew; + + switch (nConnect) + { + case CONNECT_NEW: + { + Connection connOld = AbstractSocketBus.this.f_mapConnections.get(peer); + if (connOld == null || lIdThatRx == 0 || connOld.m_lIdentityPeer != lIdThatRx || + connOld.m_state.ordinal() >= ConnectionState.DEFUNCT.ordinal()) + { + connNew = makeConnection(peer); + connNew.m_nProtocol = nProt; + connNew.m_channel = getChannel(); + connNew.m_lIdentityPeer = lIdThatRx; + break; + } + // else; we lost the connection when we were half connected, i.e. we had learned our peer's ID, + // but it had not learned ours. Since it didn't know ours it could only do a CONNECT_NEW, but + // since we'd managed to learn its ID, we do have sufficient info to migrate. + + nConnect = CONNECT_MIGRATE; + lIdThisRx = connOld.f_lIdentity; + // fall through + } + + case CONNECT_MIGRATE: + { + // as part of migrating a connection we must ensure that we're migrating to the + // same logical connection. This is accomplished by only allowing migration if + // the connection IDs (ours and our peers) remain the same. We'll each validate + // each other's IDs. Note, it is ok to have only one of the two sets match so long + // as the other set doesn't match because of an unknown (0). We handle them potentially + // not knowing our ID above. + Connection connOld = AbstractSocketBus.this.f_mapConnections.get(peer); + if (connOld != null && + (connOld.f_lIdentity == lIdThisRx || nProt == 1) && + (lIdThatRx == 0 || connOld.m_lIdentityPeer == 0 || connOld.m_lIdentityPeer == lIdThatRx) && // peer id may still be unknown, and we learn it now + connOld.m_state.ordinal() < ConnectionState.DEFUNCT.ordinal()) + { + SocketChannel chanOld = connOld.m_channel; + SocketChannel chanNew = getChannel(); + + scheduleUnsafeTask(chanOld, new Runnable() + { + @Override + public void run() + { + synchronized (connOld) + { + if (connOld.m_channel == chanOld && connOld.m_state.ordinal() <= ConnectionState.ACTIVE.ordinal()) + { + if (connOld.m_state == ConnectionState.ACTIVE) + { + // to get this far we know that the TCP connection went down without the user choosing + // for it to, and without the other end dying, thus we should log a higher level log + // message, there is something wrong with the environment, though we are auto-correcting + // it. + + getLogger().log( + makeExceptionRecord(Level.WARNING, connOld.m_eMigrationCause, + "{0} accepting connection migration with {1}, replacing {2} with {3}: {4}", + getLocalEndPoint(), peer, connOld.m_channel, getChannel(), connOld)); + connOld.m_eMigrationCause = null; + } + // else; during connection establishment we may have simply had a connect timeout, this is not + // worth logging a warning over + + // move onto the accept phase + m_phase = HandshakePhase.ACCEPT; + m_headerIn.clear().limit(1); + + m_headerOut.clear(); + m_headerOut.put(CONNECT_MIGRATE); + connOld.m_nProtocol = nProt; // may not have been learned previously + if (nProt > 3) + { + // it's possible that we hadn't yet learned our peer's ID, though it + // had clearly learned ours + if (connOld.m_lIdentityPeer == 0) + { + connOld.m_lIdentityPeer = lIdThatRx; + } + // else; we've already asserted that the ids are equal above + + m_headerOut.putLong(connOld.f_lIdentity); + m_headerOut.putLong(connOld.m_lIdentityPeer); + } + else if (nProt > 1) + { + m_headerOut.putLong(connOld.m_lIdentityPeer); + } + + m_headerOut.put((byte) 0).flip(); // accept indicator + + m_connection = connOld; + closeChannel(chanOld); + + connOld.onMigration(); + + connOld.m_channel = chanNew; + + // re-register this channel on the original handler to resume processing + try + { + getSelectionService().register(getChannel(), HandshakeHandler.this); + connOld.m_handler = HandshakeHandler.this; + } + catch (IOException e) + { + closeChannel(chanNew); + } + } + else + { + closeChannel(chanNew); + } + } + } + }, /*cMillis*/ 0); + return 0; + } + + // we can't reconnect what we do not have (or to what we've disconnected from); abandon the connection + getLogger().log( + makeRecord(Level.FINE, "{0} rejecting connection migration from {1} on {2}, no existing connection {3}/{4}", + getLocalEndPoint(), peer, getChannel().socket().getLocalSocketAddress(), + lIdThisRx, (connOld == null ? 0 : connOld.f_lIdentity))); + m_headerIn.clear(); // some read space > accept size + m_phase = HandshakePhase.ABANDON; + + ByteBuffer bufOut = m_headerOut = ByteBuffer.allocate(1 + (nProt > 3 ? 16 : nProt > 1 ? 8 : 0)); + bufOut.put(CONNECT_NEW); // indicate that we don't have awareness of this connection + + // we don't have a connection, and thus don't have an identity; just write zeros + if (nProt > 3) + { + bufOut.putLong(0).putLong(0); + } + else if (nProt > 1) + { + bufOut.putLong(0); + } + + bufOut.flip(); + + return OP_READ | OP_WRITE; + } + + default: + throw new IOException("protocol error"); + } + + try + { + synchronized (connNew) + { + final Connection connOld; + + Lock lock = AbstractSocketBus.this.f_lockState.readLock(); + lock.lock(); + try + { + BusState nStateCurr = AbstractSocketBus.this.m_nState; + if (nStateCurr != BusState.OPEN) + { + // Bus is closed. Close the handler and return + getLogger().log( + makeRecord(Level.FINE, "{0} rejecting connection from {1}, bus is closing", + getLocalEndPoint(), + getChannel().socket().getInetAddress())); + close(null); + return 0; + } + + connOld = AbstractSocketBus.this.f_mapConnections.putIfAbsent(peer, connNew); + + if (connOld == null) + { + // common case, simply open the new connection + m_connection = connection = connNew; + connection.open(); + } + } + finally + { + lock.unlock(); // only need for putIfAbsent, and connection.open() + } + + if (connOld != null) + { + synchronized (connOld) + { + switch (connOld.m_state) + { + case OPEN: + { + // We have an out-bound pending connection which + // has yet to finish handshaking; apparently our + // peer has the same. We need to choose one, and + // ensure that the peer makes the same choice. + + // lesser of the two acceptor endpoints wins + final EndPoint self = getLocalEndPoint(); + if (self.getCanonicalName() + .compareTo(peer.getCanonicalName()) < 0) + { + // this bus wins; don't accept the + // peer's connection, our initiated + // connection will eventually get + // accepted by them, and cause them + // to close this losing channel. + + getLogger().log(makeRecord(Level.FINER, + "{0} wins simultaneous connect with {1}, abandoning {2}", + self, peer, getChannel().socket())); + + m_phase = HandshakePhase.ABANDON; + m_headerIn.clear().limit(2); // some read space > accept size + m_headerOut.clear().flip(); // no accept byte + return OP_READ; + } + else + { + // this bus looses; we will continue to + // use connOld, but with the peer's + // initiated channel + + getLogger().log(makeRecord(Level.FINER, + "{0} loses simultaneous connect with {1}, closing {2}", + self, peer, connOld.m_channel.socket())); + + // couple this new channel, and + // HandshakeHandler with the old Connection + // This requires executing all Runnables on the + // SelectionSvc thread associated with the old channel + // before the new channel is linked with the + // old Connection. (for maintaining execution order). + + // Register a Runnable on the old channel. + // Execution of this Runnable ensures that there is no + // pending Runnables on the old SS thread for this connection. + getLogger().log(makeRecord(Level.FINER, + "{0} deferring handshake attempt with {1} on {2}", + self, peer, getChannel().socket())); + + final SocketChannel channelNew = getChannel(); + final SelectionService.Handler handlerNew = this; + + connOld.invoke( + new Runnable() + { + @Override + public void run() + { + boolean fContinue; + synchronized (connOld) + { + try + { + connOld.m_channel.close(); + } + catch (IOException ioe) + { + } + + if (connOld.m_state.ordinal() < ConnectionState.DEFUNCT.ordinal()) + { + connOld.m_channel = channelNew; + connOld.m_lIdentityPeer = lIdThatRx; + fContinue = true; + } + else + { + // application released the connection during a concurrent + // connect, just drop the new channel + fContinue = false; + try + { + channelNew.close(); + } + catch (IOException e) + { + } + } + + // re-open invocation + Queue queueDeferred = connOld.m_queueDeferred; + connOld.m_queueDeferred = null; + + // reschedule any deferred invocations + for (Runnable runnable : queueDeferred) + { + connOld.invoke(runnable); + } + } + + if (fContinue) + { + getLogger().log(makeRecord(Level.FINER, + "{0} continue handshake attempt with {1} on {2}", + self, peer, getChannel().socket())); + + try + { + // Enable read/write interest for the new channel + getSelectionService().register(channelNew, handlerNew); + connOld.m_handler = handlerNew; + } + catch (IOException ioe) + { + onException(ioe); + } + } + } + } + ); + + m_connection = connection = connOld; + + // disable read/write interest for the new channel. It + // will be re-enabled when all the pending Runnables on the old + // channel has been executed. + nInterest = 0; + + // defer new Runnables until we switch channels + if (connOld.m_queueDeferred == null) + { + connOld.m_queueDeferred = new LinkedList(); + } + // else; deferral is already in progress (not sure this can even happen) + } + } + break; + + case ACTIVE: + // We can end up in this situation if the local + // disconnect hasn't yet happened. + // Fall though and enqueue the new connection + // so that it can/ be processed when the old + // connection eventually goes away. + + // fall through + case DEFUNCT: + // our connection is DISCONNECTED but + // the app has yet to release it. + + // record this into connOld, closing any + // prior one + if (connOld.m_next == null) + { + getLogger().log(makeRecord(Level.FINE, + "{0} deferring reconnect attempt from {1} on {2}, pending release", + getLocalEndPoint(), peer, getChannel().socket())); + } + else + { + getLogger().log(makeRecord(Level.FINE, + "{0} replacing deferred reconnect attempt from {1} on {2}, pending release", + getLocalEndPoint(), peer, getChannel().socket())); + connOld.m_next.close(null); + } + m_connection = connection = connNew; + connOld.m_next = this; + nInterest = 0; // deffer accept until release + break; + + case FINAL: + // to see this the old connection has or + // is in the process of being unregistered + // try again, i.e. just pop out and let the + // next selection operation call back in + // for another try + // NOTE: headers have been left unchanged + return OP_WRITE; + + default: + throw new IllegalStateException("state = " + connOld.m_state); + } + } + } + } + } + finally + { + if (m_connection != connNew) + { + connNew.dispose(); + } + } + + m_headerOut.clear(); + if (nProt > 0) + { + // Note, that if we're here nConnect == CONNECT_NEW, as inbound migrations are completely handled above. + + // the acceptor doesn't have sufficient information to send the connect type until it + // has received the initator's introduction, so we send it at the start of the accept + // phase. Note that on the wire this doesn't look asymetrical since the two phases are + // back to back, i.e. the initiator is still in its introduction phase awaiting this byte. + m_headerOut.put(nConnect); // if we got this far we're just echoing back the same connect type as the other side + + if (nProt > 1) + { + m_headerOut.putLong(connection.f_lIdentity); + + if (nProt > 3) + { + m_headerOut.putLong(connection.m_lIdentityPeer); + } + } + } + } + else // outbound connection + { + if (!connection.getPeer().equals(peer)) + { + // out-bound connection, but the peer replied with + // a different name then we used. While this is ok from an + // inet perspective, it is not ok from a bus perspective since + // in order to disallow multiple connections between peers + // each peer can only be known via one name. + + getLogger().log(makeRecord(Level.FINER, "{0} Out-bound connection to" + + " {1}, found {2}, single connection pair cannot be ensured", + getLocalEndPoint(), + connection.getPeer(), peer)); + + // Should this be threaded as an error? + // the reason we don't treat it as an error is that the other side could be listening + // on the wildcard address, and we're connecting to it via a specific IP. So here + // we choose to be practical and allow the possibility of multiple connections between + // peers rather then to not allow connections at all. + + // TODO improve the protocol so that can be both practical and accurate + /* + close(new IOException("peer mismatch, expected " + + connection.getPeer() + " found " + peer)); + return 0;*/ + } + + switch (nConnect) // from peer's perspective + { + case CONNECT_NEW: + long lIdPeerCurr = connection.m_lIdentityPeer; + if (lIdPeerCurr == 0) + { + // common case; initial connection, we learn the peer's identity + connection.m_lIdentityPeer = lIdThatRx; + } + else + { + // since we'd previously known our peer's id we must have initiated a migration + // and the peer responded with CONNECT_NEW indicating that they didn't have a + // matching connection. lId should be 0 if its not that is some form of protocol error + connection.scheduleDisconnect(new IOException( + "connection migration rejected by peer; no existing connection")); + return OP_READ; + } + // else; we're migrating to the same peer + break; + + case CONNECT_MIGRATE: + // the accepting peer indicated it is a migration, apparently we must have also sent a migration + // or the peer had more info then us (prior half connect) and had a connection with our connection + // ID, thus allowing a migration + if (nProt > 1) + { + if (connection.f_lIdentity != lIdThisRx) + { + // but we've been recycled, i.e. not the the same logical connection + connection.scheduleDisconnect(new IOException( + "connection migration failed; mismatch on local identity " + + connection.f_lIdentity + "/" + lIdThisRx)); + return OP_READ; + } + else if (connection.m_lIdentityPeer == 0) + { + // this can only happen because of a prior half connect, where we hadn't learned our peer's + // connection ID, but it had learned ours. Our peer then decided that this could be + // a migration, and thus we learn their ID now + connection.m_lIdentityPeer = lIdThatRx; + // fall through + } + else if (nProt > 3 && connection.m_lIdentityPeer != lIdThatRx) + { + // should not be possible; this is basically an assertion + connection.scheduleDisconnect(new IOException( + "connection migration failed (protocol error); mismatch on remote identity " + + connection.m_lIdentityPeer + "/" + lIdThatRx)); + return OP_READ; + } + // else; identity match, accept the migration + } + + getLogger().log( + makeExceptionRecord(Level.WARNING, connection.m_eMigrationCause, + "{0} accepted connection migration with {1} on {2}: {3}", + getLocalEndPoint(), peer, getChannel(), connection)); + + connection.m_eMigrationCause = null; + break; + } + + m_headerOut.clear(); + } + + connection.setProtocolVersion(nProt); + + // move onto the accept phase + m_phase = HandshakePhase.ACCEPT; + m_headerIn.clear().limit(1); + m_headerOut.put((byte) 0) // accept byte + .flip(); + + return nInterest; + } + + /** + * Evaluate the "accept" byte. + * + * @return the new interest set + * + * @throws IOException on an I/O error + */ + public int onAccept() + throws IOException + { + // getting here means that we've received the accept byte + // the value is actually meaningless, but getting the byte is + // as it will only be sent if the other side has accepted our + // connection, otherwise they would just close the socket + + Connection connection = m_connection; + synchronized (connection) + { + switch (connection.m_state) + { + case OPEN: + connection.m_state = ConnectionState.ACTIVE; + // fall through + + case ACTIVE: // because of migration + // switch out the handlers + getSelectionService().register(getChannel(), connection); + connection.m_handler = connection; + break; + + default: + throw new IllegalStateException("state = " + connection.m_state); + } + } + + return 0; + } + + /** + * Handle extra data supplied to an abandoned connection. + * + * @return the new interest set. + */ + public int onAbandon() + { + // to get here we've read more then just the peer's accept byte + // this means that it has started to use the connection, which + // should not be possible, actively reject the connection + close(new IOException("protocol error")); + + return OP_READ; + } + + /** + * Close the HandshakeHandler's channel. + * + * @param eReason the reason for the disconnect, or null + */ + public void close(Throwable eReason) + { + // we are closing a channel which never reached the point where + // we could exchange data, this channel may or may not be + // associated with a local Connection. + + SocketChannel channel = getChannel(); + Connection connection = m_connection; + HandshakePhase phase = m_phase; + + if (connection == null) + { + if (phase != HandshakePhase.ABANDON) + { + getLogger().log(makeExceptionRecord( + eReason instanceof SSLException ? Level.WARNING : Level.FINEST, eReason, + "{0} close due to exception during handshake phase {1} on {2}", + getLocalEndPoint(), phase, channel.socket())); + } + } + else + { + connection.scheduleDisconnect(eReason); + } + + closeChannel(channel); + } + + // ----- data members ------------------------------------------- + + /** + * Non-null once we can associate the handshake with a local + * Connection. + *

+ * For out-bound connections this happens immediately, while for + * in-bound connections the data isn't available until we've finished + * the introduction phase. + * + */ + protected Connection m_connection; + + /** + * The handshake state. + */ + protected HandshakePhase m_phase = HandshakePhase.NEGOTIATE; + + /** + * The out-bound protocol header. + *

+ * The buffer will be resized as needed for each phase. + */ + protected ByteBuffer m_headerOut; + + /** + * The in-bound protocol header. + *

+ * Initially only large enough to complete the identification phase, + * it will be resized for the introduction phase once we know that + * we can communicate with the peer. + */ + protected ByteBuffer m_headerIn; + + /** + * The negotiated protocol version. + */ + protected int m_nProtocol; + } + + + // ---- AcceptHandler --------------------------------------------------- + + /** + * AcceptHandler accepts new client connections. + */ + protected class AcceptHandler + extends SafeSelectionHandler + { + /** + * Construct an AcceptHandler for the bus. + * + * @param channel the ServerSocketChannel + */ + protected AcceptHandler(ServerSocketChannel channel) + { + super(channel); + } + + /** + * {@inheritDoc} + */ + public int onReadySafe(int nOps) + { + SocketChannel chan = null; + try + { + chan = getChannel().accept(); + if (chan != null) + { + getLogger().log(makeRecord(Level.FINEST, + "{0} starting phase NEGOTIATE on {1}", + getLocalEndPoint(), chan.socket())); + + Sockets.configureBlocking(chan, false); + configureSocket(chan.socket()); + + getSelectionService().register(chan, new HandshakeHandler(chan, null)); + } + } + catch (IOException e) + { + if (chan == null) + { + // error in accept + throw new RuntimeException(e); + } + else // error with new channel; just close it + { + try + { + chan.close(); + } + catch (IOException e1) {} + } + } + return OP_ACCEPT; + } + + /** + * {@inheritDoc} + */ + public int onException(Throwable t) + { + // in the event of an unexpected exception such as OOME, we + // likely don't want to close our server socket + ServerSocketChannel channel = getChannel(); + if (channel.isOpen()) + { + getLogger().log(makeExceptionRecord(Level.INFO, t, + "{0} unexpected exception during Bus accept, ignoring", getLocalEndPoint())); + return OP_ACCEPT; + } + else // not open + { + synchronized (AbstractSocketBus.this) + { + if (m_nState == BusState.OPEN) + { + // our ServerSocket was unexpectedly closed; can this even happen? + getLogger().log(makeExceptionRecord(Level.SEVERE, t, + "{0} ServerSocket failure; no new connection will be accepted", getLocalEndPoint())); + } + return 0; + } + } + } + } + + /** + * BusState represents the various states a Bus may be in. + */ + protected enum BusState + { + /** + * State indicate that the bus has yet to be opened. + */ + INITIAL, + + /** + * State indicate that the bus has been opened. + */ + OPEN, + + /** + * State indicate that the bus is closing. + */ + CLOSING, + + /** + * State indicate that the bus has been closed. + */ + CLOSED + } + + + // ----- data members --------------------------------------------------- + + /** + * The SocketDriver which produced this bus. + */ + protected final SocketBusDriver f_driver; + + /** + * For the purpose of testing failed connections. + */ + protected final int f_nDropRatio; + + /** + * For the purpose of testing corrupted data stream. + */ + protected final int f_nCorruptionRatio; + + /** + * True if CRC validation is enabled for this bus. + */ + protected final boolean f_fCrc; + + /** + * The ServerSocketChannel on which this bus accepts new connections. + */ + private final ServerSocketChannel f_channelServer; + + /** + * The state of the bus. + *

+ * Changes to the state must be done which holding the write lock on + * f_lockState. + */ + private volatile BusState m_nState = BusState.INITIAL; + + /** + * Lock protecting changes to the bus state. + *

+ * The write lock must be held when changing the bus state. The read lock + * must be held when adding or releasing connections. Otherwise locking is + * done on a per-connection basis, by synchronizing on the connection. + */ + private final ReadWriteLock f_lockState = new ReentrantReadWriteLock(); + + /** + * The local EndPoint for the bus. + */ + protected UrlEndPoint m_pointLocal; + + /** + * The registered event collector. + */ + private Collector m_collectorEvent; + + /** + * Map of current connections. + *

+ * Changes to this map must be made while holding the read lock on + * f_lockState. + */ + private final ConcurrentMap f_mapConnections = new ConcurrentHashMap<>(); + + /** + * Set of connections to flush. + */ + private final Set f_setFlush = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + /** + * Empty connection array. + */ + private static final Connection[] EMPTY_CONNECTION_ARRAY = new Connection[0]; + + /** + * The connection ID generator. + */ + protected static final AtomicLong f_atomicIdGenerator = new AtomicLong(SafeClock.INSTANCE.getSafeTimeMillis()); + + /** + * used to indicate that a new connection is desired. + */ + private static final byte CONNECT_NEW = 0; + + /** + * Used to indicate that the peer wishes to migrate a connection to a new channel + */ + private static final byte CONNECT_MIGRATE = 1; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/BufferedSocketBus.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/BufferedSocketBus.java new file mode 100644 index 0000000000000..c8acaba339769 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/BufferedSocketBus.java @@ -0,0 +1,1933 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net.socketbus; + +import com.oracle.coherence.common.internal.util.HeapDump; +import com.oracle.coherence.common.io.BufferSequence; +import com.oracle.coherence.common.io.BufferManager; +import com.oracle.coherence.common.io.Buffers; +import com.oracle.coherence.common.net.exabus.EndPoint; +import com.oracle.coherence.common.net.exabus.util.UrlEndPoint; +import com.oracle.coherence.common.net.exabus.util.SimpleEvent; +import com.oracle.coherence.common.net.exabus.Event; +import com.oracle.coherence.common.util.Duration; +import com.oracle.coherence.common.util.MemorySize; +import com.oracle.coherence.common.util.SafeClock; + +import java.io.DataInput; +import java.io.IOException; +import java.nio.ByteBuffer; + +import java.util.Arrays; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.net.SocketException; + +import java.util.logging.Level; + +import java.util.zip.CRC32; + + +/** + * BufferedSocketBus adds write buffering to the AbstractSocketBus. + * + * @author mf 2010.12.1 + */ +public abstract class BufferedSocketBus + extends AbstractSocketBus + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a BufferedSocketMessageBus. + * + * @param driver the socket driver + * @param pointLocal the local endpoint + * + * @throws IOException if an I/O error occurs + */ + public BufferedSocketBus(SocketBusDriver driver, UrlEndPoint pointLocal) + throws IOException + { + super(driver, pointLocal); + } + + /** + * {@inheritDoc} + */ + @Override + protected void onOpen() + { + final long cMillisFlush = f_driver.getDependencies().getMaximumReceiptDelayMillis(); + if (cMillisFlush > 0) + { + scheduleTask(new Runnable() + { + @Override + public void run() + { + // reschedule self for next periodic flush + scheduleTask(this, cMillisFlush); + + for (Connection conn : getRegisteredConnections()) + { + if (((BufferedConnection) conn).isReceiptFlushRequired()) + { + conn.optimisticFlush(); + } + } + } + }, cMillisFlush); + } + + super.onOpen(); + } + + + // ----- BufferedConnection interface ---------------------------------- + + /** + * BufferedConnection implements a reliable stream connection with + * I/O offloading. + */ + public abstract class BufferedConnection + extends Connection + { + /** + * Create a BufferedConnection for the specified peer. + * + * @param peer the peer + */ + public BufferedConnection(UrlEndPoint peer) + { + super(peer); + } + + /** + * Return the threshold at which a send operation should perform + * an auto-flush of the unflushed write batch. + * + * @return the threshold in bytes + */ + protected long getAutoFlushThreshold() + { + long cb = m_cbAutoFlushThreshold; + + if (cb <= 0) + { + try + { + // goal is to avoid engaging the selection service writer + // or more specifically avoid flip-flopping between using + // it and not using it. If we allow our buffered data + // to exceed the underlying buffer size then a write is + // likely to engage it, so we start writing at the point + // that it would only be partially full. Obviously we + // don't want to do micro writes either, so we still want + // to be using a decent portion of the buffer before + // writing + // TODO: consider continuing to auto-resize the threshold based on how + // often an flush fails to fully flush the buffer + cb = f_driver.getDependencies().getAutoFlushThreshold(); + if (cb <= 0) + { + cb = Math.min(getPacketSize() * 9, // try for >90% packet utilization + getSendBufferSize() / 4); // ensure we don't fill tx buffer + } + m_cbAutoFlushThreshold = cb; + } + catch (SocketException e) {} + + if (cb <= 0) + { + // not yet connected + cb = 64 * 1024; + } + } + + return cb; + } + + /** + * Return the threshold at which forced acks of pending receipts is + * requested from the peer. + * + * @return the threshold in bytes + */ + protected long getForceAckThreshold() + { + long cb = m_cbForceAckThreshold; + + if (cb <= 0) + { + cb = f_driver.getDependencies().getReceiptRequestThreshold(); + + try + { + if (cb <= 0) + { + cb = getSendBufferSize() * 3; + } + m_cbForceAckThreshold = cb; + } + catch (SocketException e) {} + + if (cb <= 0) + { + // not yet connected + cb = 3 * 64 * 1024; + } + } + + return cb; + } + + /** + * Return the threshold at which to declare backlog + * + * @return the threshold in bytes + */ + protected long getBacklogExcessiveThreshold() + { + long cb = m_cbBacklogExcessiveThreshold; + + if (cb <= 0) + { + try + { + m_cbBacklogExcessiveThreshold = cb = getSendBufferSize(); + } + catch (SocketException e) {} + + if (cb <= 0) + { + // not yet connected + cb = 1024 * 1024; + } + } + + return cb; + } + + @Override + public BufferedConnection ensureValid() + { + super.ensureValid(); + return this; + } + + /** + * Return the number of concurrently executing writers. + * + * @return the number of concurrently executing writers. + */ + protected int getConcurrentWriters() + { + // estimate how contended this connection is by including all threads which contributed to this batch + // as well as any threads waiting to use the connection + + return m_cWritersWaiting.get() + m_cWritersBatch; + } + + /** + * Extracted post "send" logic. + * + * The caller must be synchronized on this connection. The parameter ordering allows the caller to inline + * the computation of each parameter. + * + * @param fFlushInProg true if a flush was in progress before the send + * @param fFlushPending true if a flush was required before the send + * @param cbPending the number of bytes pending on the connection + */ + protected void evaluateAutoFlush(boolean fFlushInProg, boolean fFlushPending, long cbPending) + { + if (!fFlushInProg) + { + if (cbPending > getAutoFlushThreshold()) + { + if (!flush(/*fAuto*/ true)) + { + if (!fFlushPending) + { + addFlushable(this); + } + // else; a flush was already pending no need to add again + } + else if (fFlushPending) + { + // flush indicated there is nothing left; and we were previously in the flush-set; do cleanup + removeFlushable(this); + } + } + else if (!fFlushPending) + { + addFlushable(this); + } + // else; a flush was already pending no need to add again + } + // else; another thread is actively waiting on flush, no need to do anything post-send flush processing + } + + /** + * Add a thread to the set of concurrent writers. + */ + protected void addWriter() + { + // we loosely track threads using a bitset + long lBitId = 1L << Thread.currentThread().getId(); // % 64 is not necessary as JLS says << will do it for me + if ((m_lWritersBatchBitSet & lBitId) == 0L) + { + m_lWritersBatchBitSet |= lBitId; + ++m_cWritersBatch; + } + } + + /** + * Send any scheduled BufferSequences. + */ + public void flush() + { + if (flush(/*fAuto*/ false)) + { + removeFlushable(this); + } + } + + /** + * Send any scheduled BufferSequences. + * + * @param fAuto true iff it is an auto-flush + * + * @return true if the connection has flushed all pending data + */ + public boolean flush(boolean fAuto) + { + SocketBusDriver.Dependencies deps = getSocketDriver().getDependencies(); + + WriteBatch batch = m_batchWriteUnflushed; + int cRecReq = m_cReceiptsUnflushed; + int cRecRet = m_cReceiptsReturn.getAndSet(0); + + // determine if we need to ask for forced acks + long cUnackedBytes = f_cBytesUnacked.get(); + if (cRecReq > 0 && cUnackedBytes > getForceAckThreshold()) + { + cRecReq = -cRecReq; // negative receipts indicates forced acks + f_cBytesUnacked.set(0); // reset counter to avoid requesting multiple forced acks + } + + // insert receipt message if necessary + if (cRecReq != 0 || cRecRet != 0 || m_fIdle) + { + m_fIdle = false; + if (batch == null) + { + m_batchWriteUnflushed = batch = new WriteBatch(); + } + + int cbReceipt = getReceiptSize(); + ByteBuffer bufferMsgReceipt = m_bufferRecycleOutboundReceipts; + if (bufferMsgReceipt == null) + { + bufferMsgReceipt = m_bufferRecycleOutboundReceipts = deps.getBufferManager().acquire(cbReceipt * 1024); + int cbCap = bufferMsgReceipt.capacity(); + bufferMsgReceipt.limit(cbCap - (cbCap % cbReceipt)); + } + + if (bufferMsgReceipt.remaining() > cbReceipt) + { + ByteBuffer buffReceipt = bufferMsgReceipt.slice(); + writeMsgReceipt(buffReceipt, cRecReq, cRecRet); + bufferMsgReceipt.position(bufferMsgReceipt.position() + cbReceipt); + batch.append(buffReceipt, /*fRecycle*/ false, /*body*/ null, /*receipt*/ cRecReq == 0 ? null : RECEIPT_ACKREQ_MARKER); + } + else // bufferMsgReceipt.remaining() == MSG_RECEIPT_SIZE + { + // write last chunk and recycle + bufferMsgReceipt.mark(); + writeMsgReceipt(bufferMsgReceipt, cRecReq, cRecRet); + batch.append(bufferMsgReceipt, /*fRecycle*/ true, /*body*/ null, /*receipt*/ cRecReq == 0 ? null : RECEIPT_ACKREQ_MARKER_RECYCLE); + m_bufferRecycleOutboundReceipts = null; + } + m_cReceiptsUnflushed = 0; + } + + if (batch == null) + { + return true; // nothing to flush + } + else if (f_cbQueued.get() == 0 && getConcurrentWriters() <= deps.getDirectWriteThreadThreshold()) + { + // SS thread isn't writing, so this is the current head. Even if the SS thread never sees this + // batch we want to overwrite any historic batch with the current head to avoid building up + // garbage which otherwise wouldn't be GCable until the SS thread iterated through many empty batches + m_batchWriteSendHead = batch; + try + { + synchronized (batch) // sync'd because ack can come in while we're still in batch.write + { + if (batch.write()) + { + m_cWritersBatch = 0; + m_lWritersBatchBitSet = 0; + return true; + } + // else; we didn't write everything, fall through and evaluate if we need to enqueue the batch + } + } + catch (IOException e) + { + // stream is now in an unknown state; queue the remainder and reconnect + m_batchWriteUnflushed = null; + enqueueWriteBatch(batch); + onException(e); + return true; // nothing more can be done + } + } + else if (m_state == ConnectionState.DEFUNCT) + { + scheduleDisconnect(null); // ensure receipts are emitted ASAP + } + + if (fAuto && batch.getLength() < getBacklogExcessiveThreshold() * 2) + { + return false; // data remains to be flushed + } + else + { + // either caller explictly flushed or we're buffering a significant + // amount, enqueue to SelectionService + m_batchWriteUnflushed = null; + m_cWritersBatch = 0; + m_lWritersBatchBitSet = 0; + + enqueueWriteBatch(batch); + return true; // euqueue'd the batch + } + } + + + @Override + protected boolean heartbeat() + { + long cbHeartbeat = m_cbWrite; + boolean fResult = false; + if (cbHeartbeat == m_cbHeartbeatLast && // we've sent nothing since the last check + f_cbQueued.get() == 0) // we have no outbound traffic queued up + { + // prevent the network infrastructure from closing the idle socket + + // setting m_fIdle to true will cause the next flush to minimally send an empty receipt + // flush will also reset idle + m_fIdle = true; + optimisticFlush(); // attempt flush in case auto-flush is disabled + fResult = true; + } + // else; the connection has seen writes since the last heartbeat check, no action required + + m_cbHeartbeatLast = cbHeartbeat; + return fResult; + } + + + /** + * Offload the specified WriteBatch to the SelectionService for processing. + * + * @param batch the WriteBatch to enqueue + */ + public void enqueueWriteBatch(WriteBatch batch) + { + long cbBatch = batch.getLength(); + + long cbQueuedNow = f_cbQueued.addAndGet(cbBatch); + + if (cbQueuedNow == cbBatch) + { + // for this to occur the just enqueued batch is at the head of the queue + // and was not even partially written by the SelectionService. In such + // a case the SelectionService may not be aware of it and we must + // re-register with selection service for WRITE interest + try + { + wakeup(); + } + catch (IOException e) + { + onException(e); + } + } + + final long cbExcessive = getBacklogExcessiveThreshold(); + if (cbQueuedNow > cbExcessive * 2 && + m_fBacklogScheduled.compareAndSet(false, true)) + { + // handle the case where the SelectionService thread doesn't wake up for any writes + // we need to prevent an endless backlog from being formed, but also want to avoid + // issuing this invocable in most cases, so we use an extra large limit. + // emit BACKLOG_EXCESSIVE from SelectionService + invoke(new Runnable() + { + public void run() + { + m_fBacklogScheduled.set(false); + + if (isValid() && !m_fBacklog && f_cbQueued.get() > cbExcessive) + { + m_fBacklog = true; + emitEvent(new SimpleEvent( + Event.Type.BACKLOG_EXCESSIVE, + getPeer())); + } + } + }); + } + } + + /** + * Write a Receipt message to a buffer. + * + * @param buff buffer to write the receipt message + * @param cRecReq number of receipts associated with the unflushed WriteBatch + * @param cRecRet number of receipts to return to the peer + * + * @return ByteBuffer to which the receipt message was written + */ + protected ByteBuffer writeMsgReceipt(ByteBuffer buff, int cRecReq, int cRecRet) + { + int nProt = getProtocolVersion(); + int nPos0 = buff.position(); + + if (nProt > 4) + { + buff.putLong(-9) // negative msg size indicates control message of that size + .position(nPos0 + 16); // skip writes for header/body crc + } + else + { + buff.putInt(-9); + } + + buff.put(MSG_RECEIPT) + .putInt(cRecReq) + .putInt(cRecRet); + + buff.limit(buff.position()).position(nPos0); + if (nProt > 4) + { + // backfill the message header with the header and body CRCs now that they can be computed + populateCtrlMsgHeaderCrc(buff); + } + + return buff; + } + + /** + * Process a receipt from the supplied stream + * + * @param in the receipt + * + * @throws IOException if an IO error occurs + */ + protected void processReceipt(DataInput in) + throws IOException + { + int cReceiptsRequested = in.readInt(); + if (cReceiptsRequested < 0 || getSocketDriver().getDependencies().getMaximumReceiptDelayMillis() == 0) + { + // negative or config indicates force receipt send immediately + m_cReceiptsReturn.addAndGet(cReceiptsRequested < 0 ? -cReceiptsRequested : cReceiptsRequested); + + if (isReceiptFlushRequired()) + { + // there are no pending flushes for this connection + optimisticFlush(); + } + } + else + { + m_cReceiptsReturn.addAndGet(cReceiptsRequested); + } + + int cReturned = in.readInt(); + if (cReturned > 0) + { + EndPoint epPeer = getPeer(); + WriteBatch batchResend = m_batchWriteResendHead; + WriteBatch batchNext = batchResend.next(); + for (;;) + { + int cEmit; + if (batchNext == null && m_batchWriteUnflushed == batchResend) + { + // the app may be concurrently writing to this (the last) batch, thus we must sync + synchronized (batchResend) + { + cReturned -= cEmit = batchResend.ack(cReturned, f_aoReceiptTmp); + } + } + else + { + // there is a subsequent batch which means the application is no longer writing + // to this batch. Also reading the next ref acts as a memory barrier ensuring + // that we have a clean view of the contents of this batch since it was written + // after this batch was done being written to by the app thread + cReturned -= cEmit = batchResend.ack(cReturned, f_aoReceiptTmp); + } + + // emit receipts outside of synchronization + for (int i = 0; i < cEmit; ++i) + { + Object oReceipt = f_aoReceiptTmp[i]; + f_aoReceiptTmp[i] = null; + if (oReceipt != RECEIPT_NO_EMIT) + { + addEvent(new SimpleEvent(Event.Type.RECEIPT, epPeer, oReceipt)); + ++m_cReceiptsEmitted; + } + } + + if (cReturned == 0) + { + // wait for more receipts + break; + } + else if (cEmit < f_aoReceiptTmp.length) + { + // this batch must be fully ack'd; move onto next batch + + // try to null out the resend.next ref so that we don't + // end up with tenured garbage referencing live data as + // this can force short lived objects into being tenured + // as well and cause long GCs. To be here clearly this batch + // is fully ack'd, so we'd think we could just null out it's + // next ref, but it is possible that if we've recently done + // a migration this batch is still pending a send, i.e. we're + // resending it not realizing that it was already ack'd. In + // such a case we can't null out next but in all other cases + // we can. To identify this we check if the head of the send + // queue is fully ack'd if so then we're in that odd state + + WriteBatch batchSend = m_batchWriteSendHead; // yes send not resend + if (batchSend.m_ofAck != batchSend.m_ofAdd) + { + // since this resend batch is fully ack'd and the send head is not + // full ack'd that means the resend pointer is in front of the send + // pointer (as is normal), and thus this resend batch is now garbage + // and we can null out its next pointer + batchResend.m_next = null; + } + + batchResend = batchNext; + batchNext = batchResend.next(); + } + // else; more to process in current batch + } + + m_batchWriteResendHead = batchResend; + + // Peer has acked. Reset counter; this also serves as a volatile write to make all of the above + // visible to checkHealth + f_cBytesUnacked.set(0); + } + } + + /** + * Process a receipt from the supplied stream + * + * @param in the receipt + * + * @throws IOException if an IO error occurs + */ + protected void processSync(DataInput in) + throws IOException + { + long cMsgOut = m_cMsgOutDelivered; + long cMsgIn = m_cMsgIn; // SYNCs aren't ackable and thus aren't reflected in either side's count + long cMsgAcked = in.readLong(); + long cMsgReceived = in.readLong(); + byte nCmdSync = getProtocolVersion() < 3 ? SYNC_CMD_NONE : in.readByte(); + + if ((nCmdSync & SYNC_CMD_DUMP) != 0) + { + String sDump = HeapDump.dumpHeapForBug("Bug-27585336-tmb-migration"); + getLogger().log(makeRecord(Level.WARNING, "{0} migration with {1} appears to not be progressing on {2}; {3} collected for analysis", + getLocalEndPoint(), getPeer(), BufferedConnection.this, sDump)); + } + + if (cMsgAcked > cMsgIn || // peer got more acks then we sent to it + cMsgOut > cMsgReceived) // we got more acks then it sent to us + { + scheduleDisconnect(new IOException("out of sync during migration in " + + cMsgAcked + "/" + cMsgIn + ", out " + cMsgOut + "/" + cMsgReceived)); + } + else + { + long cSkip = m_cMsgInSkip = cMsgIn - cMsgAcked; + long cRedeliver = cMsgReceived - cMsgOut; + getLogger().log(makeRecord(Level.FINE, + "{0} synchronizing migrated connection with {1} will result in {2} skips and {3} re-deliveries: {4}", + getLocalEndPoint(), getPeer(), cSkip, cRedeliver, BufferedConnection.this)); + } + } + + /** + * Return true iff there are pending receipts that needs to be flushed but no application data to flush + * + * @return true iff connection has pending receipts but no unflushed application data + */ + protected boolean isReceiptFlushRequired() + { + return (m_cReceiptsReturn.get() > 0 || m_fIdle) && !isFlushRequired(); + } + + /** + * Return true there is application data pending a flush. + * + * @return true iff there is application data pending a flush + */ + protected boolean isFlushRequired() + { + WriteBatch batch = m_batchWriteUnflushed; + return batch != null && batch.m_ofSend < batch.m_ofAdd; + } + + /** + * {@inheritDoc} + */ + public int onReadySafe(int nOps) + throws IOException + { + return m_nInterestOpsLast = processReads((nOps & OP_READ) != 0) | processWrites((nOps & OP_WRITE) != 0); + } + + @Override + protected void checkHealth(long ldtNow) + { + if (m_state == null || m_state.ordinal() > ConnectionState.ACTIVE.ordinal()) + { + return; + } + + // establish write health, i.e. we've done a write or have nothing (flushed) to write + + // m_cbWrite is a dirty read as writes may be done off the SS thread, but even + // if they are f_cbQueued will be updated after each m_cbWrite update, thus worst case we're + // guaranteed to see the updated m_cbWrite on our next pass. + long cbWrite = m_cbWrite; + long cbWriteLast = m_cbWriteLastCheck; + boolean fWriteHealthy = cbWrite > cbWriteLast || f_cbQueued.get() == 0; + + // establish read health + + // we're unhealthy if we have an inbound receipt pending and we aren't actively making read progress. + // note we consider reads of anything to be a good sign simply because our ack could still be coming. + + long ldtAckTimeout = m_ldtAckTimeout; + long cbRead = m_cbRead; + Object oReceiptUnacked = null; + int ofReceiptUnacked = 0; + Object batchUnacked = null; + + f_cBytesUnacked.get(); // volatile read to allow all other read-health checks to see data recently written + // by the corresponding SS thread. + boolean fReadHealthy = true; + if (cbRead == m_cbReadLastCheck) + { + // we haven't read anything, but we need to check if we should have read something. The only thing + // we check for are acks, so lets see if we are expecting one; scan the resend queue to see if we've + // sent anything which requires an ack, and also sent the subsequent ack request. We could also + // monitor messages which have started to arrive, but then stalled, but those will naturally be protected + // by our peer running its own health check waiting for us to send the corresponding ack, and while we + // could monitor these, we couldn't monitor messages until they started to arrive, so the monitoring would + // be both redundant and incomplete. + UNHEALTHY: for (WriteBatch batchResend = m_batchWriteResendHead; batchResend != null; batchResend = batchResend.next()) + { + Object[] aReceipt = batchResend.m_aReceipt; + int ofSafe = aReceipt.length; // defend against concurrent batch.append/ack + for (int i = Math.min(ofSafe, batchResend.m_ofAck), e = Math.min(ofSafe, batchResend.m_ofSend); i < e; ++i) + { + Object oReceipt = aReceipt[i]; + if (oReceiptUnacked == null) + { + if (oReceipt != null && + oReceipt != RECEIPT_MSG_MARKER && // don't count + oReceipt != RECEIPT_HEADER_RECYCLE && // artificial receipts + oReceipt != RECEIPT_MSG_MARKER_HEADER_RECYCLE && // which + oReceipt != RECEIPT_ACKREQ_MARKER && // don't + oReceipt != RECEIPT_ACKREQ_MARKER_RECYCLE) // get ack'd + { + // this indicates that we've sent a message which requires an ack that we've yet + // to receive; but we also need to know we've sent the ack request. The ack request + // for this would go out in our next receipt, so start searching for that. + oReceiptUnacked = oReceipt; // in coherence this will be the actual Message + ofReceiptUnacked = i; + batchUnacked = batchResend; + } + } + else if (oReceipt == RECEIPT_ACKREQ_MARKER || + oReceipt == RECEIPT_ACKREQ_MARKER_RECYCLE) + { + // oReceiptUnacked is non-null thus we know we've sent something requiring an ack, and + // we've now seen that we've sent the ack request as well, thus our health is now suspect. + fReadHealthy = false; + break UNHEALTHY; + } + } + } + } + else + { + // we read something; we have read health + fReadHealthy = true; + + // but we also need to help out our peer. if we're on a slow network and the peer has a large tx buffer + // it's possible they finished their write long before the last packet from that write will actually + // leave TCP's tx buffer. The peer can't tell when that has happened and started their ack timer at + // the point they finished writing to the tx buffer. Since we don't want the ack timeout to need to + // be relative to message sizes or network speed we need a way to help our peer to not declare a timeout. + // We can do this by ensuring that maintain read health while awaiting the ack, and we can do that by + // sending dummy data, i.e. heartbeats. But of course we only want to do that if we can see that that + // their TCP stack is still draining the tx buffer, which of course we can infer by us still draining + // our rx buffer. To be here we know that we read some bytes, but we also want to know that there is + // still more coming, so for that we need to see that we're not waiting on a message header. + + if ((m_nInterestOpsLast & OP_EAGER) == 0) // no pending read + { + m_ldtForceHeartbeat = 0; // disable heartbeat timeout + } + else if (m_ldtForceHeartbeat == 0 || // start of pending read + (ldtNow > m_ldtForceHeartbeat && heartbeat())) // or time to force heartbeat + { + // force a HB multiple times during an ack timeout period, this assumes our peer has the same timeout as us + m_ldtForceHeartbeat = ldtNow + f_driver.getDependencies().getAckTimeoutMillis() / 3; + } + // else; not time to force a heartbeat yet + } + + if (fReadHealthy && fWriteHealthy) // common path + { + // we're healthy; disable any active timeout + m_ldtAckFatalTimeout = m_ldtAckTimeout = 0; + + // only record read/write amounts when healthy + m_cbWriteLastCheck = cbWrite; + m_cbReadLastCheck = cbRead; + } + else if (ldtAckTimeout == 0) + { + // health is now in doubt, set timeouts + long cMillisTimeout = f_driver.getDependencies().getAckTimeoutMillis(); + + m_ldtAckTimeout = cMillisTimeout == 0 || getProtocolVersion() == 0 ? Long.MAX_VALUE : ldtNow + cMillisTimeout; + cMillisTimeout = f_driver.getDependencies().getAckFatalTimeoutMillis(); + m_ldtAckFatalTimeout = cMillisTimeout == 0 ? Long.MAX_VALUE : ldtNow + cMillisTimeout; + } + else if (ldtNow >= m_ldtAckFatalTimeout) + { + // fatal timeout expired + long cMillisTimeout = f_driver.getDependencies().getAckFatalTimeoutMillis(); + Duration dur = new Duration(cMillisTimeout, Duration.Magnitude.MILLI); + + getLogger().log(makeRecord(Level.WARNING, + "{0} dropping connection with {1} after {2} fatal ack timeout health(read={3}, write={4}), receiptWait={5}: {6}", + getLocalEndPoint(), getPeer(), dur, fReadHealthy, fWriteHealthy, oReceiptUnacked, BufferedConnection.this)); + + scheduleDisconnect(new IOException("fatal ack timeout after " + dur)); + } + else if (ldtNow >= ldtAckTimeout) + { + // timeout expired + final int cMultCap = 10; + long cMillisTimeout = f_driver.getDependencies().getAckTimeoutMillis(); + Duration dur = new Duration(cMillisTimeout * Math.min(cMultCap, m_cUnackLast + 1), Duration.Magnitude.MILLI); + + getLogger().log(makeRecord(Level.WARNING, + "{0} initiating connection migration with {1} after {2} ack timeout health(read={3}, write={4}), receiptWait={5}: {6}", + getLocalEndPoint(), getPeer(), dur, fReadHealthy, fWriteHealthy, oReceiptUnacked, BufferedConnection.this)); + + if (oReceiptUnacked == null) + { + m_nIdUnackLast = 0; + m_cUnackLast = 0; + } + else + { + // if we have successive read health timeouts then perhaps our ack timeout is just too small for + // an apparently really slow network and big message. While a needless connection migration is + // harmless, endless ones without progress is certainly not. So lets push up the timeout while + // we work to get this larger message successfully ack'd. Note our health check algorithm already + // defends against large message transmission time in that we're happy so long as we see read and + // write progress, but it doesn't account for how long it may take to drain the OS tx buffer. So + // this time increase defends against that unknown. We do limit the increase we'll extend the + // configured timeout by cMultiCap times the configured timeout. + int nId = System.identityHashCode(oReceiptUnacked) ^ + System.identityHashCode(batchUnacked) ^ ofReceiptUnacked; // in case the same receipt object is reused frequently + if (nId == m_nIdUnackLast) + { + int cStuck = ++m_cUnackLast; + cMillisTimeout *= Math.min(cMultCap, cStuck + 1); + + if (cStuck == MIGRATION_LIMIT_BEFORE_DUMP) + { + // see onMigration where we also check m_cUnackLast and request that our peer collect a dump as well + String sName = HeapDump.dumpHeapForBug("Bug-27585336-tmb-migration"); + getLogger().log(makeRecord(Level.WARNING, + "{0} has failed to deliver {1} to {2} after {3} attempts, {4} has been collected for analysis", + getLocalEndPoint(), oReceiptUnacked, BufferedConnection.this, cStuck, sName)); + ldtNow = SafeClock.INSTANCE.getSafeTimeMillis(); // heap dump may have taken awhile + } + } + else + { + m_cUnackLast = 0; + } + m_nIdUnackLast = nId; + } + + // reset ack timeout + m_ldtAckTimeout = ldtNow + cMillisTimeout; + + migrate(new IOException("ack timeout after " + dur)); + } + // else; progressing towards timeout + } + + + @Override + public void onMigration() + { + super.onMigration(); + + // don't log as a warning, we can get here simply because the other process terminated, i.e. delay + // logging as a warning until we actually re-establish the connection. + getLogger().log(makeRecord(Level.FINER, "{0} migrating connection with {1}", + getLocalEndPoint(), BufferedConnection.this)); + + m_cMsgInSkip = 0; // our peer will start with a new SYNC message which will tell us exactly how much we should skip + + WriteBatch batchWriteUnflushed = m_batchWriteUnflushed; + if (batchWriteUnflushed != null) + { + m_batchWriteUnflushed = null; + f_cbQueued.addAndGet(batchWriteUnflushed.getLength()); + } + + // remove any sends (from f_cbQueued) that were pre-ack'd during a former migration + for (WriteBatch batch = m_batchWriteSendHead; batch != null && batch.m_ofAck == batch.m_ofAdd; batch = batch.next()) + { + batch.m_ofSend = batch.m_ofAdd; // pretend we've sent everything + f_cbQueued.addAndGet(-batch.getLength()); + } + + // rewind batches from the resend queue; Note: we scan the entire queue because it is not trivial + // to identify where it is safe to stop scanning. Specifically we can't stop and the first unsent + // message since we can run into empty batches + WriteBatch batchResendHead = m_batchWriteResendHead; + for (WriteBatch batch = batchResendHead; batch != null; batch = batch.next()) + { + f_cbQueued.addAndGet(batch.rewind()); + } + + int nProt = getProtocolVersion(); + if (nProt > 0) + { + // place SYNC message at the start of the send queue, but don't included it in the resend queue + WriteBatch batchWriteSync = m_batchWriteSendHead = new WriteBatch(/*fLink*/ false); + + int cbHead = nProt < 5 ? 4 : 16; // msg header size + int cbBody = 17 + (nProt < 3 ? 0 : 1); // msg body size + ByteBuffer bufSync = ByteBuffer.allocate(cbHead + cbBody); + + if (nProt > 4) + { + bufSync.putLong(-cbBody) // negative msg size indicates control message of that size + .position(16); // skip writes for header/body crc + } + else + { + bufSync.putInt(-cbBody); + } + + bufSync.put(MSG_SYNC) + .putLong(m_cMsgOutDelivered) + .putLong(m_cMsgIn); + + if (nProt > 2) + { + bufSync.put(m_cUnackLast == MIGRATION_LIMIT_BEFORE_DUMP ? SYNC_CMD_DUMP : SYNC_CMD_NONE); + } + + bufSync.flip(); + if (nProt > 4) + { + // backfill the message header with the header and body CRCs now that they can be computed + populateCtrlMsgHeaderCrc(bufSync); + } + + batchWriteSync.append(bufSync, /*fRecycle*/ false, /*body*/ null, /*receipt*/ null); + batchWriteSync.m_ofAck = batchWriteSync.m_ofAdd; // prevent it from accepting bundles, since it isn't resend eligible + batchWriteSync.m_next = batchResendHead; + f_cbQueued.addAndGet(batchWriteSync.getLength()); + } + else if (m_state == ConnectionState.ACTIVE) + { + // this shouldn't be possible except at protocol v0 and we don't call migrate when running at v0 + scheduleDisconnect(new IOException("protocol error; sync at protocol=" + nProt + ", with in=" + m_cMsgIn + ", out=" + m_cMsgOutDelivered)); + } + else + { + // the disconnect may have occurred before we negotiated a protocol version, we can't send a SYNC + // if we don't know the version, since by the time it gets processed on the new connection a version will + // have been negotiated and we would need to use that unknown version here. But since we haven't negotiated + // a version yet it also means we could not have exchanged messages either, so it is safe to skip the SYNC. + m_batchWriteSendHead = batchResendHead; + } + } + + /** + * Populate message header. + * + * @param bufHead the header buffer to be written to + */ + protected void populateCtrlMsgHeaderCrc(ByteBuffer bufHead) + { + int cbHeader = 16; + int nPos = bufHead.position(); + int nLimit = bufHead.limit(); + int lCrc = 0; + CRC32 crc32 = f_crcTx; + + // compute and write body CRC; Note, we still need to write a 0 when crc is disabled + // as buffers may not be zero'd out to begin with + if (crc32 != null) + { + crc32.reset(); + bufHead.position(nPos + cbHeader); + lCrc = Buffers.updateCrc(crc32, bufHead); + lCrc = lCrc == 0 ? 1: lCrc; + } + bufHead.putInt(nPos + 8, lCrc); // write body crc + + // compute and write header CRC + if (crc32 != null) + { + crc32.reset(); + bufHead.position(nPos).limit(nPos + cbHeader - 4); + lCrc = Buffers.updateCrc(crc32, bufHead); + lCrc = lCrc == 0 ? 1: lCrc; + bufHead.limit(nLimit); + } + + bufHead.putInt(nPos + 12, lCrc); // write headed crc + bufHead.position(nPos); + } + + /** + * Handle any incoming data. + * + * @param fReady true iff the channel is readable + * + * @return a partial SelectionService.Handler interest set + * + * @throws IOException if an I/O error occurs + */ + protected abstract int processReads(boolean fReady) + throws IOException; + + /** + * Write the contents of the WriteQueue to the channel. + * + * @param fReady true iff the channel is writeable + * + * @return a partial SelectionService.Handler interest set + * + * @throws IOException if an I/O error occurs + */ + protected int processWrites(boolean fReady) + throws IOException + { + long cbBacklog = f_cbQueued.get(); + + if (fReady) + { + WriteBatch batch = m_batchWriteSendHead; + long cbBundle = getAutoFlushThreshold(); + long cbExcessive = getBacklogExcessiveThreshold(); + boolean fBacklog = m_fBacklog; + + // process the queue + + // Note: Avoid the possibility of staying in this loop "forever" if the producer and consumer + // are matching pace. The only reason to bail out is to allow other work to be accomplished + // on this SelectionService thread. + long cbWritten = 0; + try + { + for (int i = 0; cbBacklog > 0 && i < 16 && (cbBacklog <= cbExcessive || fBacklog); ++i) + { + long cbBatch = batch.getLength(); + while (cbBatch != 0 && // don't bundle into an empty batch + cbBatch < cbBundle && // batch is small enough that it is worth bundling + cbBacklog > cbBatch && // there is more in the backlog, i.e. batch.bundle won't NPE + batch.m_ofAck < batch.m_ofAdd) // ensure we don't bundle into fully ack'd batches as this can lead to data loss during migration + { + cbBatch = batch.bundle(); + } + + if (cbBatch == 0 || batch.write()) // unlike in flush we don't need to sync since acks are also processed on this thread + { + cbWritten += cbBatch; + cbBacklog -= cbBatch; + + if (cbBacklog == 0) + { + // check to see if more has been queued + cbBacklog = f_cbQueued.addAndGet(-cbWritten); + cbWritten = 0; + + if (cbBacklog == 0) + { + break; + } + } + + batch = batch.next(); + } + else // we've exhausted the socket write buffer + { + cbWritten += (cbBatch - batch.getLength()); + break; + } + } + } + finally + { + cbBacklog = f_cbQueued.addAndGet(-cbWritten); + m_batchWriteSendHead = batch; + } + + // change backlog status if necessary + if (fBacklog) + { + if (cbBacklog < cbExcessive / 2) + { + m_fBacklog = false; + emitEvent(new SimpleEvent(Event.Type.BACKLOG_NORMAL, getPeer())); + } + } + else if (cbBacklog > cbExcessive) + { + m_fBacklog = true; + emitEvent(new SimpleEvent(Event.Type.BACKLOG_EXCESSIVE, getPeer())); + } + } + + return cbBacklog > 0 ? OP_WRITE : 0; + } + + @Override + public void dispose() + { + ByteBuffer bufferMsgReceipt = m_bufferRecycleOutboundReceipts; + if (bufferMsgReceipt != null) + { + // recycle resources + getSocketDriver().getDependencies().getBufferManager().release(bufferMsgReceipt); + m_bufferRecycleOutboundReceipts = null; + } + + super.dispose(); + } + + @Override + public void drainReceipts() + { + // Note: we must drain in send order, specifially + // resent, current, queue'd, unflushed + + synchronized (this) + { + WriteBatch batch = m_batchWriteUnflushed; + m_batchWriteUnflushed = null; + if (batch != null) + { + enqueueWriteBatch(batch); + } + + for (batch = m_batchWriteResendHead; batch != null; batch = batch.next()) + { + batch.m_ofSend = batch.m_ofAdd; // pretend we've sent it + f_cbQueued.addAndGet(-batch.getLength()); + int cEmit; + while ((cEmit = batch.ack(Integer.MAX_VALUE, f_aoReceiptTmp)) > 0) + { + for (int i = 0; i < cEmit; ++i) + { + Object oReceipt = f_aoReceiptTmp[i]; + f_aoReceiptTmp[i] = null; + if (oReceipt != RECEIPT_NO_EMIT) + { + addEvent(new SimpleEvent(Event.Type.RECEIPT, getPeer(), oReceipt)); + } + } + } + } + + m_batchWriteResendHead = m_batchWriteSendHead = m_batchWriteTail = new WriteBatch(/*fLink*/ false); + } + } + + /** + * Populate message header. + * + * @param buffHead the header buffer to be written to + * @param aBuff the buffer array that contains the message + * @param of the offset + * @param cBuffers the number of buffers + * @param cbBuffer the number of bytes in message + */ + protected void populateMessageHeader(ByteBuffer buffHead, ByteBuffer[] aBuff, int of, int cBuffers, long cbBuffer) + {} + + /** + * Return the receipt size that includes header and body. + * + * @return the receipt size + */ + protected abstract int getReceiptSize(); + + @Override + public String toString() + { + WriteBatch batch = m_batchWriteUnflushed; + long ldtAckTimeout = m_ldtAckTimeout; + String sTimeout; + + if (ldtAckTimeout == 0) + { + sTimeout = "n/a"; + } + else + { + long ldtNow = SafeClock.INSTANCE.getSafeTimeMillis(); + long ldtAckFatal = m_ldtAckFatalTimeout; + sTimeout = "ack=" + new Duration(ldtAckTimeout - ldtNow, Duration.Magnitude.MILLI) + + (ldtAckFatal == Long.MAX_VALUE ? "" : ", conn=" + new Duration(ldtAckFatal - ldtNow, Duration.Magnitude.MILLI)); + } + + // NOTE: there may be many more pending outs, they just didn't have receipts so we won't count them until the next message with a receipt goes out + // and the corresponding RECEIPT message comes back, at which point we'll scan the resend queue and count up the former messages which have now + // been confirmed as delivered. When comparing the toStrings of each side of a connection, we'll always see our out lag behind our peers in because + // the peer must receive it before we can *know* that it was delivered, and we defer the ack of the ack until the next application RECEIPT request + // thus peer's in is *always* greater then local out. + return super.toString() + ", bufferedOut=" + new MemorySize(f_cbQueued.get()) + + ", unflushed=" + new MemorySize(batch == null ? 0 : batch.getLength()) + + ", delivered(in=" + m_cMsgIn + ", out=" + m_cMsgOutDelivered + ")" + // see note above regarding out + ", timeout(" + sTimeout + "), interestOps=" + m_nInterestOpsLast + + ", unflushed receipt=" + m_cReceiptsUnflushed + ", receiptReturn " + m_cReceiptsReturn + + ", isReceiptFlushRequired " + isReceiptFlushRequired(); + } + + /** + * WriteBatch is used to encapsulate an array of ByteBuffers which + * are to be written to the connection. + */ + public class WriteBatch + { + public WriteBatch() + { + this(true); + } + + public WriteBatch(boolean fLink) + { + if (fLink) + { + m_batchWriteTail = m_batchWriteTail.m_next = this; + } + } + + /** + * Return the number of bytes remaining to be sent in the batch. + * + * @return the number of bytes remaining to be sent in the batch + */ + public long getLength() + { + return m_cbBatch; + } + + /** + * Append a message to the batch. + * + * This method is synchronized as it is always called from the app thread and + * the batch may be concurrently being ack processed on the SS thread. + * + * @param bufHead the buffer header to append + * @param fRecycleHead true iff the buffer header should be recycled + * @param bufseqBody optional buffer body to append + * @param receipt optional associated receipt + * + * @return the batch size + */ + public synchronized long append(ByteBuffer bufHead, boolean fRecycleHead, BufferSequence bufseqBody, Object receipt) + { + addWriter(); + + int cBufferAdd = 1 + (bufseqBody == null ? 0 : bufseqBody.getBufferCount()); + + int ofAdd = ensureAdditionalBufferCapacity(cBufferAdd); + Object[] aoReceipt = m_aReceipt; + ByteBuffer[] aBuffer = m_aBuffer; + long cbBody = 0; + if (bufseqBody != null) + { + bufseqBody.getBuffers(0, cBufferAdd - 1, aBuffer, ofAdd + 1); + cbBody = bufseqBody.getLength(); + + populateMessageHeader(bufHead, aBuffer, ofAdd + 1, cBufferAdd, cbBody); + } + + aBuffer[ofAdd] = bufHead; + + long cb = bufHead.remaining() + cbBody; + long cbUnacked = cbBody; + + if (fRecycleHead) + { + // we don't count recycled against unacked + aoReceipt[ofAdd] = RECEIPT_HEADER_RECYCLE; + } + else + { + cbUnacked += bufHead.remaining(); + } + + // mark each of the append buffers so that we can reset to initial position + // in case we need to retransmit due to migration + for (int of = ofAdd, eOf = ofAdd + cBufferAdd; of < eOf; ++of) + { + aBuffer[of].mark(); + } + + if (receipt == null && m_cbBatch == 0 && + cBufferAdd > 1) // cBufferAdd > 1 ensures it is not a control message; SYNCs shouldn't be counted and we don't want receipts for receipts + { + // in order to prevent the resend queue from growing endlessly we ensure that we + // have periodic acks by injecting artificial receipts at most once per batch. + // this becomes a real receipt from the protocol perspective, but it will never be emitted + // to the user + receipt = RECEIPT_NO_EMIT; + } + + if (receipt == null) + { + // mark message boundaries with artificial receipts, unlike RECEIPT_NO_EMIT these + // are not real from a protocol perspective + aoReceipt[ofAdd + cBufferAdd - 1] = fRecycleHead && cBufferAdd == 1 + ? RECEIPT_MSG_MARKER_HEADER_RECYCLE // combo receipt indicating msg and recycling + : RECEIPT_MSG_MARKER; + } + else if (cBufferAdd == 1) + { + if (receipt == RECEIPT_ACKREQ_MARKER || receipt == RECEIPT_ACKREQ_MARKER_RECYCLE) + { + aoReceipt[ofAdd] = receipt; + } + else + { + // we don't have a way to encode this. There is also no way for a user to cause this + // it could only be the result of a bug in the bus + throw new IllegalStateException(); + } + // else; it is an ACK with a marker; do nothing + } + else + { + aoReceipt[ofAdd + cBufferAdd - 1] = receipt; + ++m_cReceiptsUnflushed; + } + + m_ofAdd = ofAdd + cBufferAdd; + + BufferedConnection.this.f_cBytesUnacked.addAndGet(cbUnacked); + + return m_cbBatch += cb; + } + + /** + * Append the next batch into this WriteBatch. + * + * @return the new batch size + */ + public long bundle() + { + WriteBatch batchSrc = m_next; + int ofAckSrc = batchSrc.m_ofAck; + int ofSndSrc = batchSrc.m_ofSend; + int ofSrc = Math.min(ofAckSrc, ofSndSrc); // ofSend can be < ofAck + int cBufferSrc = batchSrc.m_ofAdd - ofSrc; + int ofAdd = ensureAdditionalBufferCapacity(cBufferSrc); + + System.arraycopy(batchSrc.m_aBuffer, ofSrc, m_aBuffer, ofAdd, cBufferSrc); + System.arraycopy(batchSrc.m_aReceipt, ofSrc, m_aReceipt, ofAdd, cBufferSrc); + + m_ofAdd = ofAdd + cBufferSrc; + + WriteBatch batchNext = batchSrc.m_next; + if (batchNext != null) + { + m_next = batchNext; + } + // else; avoid invalidating the tail + + if (ofAckSrc > ofSndSrc) // the source batch is partially ack'd; thus this batch is fully ack'd + { + m_ofAck += (ofAckSrc - ofSndSrc); + } + + long cbBatch = m_cbBatch += batchSrc.getLength(); + + // NOTE: f_cBytesUnacked and m_cReceiptsUnflushed don't need to be updated as it had already been + // accounted for when producing the source batch + + // make the original batch unusable + batchSrc.m_aReceipt = batchSrc.m_aBuffer = EMPTY_BUFFER_ARRAY; + batchSrc.m_ofAck = batchSrc.m_ofSend = batchSrc.m_ofAdd = 0; + batchSrc.m_cbBatch = 0; + // we don't null out batchSrc.m_next as there could still be head pointers referencing it + + return cbBatch; + } + + /** + * Attempt to write the batch to the connection. When called from the client thread synchronization + * must be held since the corresponding ack could come in and be processed on the SS thread before + * this call returns. + * + * @return true iff the entire batch has been written + * + * @throws IOException if an I/O error occurs + */ + public boolean write() + throws IOException + { + ByteBuffer[] aBuffer = m_aBuffer; + int ofSend = m_ofSend; + int ofAdd = m_ofAdd; + + long cb = BufferedConnection.this.write(aBuffer, ofSend, ofAdd - ofSend); + + // advance offset, decrement cBuffer based on amount written + if (cb > 0) + { + for (; ofSend < ofAdd && !aBuffer[ofSend].hasRemaining(); ++ofSend) + {} + + m_cbBatch -= cb; + m_ofSend = ofSend; + } + + return ofSend == ofAdd; + } + + /** + * Ack some the contents of the batch up through the next receipt. + * + * This method will emit the receipt(s) as well. + * + * @param cReceipts the maximum number of receipts to consume + * @param aoReceipt array which will be filled with receipts to emit + * + * @return the number of receipts copied into aoReceipt + */ + public int ack(int cReceipts, Object[] aoReceipt) + { + BufferManager manager = getSocketDriver().getDependencies().getBufferManager(); + int cMsg = 0; + ByteBuffer[] aBuff = m_aBuffer; + Object[] aReceipt = m_aReceipt; + int ofSend = m_ofSend; + int ofAdd = m_ofAdd; + int ofAck = m_ofAck; + int ofReceipt = 0; + + while (ofAck < ofAdd && cReceipts > 0) + { + Object oReceipt = aReceipt[ofAck]; + ByteBuffer buff = aBuff[ofAck]; + + aBuff[ofAck] = null; + aReceipt[ofAck] = null; + + if (ofAck >= ofSend) + { + // we've received an ACK for something we haven't sent yet. This can only legally happen + // after a connection migration, which means that we haven't *re*sent it yet, but our peer + // received our original transmission, we just didn't receive the ack. The ack we're processing + // is in response to our original send, and there will *not* be any ack when we resend this + // message. While it would be nice to simply not send the message, we can't do that as we've + // already told our peer (indirectly via SYNC) how many messages we'll be resending, so we + // must send that many. This batch already resides in the write queue but we can't retain these + // buffers as once they are recycled or a receipt is emitted their contents could be changed and + // thus we would be sending garbage to our peer, if we send garbage as a message length then + // the peer can't properly skip messages. We could alternately substitute alternate lengths in + // but then we'd have much more bookkeeping to do especially for messages which were partially sent. + + ByteBuffer buffNew = ByteBuffer.allocate(buff.remaining()); // normal GCable garbage + buffNew.put(buff).flip(); + aBuff[ofAck] = buffNew; + } + + ++ofAck; + + if (oReceipt == RECEIPT_HEADER_RECYCLE) + { + manager.release(buff); + } + else if (oReceipt == RECEIPT_MSG_MARKER_HEADER_RECYCLE || + oReceipt == RECEIPT_ACKREQ_MARKER_RECYCLE) + { + ++cMsg; + manager.release(buff); + } + else if (oReceipt == RECEIPT_MSG_MARKER || + oReceipt == RECEIPT_ACKREQ_MARKER) + { + ++cMsg; + } + else if (oReceipt != null) // real receipt + { + ++cMsg; + --cReceipts; + aoReceipt[ofReceipt++] = oReceipt; + if (ofReceipt == aoReceipt.length) + { + break; + } + } + } + + m_ofAck = ofAck; + m_cMsgOutDelivered += cMsg; + + return ofReceipt; + } + + /** + * Rewind the state of the batch such that any previously {@link #write sent}, but @{link #ack unacked} + * messages can be resent. + * + * @return the number of bytes which were rescheduled + */ + public long rewind() + { + int ofAck = m_ofAck; + int ofSend = m_ofSend; + ByteBuffer[] aBuffer = m_aBuffer; + long cbDelta = 0; + + if (ofSend < ofAck) // rare; only if we do a quick double migration + { + // we don't need to resend these since they've been acked before we rewound + // but we do need to adjust our batch size accordingly + for (; ofSend < ofAck; ++ofSend) + { + cbDelta -= aBuffer[ofSend].remaining(); + } + } + m_ofSend = ofAck; // our new send position + + for (int ofAdd = m_ofAdd; ofAck < ofAdd; ++ofAck) + { + ByteBuffer buff = aBuffer[ofAck]; + cbDelta -= buff.remaining(); + buff.reset(); // see append + cbDelta += buff.remaining(); + } + + m_cbBatch += cbDelta; + + return cbDelta; + } + + /** + * Ensure the buffer array has enough capacity to add the specified + * number of buffers. + * + * @param cBufferAdd the number of buffers that will be added + * + * @return the index at which to add + */ + protected int ensureAdditionalBufferCapacity(int cBufferAdd) + { + ByteBuffer[] aBuffer = m_aBuffer; + Object[] aReceipt = m_aReceipt; + int ce = aBuffer.length; + int ofSrc = Math.min(m_ofAck, m_ofSend); + int ofAdd = m_ofAdd; + int cBufferUsed = ofAdd - ofSrc; + + if (cBufferUsed + cBufferAdd > ce) + { + // reallocate and shift the buffer array + ByteBuffer[] aBufferNew = new ByteBuffer[(ce + cBufferAdd) * 2]; + System.arraycopy(aBuffer, ofSrc, aBufferNew, 0, cBufferUsed); + + Object[] aReceiptNew = new Object[aBufferNew.length]; + System.arraycopy(aReceipt, ofSrc, aReceiptNew, 0, cBufferUsed); + + m_aBuffer = aBufferNew; + m_aReceipt = aReceiptNew; + + m_ofAck -= ofSrc; + m_ofSend -= ofSrc; + + ofAdd = m_ofAdd -= ofSrc; + } + else if (ofSrc + cBufferUsed + cBufferAdd > ce) + { + // we have enough space, but need to shift the buffers + System.arraycopy(aBuffer, ofSrc, aBuffer, 0, cBufferUsed); + Arrays.fill(aBuffer, cBufferUsed, ce, null); + + System.arraycopy(aReceipt, ofSrc, aReceipt, 0, cBufferUsed); + Arrays.fill(aReceipt, cBufferUsed, ce, null); + + m_ofAck -= ofSrc; + m_ofSend -= ofSrc; + + ofAdd = m_ofAdd -= ofSrc; + } + // else; nothing to do + + return ofAdd; + } + + /** + * Return the next batch in the queue + * + * @return the next batch + */ + public WriteBatch next() + { + return m_next; + } + + /** + * Return true iff there are subsequent batches in the queue. + * + * @return true iff there are subsequent batches in the queue. + */ + public boolean hasNext() + { + return m_next != null; + } + + /** + * The total number of bytes remaining to write in the batch. + */ + protected long m_cbBatch; + + /** + * The ByteBuffer array. + */ + protected ByteBuffer[] m_aBuffer = new ByteBuffer[16]; + + /** + * An array of potentially null receipts corresponding to the buffers in m_aBuffer. + */ + protected Object[] m_aReceipt = new Object[m_aBuffer.length]; + + /** + * The next free buffer + */ + protected int m_ofAdd; + + /** + * The next offset to process while sending + */ + protected int m_ofSend; + + /** + * The next buffer to process when we receive an ack + */ + protected int m_ofAck; + + /** + * The next batch in the write queue. + */ + protected volatile WriteBatch m_next; + } + + // ----- data members ------------------------------------------- + + /** + * The tail of the write queue. + * + * This is only accessed while synchronized on the connection. + */ + protected WriteBatch m_batchWriteTail = new WriteBatch(/*fLink*/ false); + + /** + * The send pointer (head) into the write queue. + * + * This is only accessed by the SS thread associated with the connection's channel. + */ + protected WriteBatch m_batchWriteSendHead = m_batchWriteTail; + + /** + * The resend pointer (head) into the write queue. + * + * This is only accessed by the SS thread associated with the connection's channel. + * + * Note that this queue has two heads and while generally the resend head is the true + * head, in some cases the send head can actually come before it. This can occur for + * a brief period after a connection migration as the peer resends ACKs for messages + * which we've yet to resend, thus allowing us to move the resend head behind the + * send head. + */ + protected WriteBatch m_batchWriteResendHead = m_batchWriteTail; + + /** + * The number of unsent bytes in the write queue. + * + * Note this atomic also acks as the primary write barrier for the queue. Producers will first insert + * into the queue and only then update f_cbQueued, ensuring that the consumer can see the updated value + * presuming that it has first checked the size. Note that producers themselves will be sync'd on the + * connection which will ensure visibility and consistency when updating the tail. + */ + protected final AtomicLong f_cbQueued = new AtomicLong(); + + /** + * The value of m_cbWrite at the last heartbeat cycle + */ + protected long m_cbHeartbeatLast; + + /** + * True if heartbeat has identified this connection as idle and an empty receipt should be sent + */ + protected boolean m_fIdle; + + /** + * The auto-flush threshold. + */ + protected long m_cbAutoFlushThreshold; + + /** + * The force ack threshold. + */ + protected long m_cbForceAckThreshold; + + /** + * The excessive backlog threashold. + */ + protected long m_cbBacklogExcessiveThreshold; + + /** + * The last interest ops for this channel. + */ + protected int m_nInterestOpsLast; + + /** + * The current unflushed WriteBatch, or null. + * + * Unflushed means that its size hasn't been added for f_cbQueued and it is still usable by application threads. + *

+ * This field is volatile for use by isFlushRequired and isReceiptFlushRequired methods + */ + protected volatile WriteBatch m_batchWriteUnflushed = m_batchWriteTail; + + /** + * An estimate of the number of threads which contributed to the current batch. + */ + protected int m_cWritersBatch; + + /** + * Bit set used to identify threads contributing to the current batch + */ + protected long m_lWritersBatchBitSet; + + /** + * True if in the backlog state, false otherwise. + */ + protected boolean m_fBacklog; + + /** + * True if a backlog check has been scheduled. + */ + protected final AtomicBoolean m_fBacklogScheduled = new AtomicBoolean(); + + /** + * The number of receipts associated with the unflushed WriteBatch. + */ + protected int m_cReceiptsUnflushed; + + /** + * The number of receipts to return to the peer on our next send. + */ + protected final AtomicInteger m_cReceiptsReturn = new AtomicInteger(); + + /** + * As estimate of the number of unacked bytes since the last received ack or sent forced flush. + * + * TODO: now that we have a resend queue we could choose to accurately track this + */ + protected final AtomicLong f_cBytesUnacked = new AtomicLong(); + + /** + * ByteBuffer to write Receipt messages + */ + protected ByteBuffer m_bufferRecycleOutboundReceipts; + + /** + * The number of threads waiting to use the connection. + */ + protected final AtomicInteger m_cWritersWaiting = new AtomicInteger(); + + /** + * The total number of messages (including control messages) received from our peer. + */ + protected long m_cMsgIn; + + /** + * The number of upcomming inbound messages to skip (due to migration). + */ + protected long m_cMsgInSkip; + + /** + * The total number of receipts emitted. + */ + protected long m_cReceiptsEmitted; + + /** + * The total number of messages (including control messages) which have been confirmed as delivered to our peer. + */ + protected long m_cMsgOutDelivered; + + /** + * The number of bytes read as of last health check. + */ + protected long m_cbReadLastCheck; + + /** + * The number of bytes written as of last health check. + */ + protected long m_cbWriteLastCheck; + + /** + * timestamp at which the current pending ack times out + */ + protected long m_ldtAckTimeout; + + /** + * The time at which the health check must force a heartbeat + */ + protected long m_ldtForceHeartbeat; + + /** + * The id of the receipt of the last message which failed the ack health check + */ + protected int m_nIdUnackLast; + + /** + * The number of times the specific last receipt triggered a health check timeout. + */ + protected int m_cUnackLast; + + /** + * timestamp at which the current pending ack fatally times out + */ + protected long m_ldtAckFatalTimeout; + + /** + * Reusable array for holding receipts. + */ + protected final Object[] f_aoReceiptTmp = new Object[16]; + } + + + // ----- constants ------------------------------------------------------ + + /** + * The number of migrations on the same message before collecting a heap dump. + */ + protected static final int MIGRATION_LIMIT_BEFORE_DUMP = 4; + + /** + * Internal message type for exchanging receipts. + */ + protected static final byte MSG_RECEIPT = 1; + + /** + * Internal message type for syncing a connection (after a migration) + */ + protected static final byte MSG_SYNC = 2; + + /** + * Perform no additional commands upon syncing + */ + protected static final byte SYNC_CMD_NONE = 0; + + /** + * Collect a heap dump upon syncing. + */ + protected static final byte SYNC_CMD_DUMP = 1; + + /** + * For messages which don't include their own receipt, this dummy marker is used, allowing us + * to identify message boundaries within a batch. This receipt is not reflected on the wire + * it is only used to identify message boundaries. + * + * Not reflected on the wire. + */ + protected static final Object RECEIPT_MSG_MARKER = new Object(); + + /** + * Used to tag header fields as being recycleable. As these are headers they would never have a receipt of their own. + * + * Not reflected on the wire. + */ + protected static final Object RECEIPT_HEADER_RECYCLE = new Object(); + + /** + * A combination of RECEIPT_MSG_MARKER and RECEIPT_HEADER_RECYCLE for single buffer recyclable messages. + * + * Not reflected on the wire. + */ + protected static final Object RECEIPT_MSG_MARKER_HEADER_RECYCLE = new Object(); + + /** + * Used to tag ack request messages. + * + * Not reflected on the wire. + */ + protected static final Object RECEIPT_ACKREQ_MARKER = new Object(); + + /** + * Used to tag ack request messages which should be recycled. + * + * Not reflected on the wire. + */ + protected static final Object RECEIPT_ACKREQ_MARKER_RECYCLE = new Object(); + + /** + * A dummy over-the-wire receipt, i.e. we will request a receipt from our peer, but we will not emit it to + * our application when it comes in. This is used in the case that the application doesn't use receipts + * or doesn't use them often, and we want to avoid having the resend queue grow too large. + * + * This receipt *is* reflected on the wire, but never emitted to the user. + */ + protected static final Object RECEIPT_NO_EMIT = new Object(); + + /** + * Empty buffer array for use in bundling. + */ + protected static final ByteBuffer[] EMPTY_BUFFER_ARRAY = new ByteBuffer[0]; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/MultiBufferMessageEvent.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/MultiBufferMessageEvent.java new file mode 100644 index 0000000000000..413076319b762 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/MultiBufferMessageEvent.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net.socketbus; + + +import com.oracle.coherence.common.io.MultiBufferSequence; +import com.oracle.coherence.common.io.BufferManager; +import com.oracle.coherence.common.net.exabus.Event; +import com.oracle.coherence.common.net.exabus.EndPoint; + +import java.nio.ByteBuffer; + + +/** + * MultiBufferMessageEvent is an implementation of a Bus Event for Messages + * over a series of Buffers where the first and last buffer in the series come + * from SharedBuffers. + * + * @author mf 2010.12.04 + */ +public class MultiBufferMessageEvent + extends MultiBufferSequence + implements Event + { + /** + * Construct a MultiBufferMessageEvent. + * + * @param src the associated MessageConnection + * @param manager the buffer manager + * @param aBuffer the buffers + * @param of the offset of the first buffer in the sequence + * @param cBuffer the number of buffers in the sequence + * @param cbBuffer the total byte size of the sequence + * @param buff0 the SharedBuffer associated with the first buffer + * @param buffN the SharedBuffer associated with the last buffer + */ + public MultiBufferMessageEvent(SocketMessageBus.MessageConnection src, + BufferManager manager, + ByteBuffer[] aBuffer, int of, int cBuffer, long cbBuffer, + SharedBuffer buff0, SharedBuffer buffN) + { + super(manager, aBuffer, of, cBuffer, cbBuffer); + m_src = src; + m_buffer0 = buff0; + m_bufferN = buffN; + } + + + // ----- Event interface ------------------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Type getType() + { + return Type.MESSAGE; + } + + /** + * {@inheritDoc} + */ + @Override + public EndPoint getEndPoint() + { + return m_src.getPeer(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getContent() + { + return this; + } + + @Override + public Object dispose(boolean fTakeContent) + { + SocketMessageBus.MessageConnection src = m_src; + m_src = null; + + if (fTakeContent) // half dispose + { + src.onMessageDispose(this); // will intentionally NPE if double disposed + return this; // return early leave BufferSequence intact + } + else if (src != null) // full dispose of Event + { + src.onMessageDispose(this); + } + // else src == null; // dispose of decoupled BufferSequence + + BufferManager manager = f_manager; + ByteBuffer[] aBuffer = m_aBuffer; + + m_buffer0.dispose(); + + // release "middle" buffers + for (int i = f_ofBuffer + 1, c = f_ofBuffer + f_cBuffer - 1; i < c; ++i) + { + manager.release(aBuffer[i]); + } + + m_bufferN.dispose(); + + m_aBuffer = null; + return null; + } + + // ----- Disposable interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + dispose(/*fTakeContent*/ false); + } + + + // ----- Object interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return getType() + " event for " + getEndPoint() + " containing " + + getLength() + " bytes"; + } + + + + // ----- data members --------------------------------------------------- + + /** + * The MessageConnection associated with the event, or null if dispose(true) has been called. + */ + protected SocketMessageBus.MessageConnection m_src; + + /** + * The SharedBuffer which manages the first accessable buffer in the + * array, i.e. m_aBuffer[m_ofBuffer] + */ + protected final SharedBuffer m_buffer0; + + /** + * The SharedBuffer which manages the last accessable buffer in the array, + * i.e. m_aBuffer[m_ofBuffer + m_cBuffer - 1] + */ + protected final SharedBuffer m_bufferN; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SharedBuffer.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SharedBuffer.java new file mode 100644 index 0000000000000..b9041a3f7a5c1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SharedBuffer.java @@ -0,0 +1,319 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net.socketbus; + + +import com.oracle.coherence.common.base.Disposable; +import com.oracle.coherence.common.base.Holder; + +import com.oracle.coherence.common.io.BufferSequence; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +import java.nio.ByteBuffer; + + +/** + * SharedBuffer holds a ByteBuffer, and releases it back to its + * BufferManager when its reference count reaches zero. + *

+ * It is not advisable to make direct use of the ByteBuffer, but rather to + * use segments, as obtained from {@link #getSegment}, which will be a + * {@link ByteBuffer#duplicate} over a segment of the shared buffer. + * + * @author mf 2010.12.06 + */ +public class SharedBuffer + implements Holder, Disposable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a SharedBuffer from a ByteBuffer and its associated + * BufferManager. The SharedBuffer is reference counted, and has an + * inital reference count of one. The {@link #dispose} method decrements + * the counter, and will ultimately release the buffer to the manager + * once the count reaches zero. + * + * @param buffer the buffer to share + * @param disposer disposer for disposing the shared buffer + */ + public SharedBuffer(ByteBuffer buffer, Disposer disposer) + { + m_buffer = buffer; + m_disposer = disposer; + } + + + // ----- SharedBuffer interface ----------------------------------------- + + /** + * Return a segment of the shared buffer. + *

+ * A successful return from this message will have also incremented the + * reference count. + * + * @param of the segment offset + * @param cb the segment length + * + * @return the SharedBuffer.Segment. + */ + public Segment getSegment(int of, int cb) + { + return new Segment(of, cb); + } + + /** + * Return a segment of the shared buffer covering its current position to + * limit. + *

+ * A successful return from this message will have also incremented the + * reference count. + * + * @return the SharedBuffer.Segment. + */ + public Segment getSegment() + { + return new Segment(); + } + + /** + * Increment the reference count. + * + * @return this SharedBuffer + */ + public SharedBuffer attach() + { + safeAdjust(/*fIncrement*/ true); + return this; + } + + /** + * Decrement the reference count, releaseing the buffer if the count + * reaches zero. + */ + public void detach() + { + if (safeAdjust(/*fIncrement*/ false) == 0) + { + m_disposer.dispose(m_buffer); + } + } + + + // ----- Holder interface ----------------------------------------------- + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException the buffer cannot be changed + */ + public void set(ByteBuffer value) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + public final ByteBuffer get() + { + return m_buffer; + } + + + // ----- Disposable interface ------------------------------------------- + + /** + * Decrement the SharedBuffer's reference count, once it reaches zero + * the shared buffer will be released to the manager. + */ + public void dispose() + { + detach(); + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Perform a safe increment or decrement of the reference counter. + * + * @param fIncrement true for increment, false for decrement + * + * @return the new value + */ + protected final int safeAdjust(boolean fIncrement) + { + while (true) + { + int cCurr = m_cRefs; + + if (cCurr <= 0) + { + // we start with a ref count of 1, so to see a zero value + // we've already released the buffer, and it is then illegal + // to be here, someone overdecremented + throw new IllegalStateException("already disposed during " + (fIncrement ? "attach" : "detach") + +"; refCount=" + cCurr + " (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + + int cNew = cCurr + (fIncrement ? 1 : -1); + if (cCurr == Integer.MAX_VALUE) + { + // once we reach max, we must leak the object, since we can't + // maintain the count, ultimately the garbage collector will + // reclaim the memory, though it will not make it back to the + // pool + return cCurr; + } + else if (REF_COUNT_UPDATER.compareAndSet(this, cCurr, cNew)) + { + if (TRACK_DISPOSE && !fIncrement && cNew == 0) + { + m_stackDispose = new Exception("disposed at"); + } + return cNew; + } + // else; try again + } + } + + + // ----- inner class: Segment ------------------------------------------- + + /** + * Segment represents a segment of a SharedBuffer. + */ + public class Segment + implements Holder, Disposable + { + // ----- constructors ------------------------------------------- + + /** + * Construct a holder for a shared buffer, around its current position + * to limit. + */ + public Segment() + { + synchronized (SharedBuffer.this) + { + m_bufferSegment = m_buffer.slice(); + } + safeAdjust(/*fIncrement*/ true); + } + + /** + * Construct a holder for a shared buffer. + * + * @param of the segment offset + * @param cb the segment length + */ + public Segment(int of, int cb) + { + synchronized (SharedBuffer.this) + { + ByteBuffer buff = m_buffer; + buff.limit(of + cb).position(of); + m_bufferSegment = buff.slice(); + } + safeAdjust(/*fIncrement*/ true); + } + + // ----- Holder interface --------------------------------------- + + /** + * {@inheritDoc} + * + * @throws UnsupportedOperationException the buffer cannot be changed + */ + @Override + public void set(ByteBuffer value) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer get() + { + return m_bufferSegment; + } + + // ----- Disposable interface ----------------------------------- + + /** + * Decrement the reference count of the SharedBuffer associated with + * this Segment. + */ + @Override + public void dispose() + { + SharedBuffer.this.detach(); + } + + // ----- data members ------------------------------------------- + + /** + * The local accessable portion of the shared buffer. + */ + protected final ByteBuffer m_bufferSegment; + } + + // ----- inner interface: Disposer -------------------------------------- + + /** + * Disposer used by the SharedBuffer to dispose ByteBuffer + */ + public interface Disposer + { + + /** + * Dispose byte buffer + * + * @param buffer byte buffer to dispose + */ + public void dispose(ByteBuffer buffer); + } + + + // ----- data members --------------------------------------------------- + + /** + * The buffer. + */ + protected final ByteBuffer m_buffer; + + /** + * The associated Disposer + */ + protected final Disposer m_disposer; + + /** + * The reference count. + */ + protected volatile int m_cRefs = 1; + + /** + * The stack at which the sequence was destroyed, if trackDestroy is enabled. + */ + protected volatile Exception m_stackDispose; + + /** + * The reference count updater. + */ + private static final AtomicIntegerFieldUpdater REF_COUNT_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(SharedBuffer.class, "m_cRefs"); + + /** + * True iff dispose locations should be tracked (for debugging) + */ + private static final boolean TRACK_DISPOSE = Boolean.getBoolean( + BufferSequence.class.getName() + ".trackDispose"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SingleBufferMessageEvent.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SingleBufferMessageEvent.java new file mode 100644 index 0000000000000..7c34c4b0d2dcd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SingleBufferMessageEvent.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net.socketbus; + + +import com.oracle.coherence.common.net.exabus.Event; +import com.oracle.coherence.common.net.exabus.EndPoint; +import com.oracle.coherence.common.io.SingleBufferSequence; + + +/** + * SingleBufferMessageEvent is an implementation of a Bus Event for Messages + * over a single SharedBuffer. + * + * @author mf 2010.12.07 + */ +public class SingleBufferMessageEvent + extends SingleBufferSequence + implements Event + { + /** + * Construct a SingleBufferSequence around a single ByteBuffer. + *

+ * The BufferSequence will directly reference the supplied buffer, subsequent + * modifications to the buffer or its positions are not allowed. + * + * @param src the associated connection + * @param buffer the buffer + */ + public SingleBufferMessageEvent(SocketMessageBus.MessageConnection src, + SharedBuffer buffer) + { + super(/*manager*/ null, buffer.get().slice()); + m_src = src; + m_bufferShared = buffer; + } + + /** + * Construct an "unsafe" SingleBufferMessageEvent around a single ByteBuffer. + *

+ * The BufferSequence will directly reference the supplied buffer, + * subsequent modifications to the buffer are not allowed. The buffer's positions will + * not be relied upon and thus are safe to update externally. + * + * @param src the associated connection + * @param buffer the buffer + * @param nPos the position within the buffer + * @param cb the number of bytes + */ + public SingleBufferMessageEvent(SocketMessageBus.MessageConnection src, SharedBuffer buffer, int nPos, int cb) + { + super(/*manager*/ null, buffer.get(), nPos, cb); + m_src = src; + m_bufferShared = buffer; + } + + // ----- Event interface ------------------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Type getType() + { + return Type.MESSAGE; + } + + /** + * {@inheritDoc} + */ + @Override + public EndPoint getEndPoint() + { + return m_src.getPeer(); + } + + /** + * {@inheritDoc} + */ + @Override + public Object getContent() + { + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Object dispose(boolean fTakeContent) + { + SocketMessageBus.MessageConnection src = m_src; + m_src = null; + + if (fTakeContent) // half dispose + { + src.onMessageDispose(this); // will intentionally NPE if double disposed + return this; // return early leave BufferSequence intact + } + else if (src != null) // full dispose of Event + { + src.onMessageDispose(this); + } + // else src == null; // dispose of decoupled BufferSequence + + m_bufferShared.dispose(); + m_buffer = null; + return null; + } + +// ----- Disposable interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + dispose(/*fTakeContent*/ false); + } + + + // ----- Object interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return getType() + " event for " + getEndPoint() + " containing " + + getLength() + " bytes"; + } + + + // ----- data members --------------------------------------------------- + + /** + * The MessageConnection associated with the event, or null if dispose(true) has been called. + */ + protected SocketMessageBus.MessageConnection m_src; + + /** + * The SharedBuffer which manages buffer. + */ + protected final SharedBuffer m_bufferShared; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SocketBusDriver.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SocketBusDriver.java new file mode 100644 index 0000000000000..f170c9b1b14ce --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SocketBusDriver.java @@ -0,0 +1,1142 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net.socketbus; + +import com.oracle.coherence.common.base.Hasher; +import com.oracle.coherence.common.net.SelectionService; +import com.oracle.coherence.common.net.SocketProvider; +import com.oracle.coherence.common.net.SelectionServices; +import com.oracle.coherence.common.net.InetSocketAddressHasher; +import com.oracle.coherence.common.net.exabus.Bus; +import com.oracle.coherence.common.net.exabus.Depot; +import com.oracle.coherence.common.net.exabus.EndPoint; +import com.oracle.coherence.common.net.exabus.util.UrlEndPoint; +import com.oracle.coherence.common.net.exabus.spi.Driver; +import com.oracle.coherence.common.io.BufferManager; +import com.oracle.coherence.common.io.BufferManagers; +import com.oracle.coherence.common.util.Duration; +import com.oracle.coherence.common.util.MemorySize; + +import java.io.IOException; +import java.net.SocketAddress; +import java.net.ServerSocket; +import java.net.SocketOptions; +import java.net.SocketException; +import java.util.logging.Logger; + + +/** + * SocketDriver is a base implementation for socket based busses. + * + * @author mf 2010.12.27 + */ +public class SocketBusDriver + implements Driver + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a SocketDriver. + * + * @param deps the driver's dependencies + */ + public SocketBusDriver(Dependencies deps) + { + m_dependencies = copyDependencies(deps).validate(); + } + + + // ----- Driver interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + public void setDepot(Depot depot) + { + m_depot = depot; + } + + /** + * {@inheritDoc} + */ + public Depot getDepot() + { + return m_depot; + } + + /** + * {@inheritDoc} + */ + public EndPoint resolveEndPoint(String sName) + { + if (sName == null) + { + return null; + } + + Dependencies deps = getDependencies(); + String sMsg = deps.getMessageBusProtocol(); + String sMem = deps.getMemoryBusProtocol(); + if (sName.startsWith(sMsg) || sName.startsWith(sMem)) + { + try + { + UrlEndPoint point = resolveSocketEndPoint(sName); + String sProtocol = point.getProtocol(); + + if (sProtocol.equals(sMsg) || sProtocol.equals(sMem)) + { + return point; + } + } + catch (IllegalArgumentException e) + { + if (sName.startsWith(sMsg + UrlEndPoint.PROTOCOL_DELIMITER) || + sName.startsWith(sMem + UrlEndPoint.PROTOCOL_DELIMITER)) + { + throw e; + } + } + } + return null; + } + + /** + * {@inheritDoc} + */ + public boolean isSupported(EndPoint point) + { + if (point == null) + { + return true; + } + else if (point instanceof UrlEndPoint) + { + Dependencies deps = getDependencies(); + String sProtocol = ((UrlEndPoint) point).getProtocol(); + return sProtocol.equals(deps.getMessageBusProtocol()) || + sProtocol.equals(deps.getMemoryBusProtocol()); + } + else + { + return false; + } + } + + /** + * {@inheritDoc} + */ + public Bus createBus(EndPoint pointLocal) + { + if (isSupported(pointLocal)) + { + try + { + Dependencies deps = getDependencies(); + UrlEndPoint pointSocket = (UrlEndPoint) pointLocal; + String sProtocol = pointSocket.getProtocol(); + + if (sProtocol.equals(deps.getMessageBusProtocol())) + { + return new SocketMessageBus(this, pointSocket); + } + // else; fall through + } + catch (IOException e) + { + throw new RuntimeException("Error creating SocketBus " + + "instance for " + pointLocal, e); + } + } + + throw new IllegalArgumentException("unsupported EndPoint " + pointLocal); + } + + + // ----- helpers ------------------------------------------------------- + + /** + * Resolve the supplied canonical name into a SocketEndPoint. + * + * @param sName the endpoint name + * + * @return the resolved EndPoint + * + * @throws IllegalArgumentException if the name is not a valid SocketEndPoint + */ + public UrlEndPoint resolveSocketEndPoint(String sName) + { + Dependencies deps = getDependencies(); + return new UrlEndPoint(sName, deps.getSocketProvider(), + deps.getSocketAddressHasher()); + } + + /** + * Resolve the EndPoint which the specified service socket is bound to. + * + * @param pointLocal the requested bind point + * @param socket the bound socket + * + * @return the EndPoint + */ + public UrlEndPoint resolveBindPoint(UrlEndPoint pointLocal, ServerSocket socket) + { + Dependencies deps = getDependencies(); + SocketProvider provider = deps.getSocketProvider(); + String sQuery = pointLocal.getQueryString(); + return new UrlEndPoint(pointLocal.getProtocol() + UrlEndPoint.PROTOCOL_DELIMITER + + provider.getAddressString(socket) + (sQuery == null ? "" : "?" + sQuery), provider, + deps.getSocketAddressHasher()); + } + + + /** + * Return the driver's Dependencies. + * + * @return the driver's Dependencies + */ + public Dependencies getDependencies() + { + return m_dependencies; + } + + /** + * Produce a shallow copy of the supplied dependencies. + * + * @param deps the dependencies to copy + * + * @return the dependencies + */ + protected DefaultDependencies copyDependencies(Dependencies deps) + { + return new DefaultDependencies(deps); + } + + + // ----- inner interface: Dependencies ---------------------------------- + + /** + * Dependencies provides an interface by which the SocketBusDriver can + * be provided with its external dependencies. + */ + public interface Dependencies + { + /** + * Return the MessageBus protocol prefix. + * + * @return the MessageBus protocol prefix + */ + public String getMessageBusProtocol(); + + /** + * Return the MemoryBus protocol prefix. + * + * @return the MemoryBus protocol prefix + */ + public String getMemoryBusProtocol(); + + /** + * Return the SelectionService used to run this driver. + * + * @return the SelectionService. + */ + public SelectionService getSelectionService(); + + /** + * Return the SocketProvider to use in producing sockets for this + * driver. + * + * @return the SocketProvider + */ + public SocketProvider getSocketProvider(); + + /** + * Return the SocketAddress Hasher to use in comparing SocketAddresses. + * + * @return the SocketAddress Hasher + */ + public Hasher getSocketAddressHasher(); + + /** + * Return the SocketOptions to utilize in this driver. + * + * @return the SocketOptions + */ + public SocketOptions getSocketOptions(); + + /** + * Return the BufferManager to use in creating temporary buffers. + * + * @return the BufferManager + */ + public BufferManager getBufferManager(); + + /** + * Return the Logger to use. + * + * @return the logger + */ + public Logger getLogger(); + + /** + * Max time after which the receipt acks will be sent to the peer. + * + * @return receipt ack delay in millis + */ + public long getMaximumReceiptDelayMillis(); + + /** + * Return the number of milliseconds after which a connection is considered to be bad due to a missing + * acknowledgement and the connection should be reestablished. + * + * @return the timeout in milliseconds, or 0 for indefinite + */ + public long getAckTimeoutMillis(); + + /** + * Return the number of milliseconds after which a connection is considered to be unrecoverable due to a missing + * acknowledgement and the connection an unsolicited DISCONNECT event should be emitted + * + * @return the timeout in milliseconds, or 0 for indefinite + */ + public long getAckFatalTimeoutMillis(); + + /** + * Return the interval between reconnect attempts + * + * @return socket reconnect interval in millis + */ + public long getSocketReconnectDelayMillis(); + + /** + * Return the maximum number sequential reconnect attempts to make + * + * @return the reconnect limit + */ + public int getSocketReconnectLimit(); + + /** + * Return the maximum time a connection may be left idle before + * the bus automatically injects traffic (hidden to the application) + * in order to ensure that the underlying network infrastructure + * does not disconnect the socket due to an idle timeout. + * + * Note: This is not SO_KEEPALIVE, this is a higher level construct + * which ensures the connection is used often enough to not appear + * idle. While SO_KEEPALIVE operates similarly there are a few + * key differences. First the interval is not controllable by the + * user. Second when SO_KEEPALIVE actually encourages termination + * of connections due to temporary network outages, that is it also + * serves as a time based disconnect detection. SO_KEEPALIVE is + * most often used to try to keep an idel connection from being + * terminated by the network infrastructure. This is a misguided use + * of the feature and often doesn't work as intended, hense this + * higher level version. + * + * @return the idle timeout in milliseconds + */ + public long getHeartbeatMillis(); + + /** + * Threshold at which a send/signal operation should perform + * an auto-flush of the unflushed write batch. + * + * @return the number of queue'd bytes at which to auto-flush + */ + public long getAutoFlushThreshold(); + + /** + * Threshold at which to request an immediate receipt from the peer. + * + * @return the number of bytes to send before requesting immediate receipts + */ + public long getReceiptRequestThreshold(); + + /** + * Return the maximum number of threads which should concurrently attempt direct writes. + *

+ * A direct write is a write performed on the calling thread rather then on a background thread. Direct writes + * tend to reduce latency when contention is low. When contention is high though better throughput and latency + * may be achieved by allowing writes to be offloaded to background threads. + *

+ * + * @return the thread count + */ + public int getDirectWriteThreadThreshold(); + + /** + * For the purposes of testing, this method specifies a percentage of read operations which + * should result in an underlying connection failure. Specifically the socket's input and output + * streams will be shutdown. + * + * @return a drop ratio + */ + public int getDropRatio(); + + /** + * For the purposes of testing, this method specifies a percentage of read operations which + * should result in a bit flip of the stream. This is to simulate data corruption from wire, should + * result in connection migration. + * + * @return a corruption ratio + */ + public int getCorruptionRatio(); + + /** + * Return true if CRC validation is enabled. + * + * @return true if CRC validation is enabled + */ + public boolean isCrcEnabled(); + } + + + // ----- inner class: DefaultDependencies ------------------------------- + + /** + * SimpleDependencies provides a basic Dependencies implementation as well + * as default values where applicable. + */ + public static class DefaultDependencies + implements Dependencies + { + /** + * Construct a DefaultDependencies object. + */ + public DefaultDependencies() + { + } + + /** + * Construct a DefaultDependencies object copying the values from the + * specified dependencies object + * + * @param deps the dependencies to copy, or null + */ + public DefaultDependencies(Dependencies deps) + { + if (deps != null) + { + m_provider = deps.getSocketProvider(); + m_hasher = deps.getSocketAddressHasher(); + m_service = deps.getSelectionService(); + m_sProtocolMessageBus = deps.getMessageBusProtocol(); + m_sProtocolMemoryBus = deps.getMemoryBusProtocol(); + m_options = deps.getSocketOptions(); + m_bufferManager = deps.getBufferManager(); + m_logger = deps.getLogger(); + m_cMaxReceiptDelayMillis = deps.getMaximumReceiptDelayMillis(); + m_cReconnectLimit = deps.getSocketReconnectLimit(); + m_cReconnectDelayMillis = deps.getSocketReconnectDelayMillis(); + m_cHeartbeatDelayMillis = deps.getHeartbeatMillis(); + m_cAckTimeoutMillis = deps.getAckTimeoutMillis(); + m_cAckFatalTimeoutMillis = deps.getAckFatalTimeoutMillis(); + m_cbAutoFlush = deps.getAutoFlushThreshold(); + m_cbReceiptRequest = deps.getReceiptRequestThreshold(); + m_cThreadsDirect = deps.getDirectWriteThreadThreshold(); + m_nDropRatio = deps.getDropRatio(); + m_nCorruptionRatio = deps.getCorruptionRatio(); + m_fCrc = deps.isCrcEnabled(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public String getMessageBusProtocol() + { + return m_sProtocolMessageBus; + } + + /** + * Specify the message bus protcol name + * + * @param sProtocol the message bus protocol name + * + * @return this object + */ + public DefaultDependencies setMessageBusProtocol(String sProtocol) + { + m_sProtocolMessageBus = sProtocol; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public String getMemoryBusProtocol() + { + return m_sProtocolMemoryBus; + } + + /** + * Specify the memory bus protcol name + * + * @param sProtocol the memory bus protocol name + * + * @return this object + */ + public DefaultDependencies setMemoryBusProtocol(String sProtocol) + { + m_sProtocolMemoryBus = sProtocol; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public SelectionService getSelectionService() + { + SelectionService svc = m_service; + return svc == null ? SelectionServices.getDefaultService() : svc; + } + + + /** + * Specify the SelectionService to be used by this driver. + * + * @param service the SelectionService + * + * @return this object + */ + public DefaultDependencies setSelectionService(SelectionService service) + { + m_service = service; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public SocketProvider getSocketProvider() + { + return m_provider; + } + + /** + * Specify the SocketProvider to use. + * + * @param provider the SocketProvider to use. + * + * @return this object + */ + public DefaultDependencies setSocketProvider(SocketProvider provider) + { + m_provider = provider; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Hasher getSocketAddressHasher() + { + Hasher hasher = m_hasher; + if (hasher == null) + { + return InetSocketAddressHasher.INSTANCE; + } + return hasher; + } + + /** + * Specify the SocketAddress Hasher to be used in comparing addresses. + * + * @param hasher the hasher + * + * @return this object + */ + public DefaultDependencies setSocketAddressHahser(Hasher hasher) + { + m_hasher = hasher; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public SocketOptions getSocketOptions() + { + SocketOptions options = m_options; + return options == null ? DEFAULT_OPTIONS : options; + } + + /** + * Specify the SocketOptions to use. + * + * @param options the options + * + * @return this object + */ + public DefaultDependencies setSocketOptions(SocketOptions options) + { + m_options = options; + return this; + } + + + /** + * {@inheritDoc} + */ + @Override + public BufferManager getBufferManager() + { + BufferManager manager = m_bufferManager; + return manager == null ? DEFAULT_BUFFER_MANAGER : manager; + } + + /** + * Specify the BufferManager to be used by this driver. + * + * @param manager the buffer manager + * + * @return this object + */ + public DefaultDependencies setBufferManager(BufferManager manager) + { + m_bufferManager = manager; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Logger getLogger() + { + Logger logger = m_logger; + return logger == null ? LOGGER : logger; + } + + /** + * Specify the Logger to use. + * + * @param logger the logger + * + * @return this object + */ + public DefaultDependencies setLogger(Logger logger) + { + m_logger = logger; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public long getMaximumReceiptDelayMillis() + { + return m_cMaxReceiptDelayMillis; + } + + /** + * Set maximum receipt ack delay + * + * @param cDelayMillis Max receipt ack delay in millis + * + * @return this object + */ + public DefaultDependencies setMaximumReceiptDelayMillis(long cDelayMillis) + { + m_cMaxReceiptDelayMillis = cDelayMillis; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public long getAckTimeoutMillis() + { + return m_cAckTimeoutMillis; + } + + /** + * Set ack timeout + * + * @param cAckTimeoutMillis ack timeout, or 0 for indefinite + * + * @return this object + */ + public DefaultDependencies setAckTimeoutMillis(long cAckTimeoutMillis) + { + m_cAckTimeoutMillis = cAckTimeoutMillis; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public long getAckFatalTimeoutMillis() + { + return m_cAckFatalTimeoutMillis; + } + + /** + * Set ack timeout + * + * @param cAckFatalTimeoutMillis ack timeout, or 0 for indefinite + * + * @return this object + */ + public DefaultDependencies setAckFatalTimeoutMillis(long cAckFatalTimeoutMillis) + { + m_cAckFatalTimeoutMillis = cAckFatalTimeoutMillis; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public long getSocketReconnectDelayMillis() + { + return m_cReconnectDelayMillis; + } + + /** + * Set reconnect interval for the Connection + * + * @param cDelayMillis reconnect delay in millis + * + * @return this object + */ + public DefaultDependencies setSocketReconnectDelayMillis(long cDelayMillis) + { + m_cReconnectDelayMillis = cDelayMillis; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public int getSocketReconnectLimit() + { + return m_cReconnectLimit; + } + + /** + * Set the reconnect limit + * + * @param cReconnectLimit the reconnect limit + * + * @return this object + */ + public DefaultDependencies setSocketReconnectLimit(int cReconnectLimit) + { + m_cReconnectLimit = cReconnectLimit; + return this; + } + + @Override + public long getHeartbeatMillis() + { + return m_cHeartbeatDelayMillis; + } + + /** + * Set the heartbeat interface for the connection. + * + * @param cMillis the heartbeat interval + * + * @return this object + */ + public DefaultDependencies setHeartbeatMillis(long cMillis) + { + m_cHeartbeatDelayMillis = cMillis; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public long getAutoFlushThreshold() + { + return m_cbAutoFlush; + } + + /** + * Set threshold for auto flush + * + * @param cbThreshold auto flush threshold + * + * @return this object + */ + public DefaultDependencies setAutoFlushThreshold(long cbThreshold) + { + m_cbAutoFlush = cbThreshold; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public long getReceiptRequestThreshold() + { + return m_cbReceiptRequest; + } + + /** + * Set threshold for receipt requests + * + * @param cbRequest receipt request threshold + * + * @return this object + */ + public DefaultDependencies setReceiptRequestThreshold(long cbRequest) + { + m_cbReceiptRequest = cbRequest; + return this; + } + + @Override + public int getDirectWriteThreadThreshold() + { + return m_cThreadsDirect; + } + + /** + * Specify the direct write threshold. + * + * @param cThreads the number of threads + * + * @return this object + */ + public DefaultDependencies setDirectWriteThreadThreshold(int cThreads) + { + m_cThreadsDirect = cThreads; + return this; + } + + @Override + public int getDropRatio() + { + return m_nDropRatio; + } + + /** + * Specify the drop rate. + * + * @param nDropRatio the ration to drop, i.e. 1:nDropRatio + * + * @return this object + */ + public DefaultDependencies setDropRatio(int nDropRatio) + { + m_nDropRatio = nDropRatio; + return this; + } + + @Override + public int getCorruptionRatio() + { + return m_nCorruptionRatio; + } + + /** + * Specify the drop rate. + * + * @param nCorruptionRatio the ration to drop, i.e. 1:nCorruptionRatio + * + * @return this object + */ + public DefaultDependencies setCorruptionRatio(int nCorruptionRatio) + { + m_nCorruptionRatio = nCorruptionRatio; + return this; + } + + @Override + public boolean isCrcEnabled() + { + return m_fCrc; + } + + /** + * Specify CRC validation should be enabled. + * + * @param fCrc true if CRC is enabled + * + * @return this object + */ + public DefaultDependencies isCrcEnabled(boolean fCrc) + { + m_fCrc = fCrc; + return this; + } + + + // ----- helpers ------------------------------------------------ + + /** + * Validate the supplied dependencies. + * + * @throws IllegalArgumentException if the dependencies are not valid + * + * @return this object + */ + protected DefaultDependencies validate() + { + ensureArgument(getMemoryBusProtocol(), "MemoryBusProtocol"); + ensureArgument(getMessageBusProtocol(), "MessageBusProtocol"); + ensureArgument(getSelectionService(), "SelectionService"); + ensureArgument(getSocketAddressHasher(), "SocketAddressHasher"); + ensureArgument(getSocketProvider(), "SocketProvider"); + + if (getMemoryBusProtocol().equals(getMessageBusProtocol())) + { + throw new IllegalArgumentException( + "memory and mess bus protocols cannot use the sane names"); + } + + return this; + } + + /** + * Ensure that the specified object is non-null + * + * @param o the object to ensure + * @param sName the name of the corresponding parameter + * + * @throws IllegalArgumentException if o is null + */ + protected static void ensureArgument(Object o, String sName) + { + if (o == null) + { + throw new IllegalArgumentException(sName + " cannot be null"); + } + } + + + // ----- data members ------------------------------------------- + + /** + * The SocketProvider to use when producing sockets. + */ + protected SocketProvider m_provider; + + /** + * The SocketAddress hasher. + */ + protected Hasher m_hasher; + + /** + * The SelectionService the busses will use for IO processing. + */ + protected SelectionService m_service; + + /** + * The message bus protocol prefix. + */ + protected String m_sProtocolMessageBus; + + /** + * The message bus protocol prefix. + */ + protected String m_sProtocolMemoryBus; + + /** + * The SocketOptions. + */ + protected SocketOptions m_options; + + /** + * The BufferManager. + */ + protected BufferManager m_bufferManager; + + /** + * The Logger. + */ + protected Logger m_logger; + + /** + * Reconnect interval for the Connection in millis + */ + protected long m_cReconnectDelayMillis = new Duration(System.getProperty( + SocketBusDriver.class.getName()+".reconnectDelayMillis", "200ms")).getNanos()/1000000; + + /** + * The maximum number of sequential reconnects to attempt. + */ + protected int m_cReconnectLimit = Integer.parseInt(System.getProperty( + SocketBusDriver.class.getName()+".reconnectLimit", "3")); + + /** + * Maximum receipt ack delay in millis + */ + protected long m_cMaxReceiptDelayMillis = new Duration(System.getProperty( + SocketBusDriver.class.getName()+".maxReceiptDelayMillis", "500ms")).getNanos()/1000000; + + /** + * Ack timeout in millis + */ + protected long m_cAckTimeoutMillis = new Duration(System.getProperty( + SocketBusDriver.class.getName()+".ackTimeoutMillis", "10s")).getNanos()/1000000; + + /** + * Fatal ack timeout in millis + */ + protected long m_cAckFatalTimeoutMillis = new Duration(System.getProperty( + SocketBusDriver.class.getName()+".fatalTimeoutMillis", "10m")).getNanos()/1000000; + + /** + * Heartbeat interval in millis, disabled by default now that we support reconnects + */ + protected long m_cHeartbeatDelayMillis = new Duration(System.getProperty( + SocketBusDriver.class.getName()+".heartbeatInterval", "0s")).getNanos()/1000000; + + /** + * Auto flush threshold + */ + protected long m_cbAutoFlush = getSafeMemorySize(System.getProperty( + SocketBusDriver.class.getName()+".autoFlushThreshold")); + + /** + * Threshold after which to request receipts. + */ + protected long m_cbReceiptRequest = getSafeMemorySize(System.getProperty( + SocketBusDriver.class.getName()+".receiptRequestThreshold")); + + /** + * The maximum number of concurrent writers on which to attempt direct writes. + */ + protected int m_cThreadsDirect = Integer.parseInt(System.getProperty( + SocketBusDriver.class.getName() + ".directWriteThreadThreshold", "1")); + + /** + * The drop ratio. + */ + protected int m_nDropRatio = Integer.parseInt(System.getProperty( + SocketBusDriver.class.getName() + ".dropRatio", "0")); + + /** + * The force corruption ratio. + */ + protected int m_nCorruptionRatio = Integer.parseInt(System.getProperty( + SocketBusDriver.class.getName() + ".corruptionRatio", "0")); + + /** + * True iff CRC validation is enabled + */ + protected boolean m_fCrc = Boolean.parseBoolean(System.getProperty( + SocketBusDriver.class.getName() + ".crc", "false")); + + + private static long getSafeMemorySize(String sValue) + { + if (sValue == null) + { + return -1; + } + return new MemorySize(sValue).getByteCount(); + } + + // ----- constants ---------------------------------------------- + + /** + * Default BufferManager. + */ + public static final BufferManager DEFAULT_BUFFER_MANAGER; + + static + { + String sManager = System.getProperty(SocketBusDriver.class.getName() + ".bufferManager", "network"); + switch (sManager) + { + case "heap": + DEFAULT_BUFFER_MANAGER = BufferManagers.getHeapManager(); + break; + + case "direct": + DEFAULT_BUFFER_MANAGER = BufferManagers.getDirectManager(); + break; + + case "network": + DEFAULT_BUFFER_MANAGER = BufferManagers.getNetworkDirectManager(); + break; + + default: + throw new IllegalArgumentException("unknown BufferManager: " + sManager); + } + } + + /** + * Default SocketOptions. + */ + public static final SocketOptions DEFAULT_OPTIONS = new SocketOptions() + { + @Override + public void setOption(int optID, Object value) + throws SocketException + { + throw new UnsupportedOperationException(); + } + + @Override + public Object getOption(int optID) + throws SocketException + { + switch (optID) + { + case TCP_NODELAY: + return true; + + case SO_LINGER: + return 0; + + case SO_RCVBUF: + return RX_BUFFER_SIZE == -1 ? null : RX_BUFFER_SIZE; + + case SO_SNDBUF: + return TX_BUFFER_SIZE == -1 ? null : TX_BUFFER_SIZE; + + default: + return null; + } + } + + final int RX_BUFFER_SIZE = (int) getSafeMemorySize(System.getProperty( + SocketBusDriver.class.getName()+".socketRxBuffer")); + final int TX_BUFFER_SIZE = (int) getSafeMemorySize(System.getProperty( + SocketBusDriver.class.getName()+".socketTxBuffer")); + }; + } + + + // ----- constants ------------------------------------------------------ + + /** + * The default Logger for the driver. + */ + private static Logger LOGGER = Logger.getLogger(SocketBusDriver.class.getName()); + + + // ----- data members --------------------------------------------------- + + /** + * The Depot managing this driver. + */ + protected Depot m_depot; + + /** + * The driver's dependencies. + */ + protected Dependencies m_dependencies; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SocketMessageBus.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SocketMessageBus.java new file mode 100644 index 0000000000000..011c3513d3edc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/socketbus/SocketMessageBus.java @@ -0,0 +1,1329 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net.socketbus; + + +import com.oracle.coherence.common.base.Disposable; +import com.oracle.coherence.common.internal.net.ProtocolIdentifiers; +import com.oracle.coherence.common.internal.util.HeapDump; +import com.oracle.coherence.common.io.Buffers; +import com.oracle.coherence.common.net.exabus.MessageBus; +import com.oracle.coherence.common.net.exabus.EndPoint; +import com.oracle.coherence.common.net.exabus.Event; +import com.oracle.coherence.common.net.exabus.util.UrlEndPoint; +import com.oracle.coherence.common.net.exabus.util.SimpleEvent; +import com.oracle.coherence.common.io.BufferSequence; +import com.oracle.coherence.common.io.BufferManager; +import com.oracle.coherence.common.io.BufferSequenceInputStream; +import com.oracle.coherence.common.util.MemorySize; + +import java.io.IOException; +import java.io.DataInput; +import java.nio.ByteBuffer; + +import java.util.Arrays; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import java.util.LinkedList; +import java.util.zip.CRC32; + +import java.net.SocketException; + +import java.util.logging.Level; + + +/** + * SocketMessageBus is a reliable MessageBus implementation based + * upon sockets. + * + * @author mf 2010.12.03 + */ +public class SocketMessageBus + extends BufferedSocketBus + implements MessageBus + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a SocketMessageBus. + * + * + * @param driver the socket driver + * @param pointLocal the local endpoint + * + * @throws IOException if an I/O error occurs + */ + public SocketMessageBus(SocketBusDriver driver, UrlEndPoint pointLocal) + throws IOException + { + super(driver, pointLocal); + } + + + // ----- AbstractSocketBus methods -------------------------------------- + + /** + * {@inheritDoc} + */ + protected String getProtocolName() + { + return getSocketDriver().getDependencies().getMessageBusProtocol(); + } + + /** + * {@inheritDoc} + */ + protected int getProtocolIdentifier() + { + return ProtocolIdentifiers.SOCKET_MESSAGE_BUS; + } + + + // ----- MessageBus methods --------------------------------------------- + + /** + * {@inheritDoc} + */ + public void send(EndPoint peer, BufferSequence bufseq, Object receipt) + { + if (bufseq == null) + { + throw new NullPointerException("Null BufferSequence for message: " + receipt); + } + + MessageConnection conn = (MessageConnection) ensureConnection(peer); + if (conn.getProtocolVersion() < 0 && conn.deferSend(bufseq, receipt)) + { + // we haven't finished handshaking yet + return; + } + + AtomicInteger cWriters = conn.m_cWritersWaiting; + + cWriters.getAndIncrement(); // track threads waiting to use the connection + synchronized (conn) + { + cWriters.getAndDecrement(); + + conn.ensureValid().evaluateAutoFlush(conn.isFlushInProgress(), conn.isFlushRequired(), + conn.send(bufseq, receipt)); + } + } + + + // ----- MessageConnection interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + protected Connection makeConnection(UrlEndPoint peer) + { + return new MessageConnection(peer); + } + + /** + * MessageConnection implements a reliable message connection, + */ + public class MessageConnection + extends BufferedConnection + { + /** + * Create a MessageConnection for the specified peer. + * + * @param peer the peer + */ + public MessageConnection(UrlEndPoint peer) + { + super(peer); + } + + /** + * Return the threshold at which to stop reading additional data from + * the socket, based on the number of undisposed bytes in the event + * queue. + * + * @return the threshold in bytes + */ + protected long getReadThrottleThreshold() + { + long cb = m_cbReadThreshold; + + if (cb <= 0) + { + try + { + // threshold is a multiple of the underlying buffer size + // the assumption is that any given socket read could + // return a full buffers worth of data, and that should + // not be considered to be "excessive", but once we've + // fallen a number of buffers worth behind it is time to + // throttle. While this could be made a configuration + // setting, it is not necessary since the underlying + // buffer size is already configurable and will directly + // influence this setting. + m_cbReadThreshold = cb = getReceiveBufferSize() * 8; + } + catch (SocketException e) {} + + if (cb <= 0) + { + // not yet connected + cb = 64 * 1024; + } + } + + return cb; + } + + /** + * {@inheritDoc} + */ + protected int processReads(boolean fReady) + throws IOException + { + if (f_fBacklogLocal.get() && m_cbEventQueue.get() > getReadThrottleThreshold()) + { + // if a backlog has been declared *and* we've exceeded the read threshold then stop reading; once + // the backlog is cleared (BACKLOG_EXCESSIVE event consumed) then we'll be awoken so we can continue. + // Note if we only checked f_fBacklogLocal then we'd end up hurting performance as at the time the backlog + // was cleared we'd have no messages ready to be processed. If we only checked m_cbEventQueue then we'd + // risk stalling indefinitely as we could cross the limit temporarily and uncross it before a backlog + // was ever declared. Thus we check both. + return 0; + } + else if (fReady) + { + ReadBatch batch = m_readBatch; + if (batch == null) + { + batch = m_readBatch = new ReadBatch(); + batch.m_cbRequired = getMessageHeaderSize(); + } + + batch.read(); + + if (batch.m_fHeader && batch.m_cbReadable == 0) + { + // we've fully read the current message and there was no partial message + // in the buffer, so it could potentially be awhile before we use this + // connection again. Avoid needlessly holding onto the read buffer memory. + // Note, in a high throughput case it is likely that we'll have read at least some + // of the next message and thus avoid the cost associated with allocation/disposal + + batch.dispose(); + m_readBatch = null; + return OP_READ; + } + else + { + // we've at least partially read the next message, assume the remainder will be here + // soon + return OP_READ | OP_EAGER; + } + } + + return OP_READ; + } + + @Override + public void dispose() + { + ReadBatch batch = m_readBatch; + if (batch != null) + { + m_readBatch = null; + batch.dispose(); + } + ByteBuffer bufferHdr = m_bufferMsgHdr; + if (bufferHdr != null) + { + getSocketDriver().getDependencies().getBufferManager().release(bufferHdr); + m_bufferMsgHdr = null; + } + + super.dispose(); + } + + /** + * Schedule a send of the specified BufferSequence. + * + * @param bufseq the BufferSequence + * @param receipt the optional receipt + * + * @return the length of the current WriteBatch + */ + public long send(BufferSequence bufseq, Object receipt) + { + long cbMsg = bufseq.getLength(); + int nProt = getProtocolVersion(); + int cbHeader = getMessageHeaderSize(); + + if (cbMsg < 0 || (nProt < 5 && cbMsg > Integer.MAX_VALUE)) + { + throw new UnsupportedOperationException( + "unsupported message size " + cbMsg); + } + + WriteBatch batch = m_batchWriteUnflushed; + if (batch == null) + { + batch = m_batchWriteUnflushed = new WriteBatch(); + } + + if (m_bufferMsgHdr == null) + { + m_bufferMsgHdr = getSocketDriver().getDependencies() + .getBufferManager().acquire(cbHeader * 1024); //4KB or 24KB for version 5 + + int cbCap = m_bufferMsgHdr.capacity(); + m_bufferMsgHdr.limit(cbCap - (cbCap % cbHeader)); + } + + // prepend the message data with it's header + ByteBuffer bufferMsgHdr = m_bufferMsgHdr; + if (bufferMsgHdr.remaining() > cbHeader) + { + ByteBuffer buffHeader = bufferMsgHdr.slice(); + buffHeader.limit(buffHeader.position() + cbHeader); + bufferMsgHdr.position(bufferMsgHdr.position() + cbHeader); + + batch.append(buffHeader, /*fRecycle*/ false, bufseq, receipt); + } + else // bufferMsgHdr.remaining() == getMessageHeaderSize() + { + // write last chunk in append and recycle + batch.append(bufferMsgHdr, /*fRecycle*/ true, bufseq, receipt); + m_bufferMsgHdr = null; + } + + ++m_cMsgUserOut; + if (receipt == null) + { + ++m_cReceiptsNull; + } + return batch.getLength(); + } + + @Override + protected void populateMessageHeader(ByteBuffer bufHead, ByteBuffer[] aBuffer, int of, int cBuffers, long cbBuffer) + { + int nProt = getProtocolVersion(); + int nPos = bufHead.position(); + if (nProt > 4) + { + CRC32 crc32 = f_crcTx; + int lCrcBody = 0; + int nLimit = bufHead.limit(); + + // write message length + bufHead.putLong(nPos, cbBuffer); + nPos += 8; + + // write body CRC + if (crc32 != null) + { + crc32.reset(); + lCrcBody = Buffers.updateCrc(crc32, aBuffer, of, cbBuffer); + lCrcBody = lCrcBody == 0 ? 1 : lCrcBody; + } + + bufHead.putInt(nPos, lCrcBody); + + nPos += 4; + + // write header CRC + int lCrcHeader = 0; + if (crc32 != null) + { + crc32.reset(); + bufHead.limit(nPos); // set limit for crc calc + lCrcHeader = Buffers.updateCrc(crc32, bufHead); + lCrcHeader = lCrcHeader == 0 ? 1 : lCrcHeader; + bufHead.limit(nLimit); // reset limit for crc write + } + + bufHead.putInt(nPos, lCrcHeader); + } + else + { + bufHead.putInt(nPos, (int) cbBuffer); + } + } + + /** + * Return message header size based on protocol version. + * + * @return message header size + */ + public int getMessageHeaderSize() + { + int nProt = getProtocolVersion(); + if (nProt > 4) + { + return 16; // 8B body length + 4B body crc, 4B header crc + } + else if (nProt >= 0) + { + return 4; // 4B body length + } + else + { + throw new IllegalStateException("connection is not ready!"); + } + } + + @Override + protected int getReceiptSize() + { + int nProt = getProtocolVersion(); + + return nProt > 4 ? 25 : 13; // header + body (9 bytes) + } + + @Override + protected void setProtocolVersion(int nProt) + { + // drain the requests sent before this point + synchronized (this) + { + super.setProtocolVersion(nProt); + + LinkedList> queue = m_queuePreNegotiate; + if (queue != null) + { + if (m_fBacklog) + { + m_fBacklog = false; + emitEvent(new SimpleEvent(Event.Type.BACKLOG_NORMAL, getPeer())); + } + + Pair pair = null; + try + { + while (true) + { + pair = queue.poll(); + if (pair == null) + { + break; + } + send(pair.getKey(), pair.getValue()); + } + flush(); + + m_queuePreNegotiate = null; + } + catch (Throwable e) + { + queue.addFirst(pair); + scheduleDisconnect(e); + } + } + } + } + + @Override + public void drainReceipts() + { + synchronized (this) + { + LinkedList> queue = m_queuePreNegotiate; + m_queuePreNegotiate = null; + if (queue != null) + { + for (Pair pair : queue) + { + Object oReceipt = pair.getValue(); + if (oReceipt != null) + { + addEvent(new SimpleEvent(Event.Type.RECEIPT, getPeer(), oReceipt)); + } + } + + // need to change backlog status, see connection.deferSend + if (m_fBacklog) + { + m_fBacklog = false; + emitEvent(new SimpleEvent(Event.Type.BACKLOG_NORMAL, getPeer())); + } + } + + super.drainReceipts(); + } + } + + @Override + public String toString() + { + ReadBatch batch = m_readBatch; + long cReceiptsEmitted = m_cReceiptsEmitted; + long cReceiptsNull = m_cReceiptsNull; + return super.toString() + ", bufferedIn(" + (batch == null ? "" : batch) + "), " + + "msgs(in=" + m_cMsgUserIn + + ", out=" + m_cReceiptsEmitted + (cReceiptsNull == 0 ? "" : "[" + (cReceiptsEmitted + cReceiptsNull) + "]") + + "/" + m_cMsgUserOut + ")"; + } + + // ----- inner class: ReadBatch ------------------------------------- + + /** + * ReadBatch handles the reading and processing of the inbound byte + * stream. + */ + public class ReadBatch + extends AtomicReference + implements Disposable, SharedBuffer.Disposer + { + /** + * Ensure that m_aBuffer has sufficient writable capacity. + * + * @param cb the required write capacity + * + * @return the ensured buffer array + */ + public ByteBuffer[] ensureCapacity(long cb) + { + BufferManager manager = getSocketDriver().getDependencies().getBufferManager(); + ByteBuffer[] aBuffer = m_aBuffer; + int ofReadable = m_ofReadable; + int ofWritable = m_ofWritable; + long cbWritable = m_cbWritable; + int cBufferWritable = m_cBufferWritable; + int cBuffer = aBuffer.length; + long cbAlloc = cb - cbWritable; + long cbMin = Math.max(getPacketSize(), 16*1024); // minimum read buffer size, used to reduce the number of read system calls + + while (cbAlloc > 0) + { + int ofEnd = ofWritable + cBufferWritable; + int ofAlloc; + if (ofEnd < cBuffer && aBuffer[ofEnd] == null) + { + // next block has not been allocated + ofAlloc = ofEnd; + } + else if (ofEnd + 1 < cBuffer) + { + // there are free slots after the write tail + ofAlloc = ofEnd + 1; + } + else if (ofReadable > 0) + { + // there are free slots at the head, compact and allocate + // at the tail; shifting everything back by ofReadable + ofAlloc = ofEnd - ofReadable; + System.arraycopy(aBuffer, ofReadable, aBuffer, 0, ofAlloc); + Arrays.fill(aBuffer, ofAlloc, ofAlloc + ofReadable, null); + ofWritable -= ofReadable; + ofEnd -= ofReadable; + ofReadable = 0; + } + else + { + // there are no free slots, new array is required + ByteBuffer[] aBufferNew = new ByteBuffer[cBuffer * 2]; + System.arraycopy(aBuffer, 0, aBufferNew, 0, cBuffer); + + aBuffer = aBufferNew; + ofAlloc = cBuffer; + cBuffer = aBuffer.length; + } + + // attempt to allocate at least the required amount plus a bit of extra + long cbStop = Math.max(cbMin, cbWritable + cbAlloc + getMessageHeaderSize()); + for (int i = ofAlloc; i < cBuffer && cbWritable < cbStop; ++i) + { + ByteBuffer buff = getAndSet(null); + if (buff == null) + { + buff = manager.acquirePref((int) Math.min(Integer.MAX_VALUE, cbStop - cbWritable)); + } + buff.clear().mark(); + int cbBuff = buff.remaining(); + cbAlloc -= Math.min(cbBuff, cbAlloc); + cbWritable += cbBuff; + ++cBufferWritable; + aBuffer[i] = buff; + } + } + + m_aBuffer = aBuffer; + m_ofReadable = ofReadable; + m_ofWritable = ofWritable; + m_cbWritable = cbWritable; + m_cBufferWritable = cBufferWritable; + + return aBuffer; + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + // we must dispose of buffers in m_aBuffer which have not + // already been handed out as messages, specifically everything + // after m_ofReadable has to be released + + int of = m_ofReadable; + SharedBuffer buffShared = m_bufferShared; + ByteBuffer[] aBuffer = m_aBuffer; + ByteBuffer buffer0 = aBuffer[of]; + if (buffShared != null && buffShared.get() == buffer0) + { + buffShared.dispose(); + ++of; + } + BufferManager manager = getSocketDriver().getDependencies().getBufferManager(); + for (int c = m_ofWritable + m_cBufferWritable; of < c; ++of) + { + manager.release(aBuffer[of]); + } + ByteBuffer buf = getAndSet(Buffers.getEmptyBuffer()); + if (buf != null) + { + manager.release(buf); + } + } + + + @Override + public void dispose(ByteBuffer buffer) + { + if (!compareAndSet(null, buffer)) + { + getSocketDriver().getDependencies().getBufferManager().release(buffer); + } + } + + /** + * Process reads. + * + * @throws IOException on an I/O error + */ + public void read() + throws IOException + { + long cbAlloc = Math.abs(m_cbRequired) - m_cbWritable; + ByteBuffer[] aBuffer = cbAlloc > 0 ? ensureCapacity(cbAlloc) : m_aBuffer; + int of = m_ofWritable; + int cBuffer = m_cBufferWritable; + + long cb = MessageConnection.this.read(aBuffer, of, cBuffer); + if (cb > 0) + { + for (; cBuffer > 0 && !aBuffer[of].hasRemaining(); ++of, --cBuffer) + { + aBuffer[of].reset(); // prepare for reading + } + + m_ofWritable = of; + m_cBufferWritable = cBuffer; + m_cbWritable -= cb; + long cbReady = m_cbReadable += cb; + + if (cbReady >= Math.abs(m_cbRequired)) + { + if (cBuffer > 0 && aBuffer[of].position() > 0) + { + ByteBuffer buffLast = aBuffer[of]; + int iPosWrite = buffLast.position(); + // the last buffer is partially readable + try + { + buffLast.limit(iPosWrite).reset(); + onReady(); + } + finally + { + // by definition buffLast still has some space + // left to write into, reset the position data; + // any message referncing this buffer would be + // doing so through a slice of a SharedBuffer + buffLast.mark(); + buffLast.limit(buffLast.capacity()).position(iPosWrite); + } + } + else + { + onReady(); + } + } + } + else if (cb < 0) + { + migrate(new IOException("input shutdown")); + } + } + + + /** + * Process the next message(s) from the already read data. + * + * @throws IOException on an I/O error + */ + public void onReady() + throws IOException + { + boolean fFlush = false; + long cbReadable = m_cbReadable; + long cbRequired = m_cbRequired; + boolean fHeader = m_fHeader; + final AtomicLong cbEvents = m_cbEventQueue; + long cMsgIn = m_cMsgIn; + long cMsgInUser = m_cMsgUserIn; + long cMsgInSkip = m_cMsgInSkip; + SharedBuffer buffShared0 = m_bufferShared; + long lCrcBody = m_lCrcBodyNext; + int cbHeader = getMessageHeaderSize(); + int nProt = getProtocolVersion(); + CRC32 crc32 = f_crcRx; + + ByteBuffer buffer0; + + if (buffShared0 == null) + { + buffShared0 = m_bufferShared = new SharedBuffer(buffer0 = m_aBuffer[m_ofReadable], this); + } + else + { + buffer0 = buffShared0.get(); + } + + try + { + // process all available headers and messages + while (cbReadable >= Math.abs(cbRequired)) + { + if (fHeader) + { + // ofReadable is left as is, the buffers will be part + // of the message, though not accessible + fHeader = false; + cbReadable -= cbHeader; + + int of = m_ofReadable; + ByteBuffer[] aBuff = m_aBuffer; + + long lCrcHeaderRx; // header CRC received + long lCrcHeaderCalc = 0; // recalculated header CRC + if (nProt > 4) + { + if (crc32 != null) + { + crc32.reset(); + lCrcHeaderCalc = Buffers.updateCrc(crc32, aBuff, of, cbHeader - 4); + lCrcHeaderCalc = lCrcHeaderCalc == 0 ? 1 : lCrcHeaderCalc; + } + + cbRequired = Buffers.getLong(aBuff, of); + lCrcBody = Buffers.getInt(aBuff, of); + lCrcHeaderRx = Buffers.getInt(aBuff, of); + + if (lCrcHeaderCalc != lCrcHeaderRx && lCrcHeaderRx != 0 && lCrcHeaderCalc != 0) + { + // CRC does not match, migrate connection + throw new IOException("incorrect CRC, corrupted header buffer; CRC read: " + + lCrcHeaderRx + " CRC re-calculated: " + lCrcHeaderCalc); + } + } + else + { + cbRequired = Buffers.getInt(aBuff, of); + } + } + else // message body + { + long cbMsg = Math.abs(cbRequired); + + Event event; + long lCrcBodyCalc = 0; + if (buffer0.remaining() >= cbMsg) // highly optimized single buffer message + { + if (crc32 != null && lCrcBody != 0) + { + crc32.reset(); + lCrcBodyCalc = Buffers.updateCrc(crc32, buffer0, cbMsg); + lCrcBodyCalc = lCrcBodyCalc == 0 ? 1 : lCrcBodyCalc; + if (lCrcBodyCalc != lCrcBody) + { + // checksum does not match, migrate connection + throw new IOException("incorrect CRC, corrupted message buffer; CRC read: " + + lCrcBody + " CRC re-calculated: " + lCrcBodyCalc); + } + } + + int nPos = buffer0.position(); + event = new SingleBufferMessageEvent(MessageConnection.this, + buffShared0.attach(), nPos, (int) cbMsg); + buffer0.position(nPos + (int) cbMsg); + } + else // multi-buffer message + { + if (crc32 != null && lCrcBody != 0) + { + crc32.reset(); + lCrcBodyCalc = Buffers.updateCrc(crc32, m_aBuffer, m_ofReadable, cbMsg); + lCrcBodyCalc = lCrcBodyCalc == 0 ? 1 : lCrcBodyCalc; + if (lCrcBodyCalc != lCrcBody) + { + // checksum does not match, migrate connection + throw new IOException("incorrect checksum, corrupted message buffer"); + } + } + + event = makeMultiBufferMessageEvent(cbMsg); + + + // re-read updated fields + buffShared0 = m_bufferShared; + buffer0 = buffShared0.get(); + } + + cbEvents.addAndGet(cbMsg); + + if (cMsgInSkip == 0) + { + fFlush = true; + + if (cbRequired >= 0) + { + ++cMsgIn; + ++cMsgInUser; + addEvent(event); + } + else if (onControlMessage(event)) + { + ++cMsgIn; + } + else // onControl returned false; thus it was a SYNC message, re-read skip count + { + cMsgInSkip = m_cMsgInSkip; + } + } + else // we have post migration messages to skip + { + --cMsgInSkip; + getLogger().log(Level.FINER, "{0} skipping" + (cbRequired < 0 ? " control " : " ") + + "message of {1} bytes from {2} after migration, {3} remain to be skipped on {4}", + new Object[]{getLocalEndPoint(), cbMsg, getPeer(), cMsgInSkip, MessageConnection.this}); + + if (cMsgInSkip == 0) + { + getLogger().log(Level.FINE, "{0} resuming migrated connection with {1}", + new Object[]{getLocalEndPoint(), MessageConnection.this}); + } + event.dispose(); + } + + fHeader = true; + cbReadable -= cbMsg; + cbRequired = cbHeader; + } + } + } + finally + { + m_cMsgUserIn = cMsgInUser; + m_cMsgIn = cMsgIn; + m_cMsgInSkip = cMsgInSkip; + m_cbReadable = cbReadable; + m_cbRequired = cbRequired; + m_fHeader = fHeader; + m_lCrcBodyNext = lCrcBody; + + if (fFlush) + { + if (cbEvents.get() > (getReadThrottleThreshold() * 2) / 3) + { + // At 2/3 of the threshold we consider ourselves "backlogged", + // we will add a task to eventLast to end this backlog + // when the event is consumed. Note that we will stop + // adding to the backlog when we hit the threshold, which + // means that the event queue should have another 1/3 worth + // of content when we end the backlog, hopefully prevent + // the queues from being bursty. + + // logic is factored out for hot-spotting benefit + issueLocalBacklog(); + } + + flushEvents(); + } + } + } + + /** + * Extract a multi-buffer message from an array of ByteBuffers. + * + * @param cbMsg the message length + * + * @return the message event + */ + protected MultiBufferMessageEvent makeMultiBufferMessageEvent(long cbMsg) + { + ByteBuffer[] aBuffer = m_aBuffer; + SharedBuffer buffShared = m_bufferShared; + int ofReadable = m_ofReadable; + long cbEnd = cbMsg; + int ofEnd = ofReadable; + + // find the end buffer + for (int cbBuf = aBuffer[ofEnd].remaining(); + cbEnd > cbBuf; + cbBuf = aBuffer[++ofEnd].remaining()) + { + cbEnd -= cbBuf; + } + + int cBufferMsg = (ofEnd - ofReadable) + 1; + + ByteBuffer buffer0 = aBuffer[ofReadable]; + ByteBuffer bufferN = aBuffer[ofEnd]; + + int iLimitN = bufferN.limit(); + bufferN.limit(bufferN.position() + (int) cbEnd); + + ByteBuffer[] aBufferMsg = new ByteBuffer[cBufferMsg]; + + // copy middle buffers + System.arraycopy(aBuffer, ofReadable + 1, aBufferMsg, 1, cBufferMsg - 2); + + // no slice required here, this is the last + // message to use this buffer + aBufferMsg[0] = buffer0; + SharedBuffer buffShared0 = buffShared; + + // replace buffShared with new SharedBuffer around bufferN + m_bufferShared = buffShared = new SharedBuffer(bufferN, this); + + aBufferMsg[cBufferMsg - 1] = bufferN.slice(); + + MultiBufferMessageEvent event = new MultiBufferMessageEvent( + MessageConnection.this, + getSocketDriver().getDependencies().getBufferManager(), + aBufferMsg, 0, cBufferMsg, cbMsg, + buffShared0.attach(), buffShared.attach()); + buffShared0.dispose(); + + // update the read position restore the limit of the + // last buffer (for next message) + bufferN.position(bufferN.limit()).limit(iLimitN); + + for (; ofReadable < ofEnd; ++ofReadable) + { + aBuffer[ofReadable] = null; // allow for GC + } + + m_ofReadable = ofReadable; + + return event; + } + + /** + * Handle a control event. + * + * @param event the control event + * + * @return true if this should count as a received message + * + * @throws IOException on an I/O error + */ + public boolean onControlMessage(Event event) + throws IOException + { + try + { + if (event.getType() != Event.Type.MESSAGE) + { + throw new IllegalStateException( + "unexpected control event: " + event); + } + + DataInput in = new BufferSequenceInputStream((BufferSequence) + event.getContent()); + byte nType = in.readByte(); + switch (nType) + { + case MSG_RECEIPT: + processReceipt(in); + return true; + + case MSG_SYNC: + processSync(in); + return false; // SYNC is not a resendable message and as such does not contribute to the message count + + default: + String sDump = HeapDump.dumpHeapForBug("28240730"); + getLogger().log(makeRecord(Level.WARNING, + "{0} received a corrupt message from {1}; collected {2} for analysis", + getLocalEndPoint(), getPeer(), sDump)); + + throw new IllegalStateException("unexpected control message type: " + nType); + } + + } + finally + { + event.dispose(); + } + } + + @Override + public String toString() + { + return "ready=" + new MemorySize(m_cbReadable) + + ", pending=" + new MemorySize(Math.abs(m_cbRequired)) + + ", free=" + new MemorySize(m_cbWritable); + } + + + // ----- data members --------------------------------------- + + /** + * The ByteBuffer array. + */ + protected ByteBuffer[] m_aBuffer = new ByteBuffer[2]; + + /** + * The offset of the first buffer to read into (i.e. write to). + */ + protected int m_ofWritable; + + /** + * The number of unfilled buffers remaining, starting at m_ofWritable. + */ + protected int m_cBufferWritable; + + /** + * The number of writable bytes. + */ + protected long m_cbWritable; + + /** + * The array offset of the first readable buffer. + */ + protected int m_ofReadable; + + /** + * The number of readable bytes. + */ + protected long m_cbReadable; + + /** + * The number of bytes required for the next header or message. + *

+ * The value will be negative for control messages. + */ + protected long m_cbRequired; + + /** + * The CRC for next message. + */ + protected long m_lCrcBodyNext; + + /** + * True if waiting for a message header, false if waiting for + * a message. + */ + protected boolean m_fHeader = true; + + /** + * Shared buffer protecting remainder of buffer at m_ofReadable. + */ + protected SharedBuffer m_bufferShared; + } + + + // ----- helpers ------------------------------------------------ + + /** + * Called as part of disposing a MessageEvent. + * + * @param bufseq the pre-disposed message + */ + public void onMessageDispose(BufferSequence bufseq) + { + AtomicLong atomicCb = m_cbEventQueue; + long cbSeq = bufseq.getLength(); + long cbCur; + + // Decrement the outstanding byte count by the length of the disposed + // sequence. Ensure that the count doesn't go negative, which could + // happen do to artificially zeroing out the count as part of the + // inbound flow-control processing (COH-5379), see issueLocalBacklog + do + { + cbCur = atomicCb.get(); + } + while (!atomicCb.compareAndSet(cbCur, Math.max (0, cbCur - cbSeq))); + } + + @Override + public void onMigration() + { + super.onMigration(); + + ReadBatch batchRead = m_readBatch; + if (batchRead != null) + { + // discard any partially read messages, they will be resent in their entirety on the new connection + getLogger().log(Level.FINER, "{0} discarding partial message from {1} consisting of {2} out of {3} bytes on {4}", + new Object[] {getLocalEndPoint(), getPeer(), batchRead.m_cbReadable, batchRead.m_cbRequired, MessageConnection.this}); + batchRead.dispose(); + m_readBatch = null; + } + } + + /** + * Defer send if handshake is not complete. + * + * @param bufSeq the message + * @param receipt the optional receipt + * + * @return true if the send has been deferred + */ + protected boolean deferSend(BufferSequence bufSeq, Object receipt) + { + synchronized (this) + { + ensureValid(); + + if (getProtocolVersion() < 0) + { + LinkedList> queue = m_queuePreNegotiate; + + if (queue == null) + { + queue = m_queuePreNegotiate = new LinkedList<>(); + + // emit the BACKLOG_EXCESSIVE event to prevent OOM in case of delayed handshake + invoke(() -> + { + if (!m_fBacklog) + { + m_fBacklog = true; + emitEvent(new SimpleEvent( + Event.Type.BACKLOG_EXCESSIVE, + getPeer())); + } + }); + } + queue.add(new Pair<>(bufSeq, receipt)); + + if (m_state == ConnectionState.DEFUNCT) + { + invoke(this::drainReceipts); + } + return true; + } + } + return false; + } + + + // ----- data members ------------------------------------------- + + /** + * The read buffer data. + */ + protected ReadBatch m_readBatch; + + /** + * The limit on how much inbound data to buffer. + */ + protected long m_cbReadThreshold; + + /** + * ByteBuffer to write Message headers. + */ + protected ByteBuffer m_bufferMsgHdr; + + /** + * The total number emitted messages; + */ + protected long m_cMsgUserIn; + + /** + * The total number of messages given to the connection to send. + */ + protected long m_cMsgUserOut; + + /** + * The total number of null receipts given with sends. + */ + protected long m_cReceiptsNull; + + /** + * The queue that holds the requests with bufseq and receipt before + * connection is ready + */ + protected LinkedList> m_queuePreNegotiate = null; + } + + // ----- inner class: TaskEvent ---------------------------- + + /** + * TaskEvent is a wrapper around a normal event, but utilizes + * the mandatory dispose() call to run a number of tasks on behalf + * of the bus, hopefully from the application rather then the bus + * thread. + */ + public static class TaskEvent + implements Event + { + public TaskEvent(Event event, Runnable... aTask) + { + m_event = event; + m_aTask = aTask; + } + + public Type getType() + { + return m_event.getType(); + } + + public EndPoint getEndPoint() + { + return m_event.getEndPoint(); + } + + public Object getContent() + { + return m_event.getContent(); + } + + public Object dispose(boolean fTakeContent) + { + Object o = m_event.dispose(fTakeContent); + + for (Runnable task : m_aTask) + { + task.run(); + } + + return o; + } + + public void dispose() + { + dispose(/*fTakeContent*/ false); + } + + public String toString() + { + return m_event.toString(); + } + + private final Event m_event; + private final Runnable[] m_aTask; + } + + /** + * Wakeup all connections. + */ + protected void wakeupConnections() + { + for (Connection conn : getConnections()) + { + try + { + conn.wakeup(); + } + catch (IOException e) + { + conn.onException(e); + } + } + } + /** + * Put the bus in the local backlog state, issuing any requisite events. + */ + protected void issueLocalBacklog() + { + if (f_fBacklogLocal.compareAndSet(false, true)) + { + // emit backlog task event + addEvent(new TaskEvent(new SimpleEvent(Event.Type.BACKLOG_EXCESSIVE, getLocalEndPoint()), + new Runnable() + { + public void run() + { + // Now that this event has been consumed we assume that all prior + // events have also been consumed, ending the backlog + + // emit end backlog event + emitEvent(new SimpleEvent( + Event.Type.BACKLOG_NORMAL, + getLocalEndPoint())); + + // Note: artificially resetting to zero here + // is a work around for an issue that occurs + // if events are disposed out of order + // (as is done by Coherence). + // Essentially we treat the processing/disposal of the BACKLOG_EXCESSIVE + // event as the application telling us "I'm keeping whatever I'm holding, so stop counting it against me" + m_cbEventQueue.set(0); + + // backlog can only be true since this is the only way to set it to false, we simply + // have to emit the backlog event first to ensure that we keep our backlog events + // in the proper order + f_fBacklogLocal.set(false); + + // one or more connections may have disabled reads during the backlog, re-enable them + // now that the backlog has been cleared + wakeupConnections(); + } + })); + } + // else; already in the backlog state + } + + /** + * A helper class to store a pair. + */ + public static class Pair + { + public Pair(K key, V value) + { + m_key = key; + m_value = value; + } + + public K getKey() + { + return m_key; + } + + public V getValue() + { + return m_value; + } + + private K m_key; + private V m_value; + } + + + // ----- data members --------------------------------------------------- + + /** + * True if we are locally backlogged. + */ + protected final AtomicBoolean f_fBacklogLocal = new AtomicBoolean(); + + /** + * The approximate size of the data in the event queue. + */ + protected final AtomicLong m_cbEventQueue = new AtomicLong(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSelector.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSelector.java new file mode 100644 index 0000000000000..5373de229a08d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSelector.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net.ssl; + +import com.oracle.coherence.common.internal.net.WrapperSelector; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.spi.SelectorProvider; + + +/** + * SSLSelector implementation. + * + * @author mf + */ +public class SSLSelector + extends WrapperSelector + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a SSLSelector + * + * @param selector the selector to wrap + * @param provider the SSLSocketProvider + * + * @throws java.io.IOException if an I/O error occurs + */ + public SSLSelector(Selector selector, SelectorProvider provider) + throws IOException + { + super(selector, provider); + } + + + // ----- SSLSelector interface ------------------------------------------ + + /** + * Update the specified SelectionKey's interest ops. + * + * @param key the key to update + * @param nOps the ops of interest + */ + public void setInterestOps(SelectionKey key, int nOps) + { + synchronized (m_oInterestLock) + { + wakeup(); // COH-7389, ensure that we don't block indefinitely if we're in select + + key.interestOps(nOps); + } + } + + // ----- Selector interface --------------------------------------------- + + + @Override + public int select(long timeout) + throws IOException + { + // COH-7389, ensure that we don't other threads block indefinitely if we're in select + synchronized (m_oInterestLock) {} + + return super.select(timeout); + } + + + // ----- Object interface ----------------------------------------------- + + public String toString() + { + return "SSLSelector(" + m_delegate + ")"; + } + + + // ----- data members ---------------------------------------------------- + + + /** + * Monitor used to prevent concurrent calls to Selector.select() and SelectionKey.interestOps, which + * blocks indefinitely on IBM's JDK. + */ + protected final Object m_oInterestLock = new Object(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSelectorProvider.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSelectorProvider.java new file mode 100644 index 0000000000000..da17678b75c9c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSelectorProvider.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.net.ssl; + +import java.net.ProtocolFamily; +import java.nio.channels.spi.SelectorProvider; +import java.nio.channels.spi.AbstractSelector; +import java.nio.channels.DatagramChannel; +import java.nio.channels.Pipe; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.io.IOException; + + +/** + * SSLSelectorProvider is a provider of Selectors for SSL based channels. + * + * @author mf 2011.02.07 + */ +public class SSLSelectorProvider + extends SelectorProvider + { + // ----- constructors ------------------------------------------- + + public SSLSelectorProvider(SelectorProvider delegate) + { + m_delegate = delegate; + } + + // ----- SelectorProvider interface ----------------------------- + + @Override + public DatagramChannel openDatagramChannel() + throws IOException + { + throw new UnsupportedOperationException(); + } + + public DatagramChannel openDatagramChannel(ProtocolFamily family) + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public Pipe openPipe() + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public AbstractSelector openSelector() + throws IOException + { + return new SSLSelector(m_delegate.openSelector(), this); + } + + @Override + public ServerSocketChannel openServerSocketChannel() + throws IOException + { + throw new UnsupportedOperationException(); + } + + @Override + public SocketChannel openSocketChannel() + throws IOException + { + throw new UnsupportedOperationException(); + } + + + // ----- Object interface --------------------------------------- + + @Override + public boolean equals(Object o) + { + if (o == this) + { + return true; + } + else if (o instanceof SSLSelectorProvider) + { + return m_delegate.equals(((SSLSelectorProvider) o).m_delegate); + } + else + { + return false; + } + } + + @Override + public int hashCode() + { + return m_delegate.hashCode(); + } + + // ----- data members ------------------------------------------- + + /** + * The delegate SelectorProvider. + */ + protected SelectorProvider m_delegate; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLServerSocket.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLServerSocket.java new file mode 100644 index 0000000000000..baf43efc6ef2d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLServerSocket.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.internal.net.ssl; + + +import com.oracle.coherence.common.internal.net.WrapperServerSocket; +import com.oracle.coherence.common.net.SSLSocketProvider; + + +import java.io.IOException; + +import java.net.ServerSocket; +import java.net.Socket; + + +/** +* Wrapper server socket implementation that performs hostname verfication +* during connect. +* +* @author jh 2010.04.27 +*/ +public class SSLServerSocket + extends WrapperServerSocket + { + // ----- constructor ---------------------------------------------------- + + /** + * Create a new SSLServerSocket that delegates all operations to the given + * server socket. + * + * @param socket the delegate server socket + * @param provider the SSLSocketProvider that created this SSLServerSocket + * + * @throws IOException on error opening the socket + */ + public SSLServerSocket(ServerSocket socket, SSLSocketProvider provider) + throws IOException + { + super(socket); + if (provider == null) + { + throw new IllegalArgumentException(); + } + m_provider = provider; + } + + + // ----- ServerSocket methods ------------------------------------------- + + /** + * {@inheritDoc} + */ + public Socket accept() + throws IOException + { + return new SSLSocket(m_delegate.accept(), m_provider); + } + + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return "SSLServerSocket{" + m_delegate + "}"; + } + + + // ----- data members --------------------------------------------------- + + /** + * The SSLSocketProvider that created this SSLSocket. + */ + protected final SSLSocketProvider m_provider; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLServerSocketChannel.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLServerSocketChannel.java new file mode 100644 index 0000000000000..eff460ea352e3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLServerSocketChannel.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.internal.net.ssl; + + +import com.oracle.coherence.common.internal.net.WrapperServerSocketChannel; +import com.oracle.coherence.common.net.SSLSocketProvider; + +import java.io.IOException; + +import java.nio.channels.IllegalBlockingModeException; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + + +/** +* SSLServerSocketChannel is a ServerSocketChannel which accepts SSL clients +* and returns SSLSocketChannels. +* +* SSLServerSocketChannel does not currently support blocking channels, it must +* be configured and used in non-blocking mode only. +* +* @author mf 2010.04.27 +* @since Coherence 3.6 +*/ +public class SSLServerSocketChannel + extends WrapperServerSocketChannel + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an SSLServerSocketChannel which wraps an un-secured + * ServerSocketChannel. + * + * @param channel the un-secuired ServerSocketChannel + * @param provider the SSLSocketProvider associated with this Channel + * + * @throws IOException if an I/O error occurs + */ + public SSLServerSocketChannel(ServerSocketChannel channel, + SSLSocketProvider provider) + throws IOException + { + super(channel, new SSLSelectorProvider(channel.provider())); + + m_providerSocket = provider; + m_fBlocking = channel.isBlocking(); + } + + + // ----- ServerSocketChannel methods ------------------------------------ + + /** + * {@inheritDoc} + */ + public SocketChannel accept() + throws IOException + { + if (m_fBlocking) + { + throw new IllegalBlockingModeException(); + } + SocketChannel chan = f_delegate.accept(); + return chan == null + ? null + : new SSLSocketChannel(chan, getSocketProvider()); + } + + + // ----- AbstractSelectableChannel methods ------------------------------ + + /** + * {@inheritDoc} + */ + protected void implConfigureBlocking(boolean block) + throws IOException + { + if (block) + { + throw new IllegalBlockingModeException(); + } + f_delegate.configureBlocking(block); + m_fBlocking = block; + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Return the SocketProvider which produced this socket. + * + * @return the SocketProvider + */ + protected SSLSocketProvider getSocketProvider() + { + return m_providerSocket; + } + + + // ----- data members --------------------------------------------------- + + /** + * The SSLSocketProvider associated with this socket. + */ + protected final SSLSocketProvider m_providerSocket; + + /** + * A cached copy of the configured blocking mode. + */ + protected boolean m_fBlocking; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSocket.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSocket.java new file mode 100644 index 0000000000000..c01e21270ebba --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSocket.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.internal.net.ssl; + + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.base.Timeout; +import com.oracle.coherence.common.internal.net.WrapperSocket; +import com.oracle.coherence.common.net.SSLSocketProvider; + +import java.io.IOException; + +import java.net.SocketAddress; +import java.net.Socket; +import java.net.InetAddress; +import java.net.SocketTimeoutException; + + +/** +* Wrapper socket implementation that performs hostname verification during +* connect. +* +* @author jh 2010.04.27 +*/ +public class SSLSocket + extends WrapperSocket + { + // ----- constructor ---------------------------------------------------- + + /** + * Create a new SSLSocket that delegates all operations to the given + * socket. + * + * @param socket the delegate socket + * @param provider the SSLSocketProvider that created this SSLSocket + * + * @throws IOException if an I/O error occurs + */ + public SSLSocket(Socket socket, SSLSocketProvider provider) + throws IOException + { + super(socket); + if (provider == null) + { + throw new IllegalArgumentException(); + } + m_provider = provider; + if (socket.isConnected()) + { + m_delegate = wrapSocket(socket, false); + } + } + + + // ----- Socket methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public synchronized void connect(SocketAddress addr, int cMillis) + throws IOException + { + Socket delegate = m_delegate; + int cMillisRestore = delegate.getSoTimeout(); + try (Timeout t = Timeout.after(cMillis == 0 ? Long.MAX_VALUE : cMillis)) + { + Blocking.connect(delegate, addr); + + // include SSL Handshake in the connect timeout by setting SO_TIMEOUT + long cMillisRemaining = Timeout.remainingTimeoutMillis(); + delegate.setSoTimeout(cMillisRemaining >= Integer.MAX_VALUE ? 0 : (int) cMillisRemaining); + m_delegate = wrapSocket(delegate, true); + } + catch (InterruptedException e) + { + throw new SocketTimeoutException(e.getMessage()); + } + finally + { + if (!delegate.isClosed()) + { + try + { + delegate.setSoTimeout(cMillisRestore); + } + catch (IOException e) {} + } + } + } + + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return "SSLSocket{" + m_delegate + "}"; + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Wrap the supplied plain Socket as an SSLSocket. The supplied socket must + * be connected. + * + * @param delegate the socket to delegate to + * @param fClientMode if the ssl socket should be in client or server mode + * + * @return the corresponding SSLSocket + * + * @throws IOException if an I/O error occurs + */ + public javax.net.ssl.SSLSocket wrapSocket(Socket delegate, boolean fClientMode) + throws IOException + { + try + { + SSLSocketProvider provider = m_provider; + SSLSocketProvider.Dependencies deps = provider.getDependencies(); + InetAddress addrConnected = delegate.getInetAddress(); + + javax.net.ssl.SSLSocket sslDelegate = (javax.net.ssl.SSLSocket) + deps.getSSLContext().getSocketFactory() + .createSocket(delegate, addrConnected.getHostName(), + delegate.getPort(), true); + + sslDelegate.setUseClientMode(fClientMode); + if (!fClientMode) + { + sslDelegate.setNeedClientAuth( deps.isClientAuthenticationRequired()); + } + + String[] asCiphers = deps.getEnabledCipherSuites(); + if (asCiphers != null) + { + sslDelegate.setEnabledCipherSuites(asCiphers); + } + + String[] asProtocols = deps.getEnabledProtocolVersions(); + if (asProtocols != null) + { + sslDelegate.setEnabledProtocols(asProtocols); + } + + provider.ensureSessionValidity(sslDelegate.getSession(), delegate); + + return sslDelegate; + } + catch (Exception e) + { + try + { + delegate.close(); + } + catch (IOException ee) + { + // ignore + } + if (e instanceof IOException) + { + throw (IOException) e; + } + throw new RuntimeException(e); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The SSLSocketProvider that created this SSLSocket. + */ + protected final SSLSocketProvider m_provider; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSocketChannel.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSocketChannel.java new file mode 100644 index 0000000000000..4b16862a99f3e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/net/ssl/SSLSocketChannel.java @@ -0,0 +1,1628 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.internal.net.ssl; + + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.base.Timeout; +import com.oracle.coherence.common.internal.net.WrapperSocket; +import com.oracle.coherence.common.internal.net.WrapperSelector; +import com.oracle.coherence.common.internal.net.WrapperSocketChannel; + +import com.oracle.coherence.common.internal.util.Timer; + +import com.oracle.coherence.common.net.SSLSocketProvider; + +import java.io.IOException; +import java.io.InterruptedIOException; + +import java.net.Socket; + +import java.nio.ByteBuffer; + +import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.GatheringByteChannel; +import java.nio.channels.IllegalBlockingModeException; +import java.nio.channels.ScatteringByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.nio.channels.NotYetConnectedException; + +import java.util.concurrent.RejectedExecutionException; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; + + +/** +* SSLSocketChannel layers SSL security onto an existing un-secured Socket. +* +* SSLSocketChannel does not currently support blocking channels, it must be +* configured and used in non-blocking mode only. +* +* @author mf 2010.04.27 +* @since Coherence 3.6 +*/ +public class SSLSocketChannel + extends WrapperSocketChannel + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an SSLSocketChannel which layers SSL protection onto the + * provided channel. + * + * @param channel the channel to layer SSL on + * @param provider the provider associated with the SSLSocketChannel + * + * @throws IOException if an I/O error occurs + */ + public SSLSocketChannel(SocketChannel channel, SSLSocketProvider provider) + throws IOException + { + super(channel, new SSLSelectorProvider(channel.provider())); + + f_providerSocket = provider; + + SSLEngine engine = openSSLEngine(); + + boolean fConnected = channel.isConnected(); + engine.setUseClientMode(!fConnected); + + f_engine = engine; + m_fBlocking = channel.isBlocking(); + + int cbPacket = engine.getSession().getPacketBufferSize(); + + ByteBuffer buffOut = f_buffEncOut = ByteBuffer.allocate(cbPacket); + buffOut.flip(); + + ByteBuffer buffIn = f_buffEncIn = ByteBuffer.allocate(cbPacket); + buffIn.flip(); + + ByteBuffer buffClear = f_buffClearIn = ByteBuffer.allocate( + engine.getSession().getApplicationBufferSize()); + buffClear.flip(); + f_aBuffClear[0] = buffClear; + } + + + // ----- WrapperSocketChannel methods ----------------------------------- + + /** + * {@inheritDoc} + */ + protected Socket wrapSocket(Socket socket) + { + return new WrapperSocket(socket) + { + public void shutdownInput() + throws IOException + { + // For SSL we cannot have a half-open comm channel, i.e. + // we must keep the underlying socket's input open, but + // that doesn't mean we can't pretend that it is closed and + // not emit more data from the socket to the application + synchronized (f_aBuffSingleInbound) // required for markKeysReadable + { + m_fInputShutdown = true; + markKeysReadable(/*fReadable*/ true); + } + } + + public void shutdownOutput() + throws IOException + { + // mimic what SSLSocket does; if we were to attempt to mimic + // what is done in shutdownInput here we would miss the side + // effect of our peer seeing that input was shutdown. In order + // for them to see that we'd have to completely close the socket + // which isn't what the user is asking for here + throw new UnsupportedOperationException( + "The method shutdownOutput() is not supported in SSLSocket"); + } + + public String toString() + { + return "SSLSocket(" + getLocalSocketAddress() + " " + getRemoteSocketAddress() + ", buffered{clear=" + f_buffClearIn.remaining() + + " encrypted=" + f_buffEncIn.remaining() + " out=" + + f_buffEncOut.remaining() + "}, handshake=" + + f_engine.getHandshakeStatus() + ", jobs=" + m_cJobsPending; + } + + public SocketChannel getChannel() + { + return SSLSocketChannel.this; + } + + public void close() + throws IOException + { + SSLSocketChannel.this.close(); + } + + public boolean isInputShutdown() + { + return m_fInputShutdown || super.isInputShutdown(); + } + + protected volatile boolean m_fInputShutdown; + }; + } + + // ----- ByteChannel methods -------------------------------------------- + + /** + * {@inheritDoc} + */ + public int read(ByteBuffer dst) + throws IOException + { + ByteBuffer[] aBuffRead = f_aBuffSingleInbound; + synchronized (aBuffRead) + { + try + { + aBuffRead[0] = dst; + return (int) read(aBuffRead, 0, 1); // we rely on EOS logic of read(ByteBuffer[]) which isn't in readInternal + } + finally + { + aBuffRead[0] = null; + } + } + } + + /** + * {@inheritDoc} + */ + public int write(ByteBuffer src) + throws IOException + { + ByteBuffer[] aBuffWrite = f_aBuffSingleOutbound; + int cb; + synchronized (aBuffWrite) + { + try + { + aBuffWrite[0] = src; + cb = (int) writeInternal(aBuffWrite, 0, 1); + } + finally + { + aBuffWrite[0] = null; + } + } + + if (!runProtocol() && cb == 0) + { + throw new IOException("end of stream"); + } + return cb; + } + + + // ----- Scatter/Gather ByteChannel methods ----------------------------- + + /** + * {@inheritDoc} + */ + public long read(ByteBuffer[] dsts, int offset, int length) + throws IOException + { + if (f_buffEncOut.hasRemaining()) + { + synchronized (f_aBuffSingleOutbound) + { + writeEncrypted(); + } + } + + long cb; + if (socket().isInputShutdown()) + { + onEndOfStream(); + if (socket().isClosed()) + { + throw new ClosedChannelException(); + } + cb = -1; + } + else + { + synchronized (f_aBuffSingleInbound) + { + cb = readInternal(dsts, offset, length); + } + } + + runProtocol(); // no need to check return value here, if we've hit EOS it will eventually be reflected by returning -1 here + return cb; + } + + /** + * {@inheritDoc} + */ + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException + { + long cb; + synchronized (f_aBuffSingleOutbound) + { + cb = writeInternal(srcs, offset, length); + } + + if (!runProtocol() && cb == 0) + { + throw new IOException("end of stream"); + } + return cb; + } + + + // ----- AbstractSelectableChannel methods ------------------------------ + + /** + * {@inheritDoc} + */ + protected void implCloseSelectableChannel() + throws IOException + { + synchronized (f_aBuffSingleInbound) + { + synchronized (f_aBuffSingleOutbound) + { + // if we are currently interrupted we need to temporarily + // disable the interrupt so that we can properly close the + // SSL connection, else SSLExceptions will result on our + // peer + boolean fInterrupted = Blocking.interrupted(); + //noinspection unused + try (Timeout t = Timeout.override(10000)) + { + for (SSLSelectionKey key = m_keyFirst; key != null; + key = key.m_keyNext) + { + key.setDataReadyOps(0); + key.setProtocolReadyOps(0); + } + + try + { + // Note: we intentionally do not shutdownInput as this is + // disallowed by SSL unless it has been prompted by the + // other side closing its output. That case is handled + // in readInternal + closeOutbound(true); + } + finally + { + f_delegate.close(); + } + } + catch (InterruptedException e) + { + fInterrupted = true; + } + + if (fInterrupted) + { + throw new InterruptedIOException(); + } + } + } + } + + /** + * {@inheritDoc} + */ + protected void implConfigureBlocking(boolean block) + throws IOException + { + if (block) + { + throw new IllegalBlockingModeException(); + } + super.implConfigureBlocking(block); + m_fBlocking = block; + } + + + + // ----- WrapperSelectableChannel methods ------------------------------- + + /** + * {@inheritDoc} + */ + public WrapperSelector.WrapperSelectionKey registerInternal(WrapperSelector selector, + int ops, Object att) + throws IOException + { + SSLSelectionKey key = new SSLSelectionKey(selector, + f_delegate.register(selector.getDelegate(), ops), att); + synchronized (f_aBuffSingleInbound) + { + key.m_keyNext = m_keyFirst; + m_keyFirst = key; + + if (f_buffClearIn.hasRemaining()) + { + markKeysReadable(true); + } + } + + runProtocol(); // no need to check for EOS here, we'll eventually hit it on read or write + return key; + } + + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return "SSLSocketChannel(" + f_socket + ")"; + } + + + // ----- helper methods ------------------------------------------------- + + /** + * Reads a sequence of bytes from this channel into a subsequence of the given buffers per the general + * contract of {@link ScatteringByteChannel#read(ByteBuffer[], int, int)}. + * + * The caller must hold the read monitor. + * + * @param aBuffDst The buffers into which bytes are to be transferred + * @param ofBuff The offset within the buffer array of the first buffer into which bytes are to be transferred; + * must be non-negative and no larger than aBuffDst.length + * @param cBuff The maximum number of buffers to be accessed; must be non-negative and no larger + * than aBuffDst.length - ofBuff + * + * @return The number of bytes read, possibly zero, or -1 if the channel has reached end-of-stream + * + * @throws IOException if an I/O error occurs + */ + protected long readInternal(ByteBuffer[] aBuffDst, int ofBuff, int cBuff) + throws IOException + { + if (m_fBlocking) + { + throw new IllegalBlockingModeException(); + } + + long cbFill = 0; + int cbRead = 0; + ByteBuffer buffEnd = aBuffDst[ofBuff + cBuff - 1]; + + for (int ofEnd = ofBuff + cBuff; ofBuff < ofEnd; + ofBuff += advance(aBuffDst, ofBuff, ofEnd)) + { + cbFill += drainClearBuffer(aBuffDst, ofBuff, ofEnd); + + if (buffEnd.hasRemaining()) + { + cbFill += decrypt(aBuffDst, ofBuff, ofEnd); + + if (buffEnd.hasRemaining()) + { + fillClearBuffer(); + + // read more encrypted data + cbRead = readEncrypted(); + if (cbRead <= 0) + { + // no new data coming in; copy over whatever we can + cbFill += drainClearBuffer(aBuffDst, ofBuff, ofEnd); + break; + } + } + } + } + + boolean fMore = f_buffClearIn.hasRemaining() || fillClearBuffer() > 0; + markKeysReadable(fMore); + + if (cbFill == 0 && cbRead == -1 && !fMore) + { + return -1; + } + else + { + return cbFill; + } + } + + + /** + * Writes a sequence of bytes to this channel from a subsequence of the given buffers per the general + * contract of {@link GatheringByteChannel#write(ByteBuffer[], int, int)}. + * + * The caller must hold the write monitor. + * + * @param aBuffSrc The buffers from which bytes are to be retrieved + * @param ofBuff The offset within the buffer array of the first buffer from which bytes are to be retrieved; + * must be non-negative and no larger than aBuffSrc.length + * @param cBuff The maximum number of buffers to be accessed; must be non-negative and no larger + * than aBuffSrc.length - ofBuff + * + * @return The number of bytes written, possibly zero + * + * @throws IOException if an I/O error occurs + */ + protected long writeInternal(ByteBuffer[] aBuffSrc, int ofBuff, int cBuff) + throws IOException + { + if (m_fBlocking) + { + throw new IllegalBlockingModeException(); + } + else if (f_buffEncOut.hasRemaining()) + { + // attempt to write any pending data, this is only done + // to ensure that if we'd swallowed an IOException (broken pipe) + // on a prior pass we definitely won't swallow it on this one + writeEncrypted(); + } + + long cbTake = 0; + try + { + for (int ofEnd = ofBuff + cBuff; ofBuff < ofEnd; + ofBuff += advance(aBuffSrc, ofBuff, ofEnd)) + { + cbTake += encrypt(aBuffSrc, ofBuff, ofEnd); + + // send what we have + if (writeEncrypted() == 0) + { + break; + } + } + } + catch (IOException e) + { + if (cbTake == 0) + { + throw e; + } + // else; we need to return cbTake exception will occur on next write attempt + } + + if (f_buffEncOut.hasRemaining()) + { + delayProtocol( + /*nInterest*/ SelectionKey.OP_WRITE, + /*nExclude*/ 0, + /*nReady*/ SelectionKey.OP_READ); + } + + return cbTake; + } + + /** + * Encrypt the supplied contents, storing them in the outgoing buffer. + * + * The caller must hold the write monitor. + * + * @param aBuffSrc an array of ByteBuffers to encrypt + * @param ofBuff the first buffer to encrypt + * @param ofEnd the end of the buffer range, not-inclusive + * + * @return the number of bytes consumed + * + * @throws IOException if an I/O error occurs + */ + protected int encrypt(ByteBuffer[] aBuffSrc, int ofBuff, int ofEnd) + throws IOException + { + ByteBuffer buffDst = f_buffEncOut; + int ofPos = buffDst.position(); + int ofLimit = buffDst.limit(); + int cbAdded = 0; + + try + { + // position the buffer for additional writes + if (ofPos == ofLimit) + { + buffDst.clear(); + ofPos = ofLimit = 0; + } + else + { + buffDst.limit(buffDst.capacity()) + .position(ofLimit); + + // Note: we could choose to compact the buffer if we are close + // to the end, but that is not the point of this buffer. This + // buffer is simply to ensure that we have space to write + // encrypted data to, it is not meant to buffer the network. + // It is the job of the delegate socket to do the network + // buffering. While we could do it here as well it would add + // unnecessary and potentially expensive array copies. + } + + if (m_cJobsPending == 0) + { + SSLEngineResult result; + + try + { + result = f_engine.wrap(aBuffSrc, ofBuff, ofEnd - ofBuff, buffDst); + } + catch (RuntimeException e) + { + // Bug 23071870: unwrap can throw RuntimeException if other side is not SSL (maybe wrap does to) + throw new SSLException(e); + } + + cbAdded = result.bytesProduced(); + if (cbAdded == 0 && result.getStatus() == SSLEngineResult.Status.CLOSED) + { + // if the engine has been closed then we can't produce any more data to put + // on the wire, and if we don't try to put something on the wire then the + // caller would never get to see an IOException indicating a closed socket + // so we must generate it ourselves + throw new IOException("connection closed"); + } + + return result.bytesConsumed(); + } + else + { + // Defend against JDK 1.5 bug (Sun BugId: 6492872) that + // causes a deadlock if the engine.wrap call is concurrent + // with the handshake task. + // being non-blocking -- pretend we've run out of buffer space. + return 0; + } + } + finally + { + // restore buff positions to reference encrypted segment + buffDst.position(ofPos) + .limit(ofLimit + cbAdded); + } + } + + /** + * Decrypt from the incomming network buffer into the supplied buffers. + * + * The caller must hold the read monitor. + * + * @param aBuffDst the destination buffers + * @param ofBuff the first buffer to decrypt into + * @param ofEnd the end of the buffer range, not-inclusive + * + * @return the number of bytes produced + * + * @throws IOException if an I/O error occurs + */ + protected int decrypt(ByteBuffer[] aBuffDst, int ofBuff, int ofEnd) + throws IOException + { + ByteBuffer buffSrc = f_buffEncIn; + SSLEngineResult result; + + try + { + result = f_engine.unwrap(buffSrc, aBuffDst, ofBuff, ofEnd - ofBuff); + } + catch (RuntimeException e) + { + // Bug 23071870: unwrap can throw RuntimeException if other side is not SSL + throw new SSLException(e); + } + + int cb = result.bytesProduced(); + if (cb == 0 && + result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW && + buffSrc.limit() == buffSrc.capacity()) + { + // only compact the encrypted buffer if it is preventing us from + // providing the app with data; this avoid the typical pattern + // of constant compaction which is expensive + buffSrc.compact().flip(); + } + return cb; + } + + + /** + * Called to terminate or accept termination of the the connection. + * + * If the caller passes true to this method, it is the caller's responsibility + * to wrap this call within a {@link Timer} block. + * + * @param fBlocking true if this call is guarded by a {@link Timeout} and should block, if necessary, + * in order to flush {@link #f_buffEncOut}. + * + * @throws InterruptedIOException if fBlocking is true and + * {@link #f_buffEncOut} could not be written within + * the caller's time limit + */ + protected void closeOutbound(boolean fBlocking) throws InterruptedIOException + { + f_engine.closeOutbound(); + + ByteBuffer buffOut = f_buffEncOut; + if (buffOut.hasRemaining()) + { + try + { + ByteBuffer[] aBuffIn = s_aBuffEmpty; + if (fBlocking) + { + Selector selector = null; + try + { + while (buffOut.hasRemaining()) + { + if (writeInternal(aBuffIn, 0, 1) == 0) + { + if (selector == null) + { + SocketChannel delegate = f_delegate; + selector = delegate.provider().openSelector(); + delegate.register(selector, SelectionKey.OP_WRITE); + } + Blocking.select(selector); + if (Blocking.interrupted()) + { + // select was interrupted by guarding Timer + throw new InterruptedIOException(); + } + } + } + } + finally + { + if (selector != null) + { + selector.close(); + } + } + } + else + { + // write as much as possible until there is no progress + for (int cb = buffOut.remaining(), cbLast = -1; + cb != cbLast; + cbLast = cb, cb = buffOut.remaining()) + { + writeInternal(aBuffIn, 0, 1); + } + } + } + catch (InterruptedIOException e) + { + throw e; + } + catch (NotYetConnectedException | IllegalBlockingModeException | IOException e) {} + } + } + + /** + * Called to indicate that the inbound stream will emit no further data. + */ + protected void onEndOfStream() + { + synchronized (f_aBuffSingleInbound) + { + synchronized (f_aBuffSingleOutbound) + { + if (f_engine.getSession().isValid()) + { + try + { + f_engine.closeInbound(); + closeOutbound(false); + } + catch (SSLException e) + { + // mimicking SSLSocket + } + catch (IOException ignored) + { + // won't occur + } + } + } + } + } + + /** + * Run the next stage of the SSL handshake protocol if any. + * + * Depending on the handshake status this method may attempt to obtain + * either the send or receive monitor, and thus it is important that + * callers either hold both or neither. + * + * This method may safely be called at any point, though in general it can + * be avoided until it is determined that no other data is moving through + * the channel, i.e. read or write return zero. + * + * @throws IOException if an I/O error occurs + * + * @return true iff the socket is still usable + */ + protected boolean runProtocol() + throws IOException + { + SSLEngine engine = f_engine; + ByteBuffer[] aBuffEmpty = s_aBuffEmpty; + final Object oRead = f_aBuffSingleInbound; + final Object oWrite = f_aBuffSingleOutbound; + + while (true) + { + switch (engine.getHandshakeStatus()) + { + case NEED_TASK: + m_fHandshaking = true; + final Runnable runnable = engine.getDelegatedTask(); + if (runnable == null) + { + synchronized (oWrite) + { + if (m_cJobsPending > 0) + { + // we are waiting on jobs to finish, ignore + // the socket state until the job(s) complete + boolean fWrite = f_buffEncOut.hasRemaining(); + delayProtocol( + /*nInterest*/ fWrite ? SelectionKey.OP_WRITE : 0, + /*nExclude*/ fWrite ? 0 : SelectionKey.OP_WRITE, + /*nReady*/ SelectionKey.OP_READ | SelectionKey.OP_WRITE); + } + } + return true; + } + else + { + Runnable task = new Runnable() + { + public void run() + { + try + { + runnable.run(); + } + finally + { + synchronized (oWrite) + { + --m_cJobsPending; + continueProtocol(); + } + } + } + }; + + synchronized (oWrite) + { + ++m_cJobsPending; + } + + try + { + getSocketProvider().getDependencies().getExecutor() + .execute(task); + task = null; + } + catch (RejectedExecutionException e) {} + finally + { + if (task != null) + { + task.run(); + } + } + } + break; + + case NEED_WRAP: + m_fHandshaking = true; + synchronized (oWrite) + { + int cbEncrypted = encrypt(aBuffEmpty, 0, 0); + int cbWritten = writeEncrypted(); + // COH-18648: + // Check to see if we encrypted or wrote anything. We care + // about this case as in TLS 1.3, half-closes are supported which means + // the inbound channel can be closed, we can, however, still technically write. + // If we didn't encrypt or write anything, then treat this situation + // the same as if there was still encrypted data. If the engine signals + // that it still needs to wrap, then return to release any locks and allow + // the channel state to stabilize. + // + if (f_buffEncOut.hasRemaining() || + /* see comment COH-18648 */ (cbEncrypted == 0 && cbWritten == 0)) + { + delayProtocol( + /*nInterest*/ SelectionKey.OP_WRITE, + /*nExclude*/ 0, + /*nReady*/ SelectionKey.OP_READ); + + if (engine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP) + { + return true; + } + // else; all engine data encrypted but still not written; continue with next phase in "parallel" + } + } + break; + + case NEED_UNWRAP: + m_fHandshaking = true; + + int cbRead; + int cbFill; + synchronized (oRead) + { + synchronized (oWrite) + { + cbRead = readEncrypted(); + cbFill = fillClearBuffer(); + + if (cbRead == 0 && cbFill == 0) + { + boolean fWrite = f_buffEncOut.hasRemaining(); + delayProtocol( + /*nInterest*/ SelectionKey.OP_READ | (fWrite ? SelectionKey.OP_WRITE : 0), + /*nExclude*/ fWrite ? 0 : SelectionKey.OP_WRITE, + /*nReady*/ SelectionKey.OP_WRITE); + return true; + } + } + } + + if (cbRead == -1) + { + // must occur outside of synchronization + onEndOfStream(); + return false; + } + break; + + case NOT_HANDSHAKING: + case FINISHED: + if (m_fHandshaking) // optimistic read + { + synchronized (oWrite) + { + endProtocol(); + m_fHandshaking = false; + } + } + return true; + } + } + } + + /** + * Delay the protocol waiting to do the specified operation. + * + * The caller must hold the write monitor. + * + * @param nInterest the protocol's interest ops + * @param nExclude the operations to forcefully exclude + * @param nReady the protocol's ready op + */ + protected void delayProtocol(int nInterest, int nExclude, int nReady) + { + try + { + for (SSLSelectionKey key = m_keyFirst; key != null; + key = key.m_keyNext) + { + key.setProtocolReadyOps(nReady); + key.interestProtocol(nInterest, nExclude); + } + } + catch (CancelledKeyException e) {} + } + + /** + * Continue the protocol by waking up the selector. + * + * The caller must hold the write monitor. + */ + protected void continueProtocol() + { + for (SSLSelectionKey key = m_keyFirst; key != null; + key = key.m_keyNext) + { + try + { + key.setProtocolReadyOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); + key.interestProtocol(SelectionKey.OP_READ | SelectionKey.OP_WRITE, 0); + + // wakeup the selector so we can continue to run the protocol + key.selector().wakeup(); + } + catch (CancelledKeyException e) {} + } + } + + /** + * Update the SelectionKeys once the handshake protocol has completed. + * + * The caller must hold the write monitor. + * + * @throws IOException if an I/O error occurs + */ + protected void endProtocol() + throws IOException + { + try + { + boolean fWrite = f_buffEncOut.hasRemaining(); + + for (SSLSelectionKey key = m_keyFirst; key != null; + key = key.m_keyNext) + { + key.setProtocolReadyOps(fWrite ? SelectionKey.OP_READ : 0); + key.interestProtocol(fWrite ? SelectionKey.OP_WRITE : 0, 0); + } + } + catch (CancelledKeyException e) {} + + if (!m_fValidated && f_engine.getSession().isValid()) + { + validatePeer(); + m_fValidated = true; + } + } + + /** + * Vaidate that the connected peer acceptible. + * + * @throws SSLException if the peer is not acceptible + */ + protected void validatePeer() + throws SSLException + { + SSLSocketProvider provider = getSocketProvider(); + SSLSession session = f_engine.getSession(); + Socket socket = f_delegate.socket(); + + try + { + provider.ensureSessionValidity(session, socket); + } + catch (SSLException e) + { + try + { + f_delegate.close(); + } + catch (IOException eIEIO) + {} + + throw e; + } + } + + /** + * Drain as much of the clear-text buffer into the supplied buffers + * + * The caller must hold the read monitor. + * + * @param aBuffDst the destination buffers + * @param ofBuff the first buffer to fill into + * @param ofEnd the end of the buffer range, not-inclusive + * + * @return the number of bytes produced + * + * @throws IOException if an I/O error occurs + */ + protected int drainClearBuffer(ByteBuffer[] aBuffDst, int ofBuff, int ofEnd) + throws IOException + { + ByteBuffer buffSrc = f_buffClearIn; + byte[] abSrc = buffSrc.array(); + int ofSrc = buffSrc.arrayOffset() + buffSrc.position(); + int cbSrc = buffSrc.remaining(); + int cbAdded = 0; + + for (; ofBuff < ofEnd && cbSrc > 0; ++ofBuff) + { + ByteBuffer buffDst = aBuffDst[ofBuff]; + + int cb = Math.min(buffDst.remaining(), cbSrc); + if (cb > 0) + { + buffDst.put(abSrc, ofSrc, cb); + ofSrc += cb; + cbAdded += cb; + cbSrc -= cb; + } + } + buffSrc.position(buffSrc.position() + cbAdded); + return cbAdded; + } + + /** + * Fill the clear-text buffer by decrypting any buffered encrypted data. + * + * The caller must hold the read monitor. + * + * @return the number of encrypted bytes which were consumed + * + * @throws IOException if an I/O error occurs + */ + protected int fillClearBuffer() + throws IOException + { + ByteBuffer buffEnc = f_buffEncIn; + ByteBuffer buffClear = f_buffClearIn; + int ofPos = buffClear.position(); + int ofLim = buffClear.limit(); + int cb = 0; + int cbStart = buffEnc.remaining(); + + try + { + if (ofPos == ofLim) + { + buffClear.clear(); + ofPos = ofLim = 0; + } + else + { + buffClear.position(buffClear.limit()) + .limit(buffClear.capacity()); + } + cb = decrypt(f_aBuffClear, 0, 1); + return cbStart - buffEnc.remaining(); + } + finally + { + buffClear.position(ofPos) + .limit(ofLim + cb); + } + } + + /** + * Attempt to write the contents the outbound buffer to the network. + * + * The caller must hold the write monitor. + * + * @return the number of bytes written + * + * @throws IOException if an I/O error occurs + */ + protected int writeEncrypted() + throws IOException + { + int cb = f_delegate.write(f_buffEncOut); + + if (!f_buffEncOut.hasRemaining()) + { + endProtocol(); + } + return cb; + } + + /** + * Attempt to read from the network into the inbound encrypted buffer. + * + * The caller must hold the read monitor. + * + * @return the number of bytes read + * + * @throws IOException if an I/O error occurs + */ + protected int readEncrypted() + throws IOException + { + ByteBuffer buff = f_buffEncIn; + int ofPos = buff.position(); + int ofLimit = buff.limit(); + int cbRead = 0; + + try + { + // prep the buffer for additional writes + if (ofLimit == ofPos) + { + buff.clear(); + ofLimit = ofPos = 0; + } + else + { + buff.position(buff.limit()) + .limit(buff.capacity()); + } + + return cbRead = f_delegate.read(buff); + } + finally + { + // restore the position and limit to reference just the encrypted + // segment + buff.position(ofPos) + .limit(ofLimit + (cbRead < 0 ? 0 : cbRead)); + } + } + + /** + * Identify the distance to the next buffer which has available space. + * + * @param aBuff the array of buffers to scan + * @param ofBuff the starting position + * @param ofEnd the end of the buffer range, not-inclusive + * + * @return the difference between ofBuff and the first available buffer + */ + protected int advance(ByteBuffer[] aBuff, int ofBuff, int ofEnd) + { + int c = 0; + while (ofBuff < ofEnd && !aBuff[ofBuff].hasRemaining()) + { + ++ofBuff; + ++c; + } + return c; + } + + /** + * Prepare the registered keys for selection. + * + * The caller must hold the read monitor. + * + * @param fReadable true if buffered inbound data exists + */ + protected void markKeysReadable(boolean fReadable) + { + for (SSLSelectionKey key = m_keyFirst; key != null; + key = key.m_keyNext) + { + if (fReadable) + { + // mark the channel as read ready + key.setDataReadyOps(SelectionKey.OP_READ); + // add interest in OP_WRITE to help avoid getting blocked in selector + key.interestData(SelectionKey.OP_WRITE); + } + else // no data buffered + { + // unmark the channel as read ready + key.setDataReadyOps(0); + // remove artificial OP_WRITE interest + key.interestData(0); + } + } + } + + + + // ----- helpers -------------------------------------------------------- + + /** + * Return the SocketProvider which produced this socket. + * + * @return the SocketProvider + */ + protected SSLSocketProvider getSocketProvider() + { + return f_providerSocket; + } + + /** + * Create and return a new SSLEngine. + * + * @return the SSLEngine + */ + public SSLEngine openSSLEngine() + { + SSLSocketProvider.Dependencies deps = getSocketProvider().getDependencies(); + SSLEngine engine = deps.getSSLContext().createSSLEngine(); + String[] asCiphers = deps.getEnabledCipherSuites(); + String[] asProtocols = deps.getEnabledProtocolVersions(); + if (asCiphers != null) + { + engine.setEnabledCipherSuites(asCiphers); + } + + if (asProtocols != null) + { + engine.setEnabledProtocols(asProtocols); + } + + engine.setNeedClientAuth(deps.isClientAuthenticationRequired()); + + return engine; + } + + + // ------ inner class: SSLSelectionKey ---------------------------------- + + /** + * An SSL aware SelectionKey. + */ + public class SSLSelectionKey + extends WrapperSelector.WrapperSelectionKey + { + // ----- constructor -------------------------------------------- + + protected SSLSelectionKey(WrapperSelector selector, SelectionKey key, Object att) + { + super (selector, key, att); + m_nOpsInterestApp = key == null ? 0 : key.interestOps(); + } + + // ----- SSLSelectionKey methods -------------------------------- + + /** + * Set SelectionKey ops which apply to the SSLSelectableChannel but + * not necessarily to the delegate channel. + * + * @param nOps the SelectionKey ops + */ + protected void setDataReadyOps(int nOps) + { + m_nOpsReadyData = nOps; + } + + /** + * Return the SelectionKey ops which apply to the SSLSelectableChannel + * but not necessarily to the delegate channel. + * + * @return the SelectionKey ops + */ + protected int getDataReadyOps() + { + return m_nOpsReadyData; + } + + /** + * Specify the operations of interest to the SSL data layer. + * + * @param nOps operations bit set + * + * @return the operations of interest to the SSL data layer. + */ + protected synchronized SSLSelectionKey interestData(int nOps) + { + if (nOps != m_nOpsInterestData) + { + int nOpsApp = m_nOpsInterestApp; + if (nOpsApp != 0) + { + ((SSLSelector) selector()).setInterestOps(m_delegate, (nOps | nOpsApp + | m_nOpsInterestProtocol) & ~m_nOpsInterestExclude); + } + // else; application isn't interested in forward progress now; don't force it + + m_nOpsInterestData = nOps; + } + return this; + } + + /** + * Return the interest operations for the SSL data layer + * + * @return the interest operations for the SSL data layer + */ + protected int interestData() + { + return m_nOpsInterestData; + } + + /** + * Specify the SelectionKey operations which are ready on the + * SSLSelectableChannel protocol but not necessarily to the delegate + * channel. + * + * @param nOps the SelectionKey ops + */ + protected void setProtocolReadyOps(int nOps) + { + m_nOpsReadyProtocol = nOps; + } + + /** + * Return the SelectionKey operations which are ready on the + * SSLSelectableChannel protocol but not necessarily to the delegate + * channel. + * + * @return the SelectionKey ops + */ + protected int getProtocolReadyOps() + { + return m_nOpsReadyProtocol; + } + + /** + * The operations of interest to the SSL protocol layer. + * + * @param nOps operations bit set + * @param nExclusions the operations to exclude from selection + * + * @return the operations of interest to the SSL protocol layer + */ + protected synchronized SSLSelectionKey interestProtocol(int nOps, int nExclusions) + { + if (!(nOps == m_nOpsInterestProtocol && nExclusions == m_nOpsInterestExclude)) + { + int nOpsApp = m_nOpsInterestApp; + if (nOpsApp != 0) + { + ((SSLSelector) selector()).setInterestOps(m_delegate, (nOps | nOpsApp + | m_nOpsInterestData) & ~nExclusions); + } + // else; application isn't interested in forward progress now; don't force it + + m_nOpsInterestProtocol = nOps; + m_nOpsInterestExclude = nExclusions; + } + return this; + } + + /** + * Get the interest operations for the SSL protocol layer + * + * @return the interest operations for the SSL protocol layer + */ + protected int interestProtocol() + { + return m_nOpsInterestProtocol; + } + + + // ----- SelectionKey methods ----------------------------------- + + /** + * {@inheritDoc} + */ + public SelectableChannel channel() + { + return SSLSocketChannel.this; + } + + /** + * {@inheritDoc} + */ + public void cancel() + { + super.cancel(); + + // remove this key from the linked-list of keys + // while this has a cost of O(N), it is rare for N > 1, and also rare + // a very rare event + synchronized (f_aBuffSingleInbound) + { + SSLSelectionKey keyPrev = null; + for (SSLSelectionKey keyCurr = m_keyFirst; + keyCurr != null; keyPrev = keyCurr, keyCurr = keyCurr.m_keyNext) + { + if (keyCurr == this) + { + if (keyPrev == null) + { + m_keyFirst = this.m_keyNext; + } + else + { + keyPrev.m_keyNext = this.m_keyNext; + } + return; + } + } + } + } + + /** + * {@inheritDoc} + */ + public int interestOps() + { + return m_nOpsInterestApp; + } + + /** + * {@inheritDoc} + */ + public synchronized SelectionKey interestOps(int ops) + { + m_delegate.interestOps(ops == 0 + ? 0 // if the app doesn't want progress, don't force it, TODO: consider respecting interstApp for intrestData/interestProtocol + : (ops | m_nOpsInterestData + | m_nOpsInterestProtocol) & ~m_nOpsInterestExclude); + m_nOpsInterestApp = ops; + return this; + } + + /** + * {@inheritDoc} + */ + public int readyOps() + { + int nReadyDelegate = m_delegate.readyOps(); + int nReadyApp = (nReadyDelegate | m_nOpsReadyData | m_nOpsReadyProtocol) & m_nOpsInterestApp; + + if (nReadyApp == 0 && (nReadyDelegate & (m_nOpsInterestData | m_nOpsInterestProtocol)) != 0) + { + // nothing the application is interested in is ready, but + // the protocol is trying to make progress, lie to the + // app to get them to trigger the protocol + // we may still be returning 0 in which case we're not + // lying but the application doesn't want to make progress + // so apparently we don't need to progress the protocol either + return m_nOpsInterestApp; + } + else + { + return nReadyApp; + } + } + + // ----- Object methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return "SSLSelectionKey(" + getKeyString(this) + ", delegate{" + + getKeyString(m_delegate) + "}, data{interest=" + + interestData() + " ready=" + getDataReadyOps() + + "}, protocol{interest=" + interestProtocol() + " ready=" + + getProtocolReadyOps() + "}, exclude=" + + m_nOpsInterestExclude + ", " + SSLSocketChannel.this + ")"; + } + + // ----- data members ------------------------------------------- + + /** + * The interest set as specified by the application + */ + protected int m_nOpsInterestApp; + + /** + * The operations which are ready in the SSL data layer. + */ + protected int m_nOpsReadyData; + + /** + * The interest set as specified by the SSL data layer. + */ + protected int m_nOpsInterestData; + + /** + * The operations which are ready in the SSL protocol layer. + */ + protected int m_nOpsInterestProtocol; + + /** + * The interest set as specified by the SSL protocol layer. + */ + protected int m_nOpsReadyProtocol; + + /** + * The interest operations to exclude. + */ + protected int m_nOpsInterestExclude; + + /** + * A link to the next SSLSelectionKey registered against the associated + * channel. + */ + protected SSLSelectionKey m_keyNext; + } + + + // ----- data members --------------------------------------------------- + + /** + * The SSLSocketProvider associated with this socket. + */ + protected final SSLSocketProvider f_providerSocket; + + /** + * The SSLEngine which provides SSL to this channel. + */ + protected final SSLEngine f_engine; + + /** + * A cached copy of the configured blocking mode. + */ + protected boolean m_fBlocking; + + /** + * Set to true once the connection has been validated. + */ + protected boolean m_fValidated; + + /** + * A reusable single element buffer array for reads, which also serves as + * the monitor to protect against multiple concurrent readers. + */ + protected final ByteBuffer[] f_aBuffSingleInbound = new ByteBuffer[1]; + + /** + * A reusable single element buffer array for writes, which also serves as + * the monitor to protect against multiple concurrent writers. + * + * If both the read and write monitor will be held then the read should be + * acquired before write. + */ + protected final ByteBuffer[] f_aBuffSingleOutbound = new ByteBuffer[1]; + + /** + * Buffered encrypted data waiting to be written to the delegate channel. + * The position and limit define the range of encrypted data. + */ + protected final ByteBuffer f_buffEncOut; + + /** + * Buffered encrypted data from the delegate channel, waiting to be + * decrypted. The position and limit define the range of encrypted data. + */ + protected final ByteBuffer f_buffEncIn; + + /** + * Buffered "clear text" data ready to be delivered to the user of this + * channel. The position and limit define the range of "clear text" data. + */ + protected final ByteBuffer f_buffClearIn; + + /** + * An ByteBuffer array containing just the clear text buffer + */ + protected final ByteBuffer[] f_aBuffClear = new ByteBuffer[1]; + + /** + * A linked list of registered SSLSelectionKeys, the linked list must be + * accessed while holding the read lock, i.e f_aBuffSingleInbound + */ + protected SSLSelectionKey m_keyFirst; + + /** + * Flag indicating if the channel is currently handshaking. + */ + protected volatile boolean m_fHandshaking; + + /** + * The number of pending jobs scheduled on behalf of the engine. This is + * only to be modified while holding the read and write locks. + */ + protected int m_cJobsPending; + + /** + * Cached array of remaining buffer byte counts. + */ + protected int[] m_acbBuff; + + /** + * Flag indicating that the first "clear text" byte should be skipped + * during the next encryption operation. + */ + protected boolean m_fSkip; + + /** + * The byte of "clear text" that should be skipped during the next + * encryption operation. + */ + protected byte m_bSkip; + + /** + * A shared empty byte buffer. + */ + protected static final ByteBuffer[] s_aBuffEmpty = {ByteBuffer.allocate(0)}; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/package.html new file mode 100644 index 0000000000000..6efa76a9e1b57 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/package.html @@ -0,0 +1,6 @@ + +The internal package contains classes which are generally not intended to be used directly, and whose APIs may expierence +a greater level of change then the remainder of the library. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/security/PeerX509TrustManager.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/security/PeerX509TrustManager.java new file mode 100644 index 0000000000000..42aa15872691e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/security/PeerX509TrustManager.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.security; + + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Principal; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + + +/** +* X509TrustManager implementation that requires the peer's certificate to be +* present in a configured key store. +* +* @author jh 2010.05.11 +*/ +public class PeerX509TrustManager + implements X509TrustManager + { + // ----- constructor ---------------------------------------------------- + + /** + * Create a new PeerTrustManager that requires the peer's certificate to + * be present in the given key store. + * + * @param keyStore the key store that contains the certificates of + * trusted peers + */ + public PeerX509TrustManager(KeyStore keyStore) + { + if (keyStore == null) + { + throw new IllegalArgumentException(); + } + m_keyStore = keyStore; + } + + + // ----- PeerTrustManager methods --------------------------------------- + + /** + * Determine if the leaf certificate in the given certificate chain is + * contained in the trusted peer key store. + * + * @param aCert the certificate chain + * @param sAuthType the authentication type + * + * @throws CertificateException if the certificate chain is not trusted + */ + public void checkPeerTrusted(X509Certificate[] aCert, String sAuthType) + throws CertificateException + { + if (aCert == null || aCert.length == 0) + { + throw new IllegalArgumentException("Missing required certificate chain"); + } + if (aCert == null || aCert.length == 0 || sAuthType == null || sAuthType.length() == 0) + { + throw new IllegalArgumentException("Missing required authentication type"); + } + + try + { + if (m_keyStore.getCertificateAlias(aCert[0]) == null) + { + throw new CertificateException("Untrusted peer: " + + getCommonName(aCert[0].getSubjectDN())); + } + } + catch (KeyStoreException e) + { + throw new CertificateException(e); + } + } + + + // ----- X509TrustManager interface ------------------------------------- + + /** + * Determine if the leaf certificate in the given certificate chain is + * contained in the trusted peer key store. + * + * @param aCert the certificate chain + * @param sAuthType the authentication type + * + * @throws CertificateException if the certificate chain is not trusted + */ + public void checkClientTrusted(X509Certificate[] aCert, String sAuthType) + throws CertificateException + { + checkPeerTrusted(aCert, sAuthType); + } + + /** + * Determine if the leaf certificate in the given certificate chain is + * contained in the trusted peer key store. + * + * @param aCert the certificate chain + * @param sAuthType the authentication type + * + * @throws CertificateException if the certificate chain is not trusted + */ + public void checkServerTrusted(X509Certificate[] aCert, String sAuthType) + throws CertificateException + { + checkPeerTrusted(aCert, sAuthType); + } + + /** + * Return an array of certificate authority certificates which are trusted + * for authenticating peers. Since this trust manager only checks the + * leaf certificate of supplied certification chains, this method always + * returns an empty array. + * + * @return the array of certificate authority certificates; always an + * empty array + */ + public X509Certificate[] getAcceptedIssuers() + { + return EMPTY_CERTS; + } + + + // ----- helper methods ------------------------------------------------- + + /** + * Return the common name of the given principal + * + * @param principal the principal + * + * @return the common name of the given principal or null if the principal + * doesn't have a common name + */ + protected String getCommonName(Principal principal) + { + String sCN = null; + String sDN = principal.getName(); + + int i = sDN.toUpperCase().indexOf(CN_PREFIX); + if (i != -1) + { + i += CN_PREFIX_LENGTH; + int j = sDN.indexOf(",", i); + if (j == -1) + { + j = sDN.length(); + } + sCN = sDN.substring(i, j); + } + + return sCN; + } + + + // ----- data members --------------------------------------------------- + + /** + * The key store used by this TrustManager. + */ + protected final KeyStore m_keyStore; + + + // ----- constants ------------------------------------------------------ + + /** + * The alogorithm used by this TrustManager. + */ + public static final String ALGORITHM = "PeerX509"; + + /** + * The prefix of a principal CN (common name) attribute. + */ + private static final String CN_PREFIX = "CN="; + + /** + * The length of a principal CN (common name) attribute prefix. + */ + private static final int CN_PREFIX_LENGTH = CN_PREFIX.length(); + + /** + * Empty array of CA certificates. + */ + private static final X509Certificate[] EMPTY_CERTS = new X509Certificate[0]; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/security/PeerX509TrustManagerFactory.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/security/PeerX509TrustManagerFactory.java new file mode 100644 index 0000000000000..20908db6a626f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/security/PeerX509TrustManagerFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.security; + +import java.security.InvalidAlgorithmParameterException; +import java.security.KeyStore; +import java.security.KeyStoreException; + +import javax.net.ssl.ManagerFactoryParameters; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactorySpi; + + +/** +* A factory for {@link PeerX509TrustManager} instances. +* +* @author jh 2010.05.11 +*/ +public class PeerX509TrustManagerFactory + extends TrustManagerFactorySpi + { + // ----- TrustManagerFactorySpi methods --------------------------------- + + /** + * Return one trust manager for each type of trust material. + * + * @return one trust manager for each type of trust material + */ + protected TrustManager[] engineGetTrustManagers() + { + return m_aTrustManager; + } + + /** + * Initialize this factory with a source of certificate authorities and + * related trust material. + * + * @param keyStore the trust material source + */ + protected void engineInit(KeyStore keyStore) + throws KeyStoreException + { + m_aTrustManager = new TrustManager[] {new PeerX509TrustManager(keyStore)}; + } + + /** + * Initialize this factory with a source of provider-specific key material. + * + * @param params provider-specific key material + */ + protected void engineInit(ManagerFactoryParameters params) + throws InvalidAlgorithmParameterException + { + throw new UnsupportedOperationException(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The TrustManager array returned by this TrustManagerFactory. + */ + private volatile TrustManager[] m_aTrustManager; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/security/SecurityProvider.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/security/SecurityProvider.java new file mode 100644 index 0000000000000..108c72752017a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/security/SecurityProvider.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.internal.security; + + +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.Provider; +import java.security.Security; + + +/** +* Security Provider implementation that returns custom security services. +* +* @author jh 2010.05.11 +*/ +public class SecurityProvider + extends Provider + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor. + */ + SecurityProvider() + { + super(NAME, 1.0, "Oracle Commons Security Provider"); + + putService(new Service(this, + "TrustManagerFactory", + PeerX509TrustManager.ALGORITHM, + PeerX509TrustManagerFactory.class.getName(), + null, + null)); + } + + + // ----- SecurityProvider methods --------------------------------------- + + /** + * Ensure that an instance of this provider has been registered with the + * system. + */ + public static void ensureRegistration() + { + try + { + synchronized (Security.class) + { + if (Security.getProvider(SecurityProvider.NAME) == null) + { + AccessController.doPrivileged( + new PrivilegedExceptionAction() + { + public Object run() throws Exception + { + Security.addProvider(new SecurityProvider()); + return null; + } + }); + } + } + } + catch (PrivilegedActionException e) + { + // should never happen + throw new RuntimeException(e.getException()); + } + } + + + // ----- constants ------------------------------------------------------ + + /** + * The name of this provider. + */ + public static final String NAME = "OracleCommonsSecurityProvider"; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/ApplicationLoader.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/ApplicationLoader.java new file mode 100644 index 0000000000000..c167ebcfa7f7c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/ApplicationLoader.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + + +/** + * Execute a java application is a sandboxed ClassLoader. + * + * @author mf 2011.07.05 + */ +public class ApplicationLoader + extends URLClassLoader + implements Runnable + { + /** + * Creates a new ApplicationLoader for the specified url. + * + * @param aUrl the url of the jar file + * @param asArgs the program arguments + */ + public ApplicationLoader(URL[] aUrl, String[] asArgs) + { + super(aUrl, SHARED_ROOT ? ClassLoader.getSystemClassLoader() : ClassLoader.getSystemClassLoader().getParent()); + f_asArgs = asArgs; + } + + /** + * Invokes the application in this jar file given the name of the + * main class and an array of arguments. The class must define a + * static method "main" which takes an array of String arguements + * and is of return type "void". + * + * @param name the name of the main class + * @param args the arguments for the application + * @exception ClassNotFoundException if the specified class could not + * be found + * @exception NoSuchMethodException if the specified class does not + * contain a "main" method + * @exception InvocationTargetException if the application raised an + * exception + */ + public void invokeClass(String name, String[] args) + throws ClassNotFoundException, + NoSuchMethodException, + InvocationTargetException + { + Class c = loadClass(name); + Method m = c.getMethod("main", new Class[] { args.getClass() }); + m.setAccessible(true); + int mods = m.getModifiers(); + if (m.getReturnType() != void.class || !Modifier.isStatic(mods) || + !Modifier.isPublic(mods)) { + throw new NoSuchMethodException("main"); + } + try { + m.invoke(null, new Object[] { args }); + } catch (IllegalAccessException e) { + // This should not happen, as we have disabled access checks + } + } + + public void run() + { + Thread.currentThread().setContextClassLoader(this); + + // Get the application's main class name + String name = f_asArgs[1]; + + // Get arguments for the application + String[] newArgs = new String[f_asArgs.length - 2]; + System.arraycopy(f_asArgs, 2, newArgs, 0, newArgs.length); + // Invoke application's main class + try + { + invokeClass(name, newArgs); + } catch (ClassNotFoundException e) { + fatal("Class not found: " + name); + } catch (NoSuchMethodException e) { + fatal("Class does not define a 'main' method: " + name); + } catch (InvocationTargetException e) { + e.getTargetException().printStackTrace(); + } + } + + + /** + * Split into multiple command lines based on & and ; delimiters + * + * @param args the line to split + * + * @return the split lines + */ + public static String[][] splitArgs(String[] args) + { + ArrayList listArgs = new ArrayList(); + + int ofStart = 0; + for (int i = 0, c = args.length; i < c; ++i) + { + if (args[i].contains("&") || args[i].contains(";")) + { + int ofAmp = args[i].indexOf('&'); + int ofSemi = args[i].indexOf(';'); + int ofTerm = ofAmp == -1 ? ofSemi : ofAmp; + String sSuffix = args[i].substring(ofTerm + 1); + + args[i] = args[i].substring(0, ofTerm + (ofAmp == -1 ? 0 : 1)); // leave &, but strip ; + + int cTok = args[i].isEmpty() ? 0 : 1; + + String[] newArgs = new String[i + cTok - ofStart]; + System.arraycopy(args, ofStart, newArgs, 0, i + cTok); + listArgs.add(newArgs); + + ofStart = i + 1; + + if (!sSuffix.isEmpty()) + { + int cReps = Integer.parseInt(sSuffix); + for (int j = 1; j < cReps; ++j) + { + listArgs.add(newArgs.clone()); + } + } + } + } + + if (ofStart < args.length) + { + String[] newArgs = new String[args.length - ofStart]; + System.arraycopy(args, ofStart, newArgs, 0, newArgs.length); + listArgs.add(newArgs); + } + + return listArgs.toArray(new String[listArgs.size()][]); + } + + public static void main(String[] args) + { + if (args.length < 2) + { + usage(); + } + + String[][] aaArgs = splitArgs(args); + + int cMain = 0; + for (final String[] asArgs : aaArgs) + { + List listJars = new ArrayList(); + try + { + StringTokenizer tok = new StringTokenizer(asArgs[0], ":"); + while (tok.hasMoreElements()) + { + listJars.add(new URL("file:" + tok.nextToken())); + } + } + catch (MalformedURLException e) + { + fatal("Invalid URL: " + asArgs[0]); + } + + String sLast = asArgs[asArgs.length - 1]; + if (sLast.endsWith("&")) + { + asArgs[asArgs.length - 1] = sLast.substring(0, sLast.length() -1); + new Thread(new ApplicationLoader(listJars.toArray(new URL[0]), asArgs), "main-" + ++cMain).start(); + } + else + { + new ApplicationLoader(listJars.toArray(new URL[0]), asArgs).run(); + } + } + } + + private static void fatal(String s) + { + System.err.println(s); + System.exit(1); + } + + private static void usage() + { + fatal("Usage: java " + ApplicationLoader.class.getName() + " [classpath class [args..] [&[N]]]+ "); + } + + protected final String[] f_asArgs; + + /** + * Indicates if the launched applications should share the system classpath. + */ + private static final boolean SHARED_ROOT = Boolean.getBoolean(ApplicationLoader.class.getName() + ".sharedRoot"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/CanonicalNames.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/CanonicalNames.java new file mode 100644 index 0000000000000..527cd5d68f20f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/CanonicalNames.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.util; + +/** + * Helper class for Canonical Name processing. + * + * @author mf/jf 12.15.2017 + * @since Coherence 19.1.0.0 + */ +public class CanonicalNames + { + /** + * Compute the canonical name for a ValueExtractor. + *

+ * Canonical name is null when one or more optional method parameters + * are provided. If sName does not end in {@link #VALUE_EXTRACTOR_METHOD_SUFFIX}, + * "()", the canonical name is sName and represents a property. + * If sName is prefixed with one of the {@link #VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES} + * and ends in {@link #VALUE_EXTRACTOR_METHOD_SUFFIX}, + * the canonical name is sName with prefix and suffix removed. + * This canonical name represents a property. + * If the sName just ends in {#link #VALUE_EXTRACTOR_METHOD_SUFFIX}, + * the canonical name is same as sName. This canonical name + * references a method. If there are one or more parameters in aoParam, + * the canonical name is null. + * + * @param sName a property or method based on processing described above + * @param aoParam optional parameters + * + * @return return canonical name of sName if it exist; otherwise, null + */ + public static String computeValueExtractorCanonicalName(String sName, Object[] aoParam) + { + final int nMethodSuffixLength = VALUE_EXTRACTOR_METHOD_SUFFIX.length(); + if (aoParam != null && aoParam.length > 0) + { + return null; + } + else if (sName.endsWith(VALUE_EXTRACTOR_METHOD_SUFFIX)) + { + // check for JavaBean accessor and convert to property if found. + String sNameCanonical = sName; + int nNameLength = sName.length(); + for (String sPrefix : VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES) + { + int nPrefixLength = sPrefix.length(); + if (nNameLength > nPrefixLength && sName.startsWith(sPrefix)) + { + // detected a JavaBean accessor; convert method to a property. remove prefix and suffix. + sNameCanonical = Character.toLowerCase(sName.charAt(nPrefixLength)) + + sName.substring(nPrefixLength + 1, nNameLength - nMethodSuffixLength); + break; + } + } + return sNameCanonical; + } + else + { + // is a property + return sName; + } + } + + /** + * Compute canonical name when no optional parameters. + * + * @param sName a property or method based on processing described in + * {@link #computeValueExtractorCanonicalName(String, Object[]) above} + * + * @return canonicalName + */ + public static String computeValueExtractorCanonicalName(String sName) + { + return computeValueExtractorCanonicalName(sName, null); + } + + // ----- constants ------------------------------------------------------ + + /** + * If sName parameter in {@link #computeValueExtractorCanonicalName(String, Object[])} + * ends with this suffix, it represents a method name. + */ + public static final String VALUE_EXTRACTOR_METHOD_SUFFIX = "()"; + + /** + * JavaBean accessor prefixes. + */ + public static final String[] VALUE_EXTRACTOR_BEAN_ACCESSOR_PREFIXES = + {"get", "is"}; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/HeapDump.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/HeapDump.java new file mode 100644 index 0000000000000..8a0e7c96c14ab --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/HeapDump.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.util; + + +import java.nio.file.Files; +import java.nio.file.Path; +import javax.management.MBeanServer; +import java.io.File; +import java.lang.management.ManagementFactory; + +import com.sun.management.HotSpotDiagnosticMXBean; + +import java.util.Iterator; + +/** + * HeapDump allows for collection of hprof based heap dumps. + * + * @author hr 2013.08.08 + */ +public class HeapDump + { + /** + * Optionally collect a heap dump of live objects for the specified bug id. + * + * The dumps collected by this method can be enabled via -Dcom.oracle.coherence.common.internal.util.HeapDump=true + * or more narrowly via -Dcom.oracle.coherence.common.internal.util.HeapDump.bugid=true. The location of the dumps + * can be controlled via -Dcom.oracle.coherence.common.internal.util.HeapDump.dir=path, which if left unset will + * default ot the temp directory. -Dcom.oracle.coherence.common.internal.util.HeapDump.bugid.limit=n or + * -Dcom.oracle.coherence.common.internal.util.HeapDump.limit=n can be used to limit the total number of heap dumps + * collected for a specific bug id, the default limit is 3. + * + * @param sBugId the bug id, this will become part of the generated file name and system properties + * + * @return the name of the store file or null if dumps have been disabled + */ + public static String dumpHeapForBug(String sBugId) + { + String sClass = HeapDump.class.getName(); + String sPropBug = sClass + "." + sBugId; + + if (Boolean.parseBoolean(System.getProperty(sPropBug, System.getProperty(sClass, "false")))) + { + String sFileName; + try + { + String sDirName; + try + { + sDirName = System.getProperty(sClass + ".dir", System.getProperty("java.io.tmpdir", ".")); + } + catch (Throwable t) + { + sDirName = "."; + } + + File fileDir = new File(sDirName); + int cLimit = Integer.parseInt(System.getProperty(sPropBug + ".limit", + System.getProperty(sClass + ".limit", "3"))); + + if (cLimit <= 0) + { + return null; + } + + String sPrefix = sBugId + "-"; + for (Iterator iter = Files.list(fileDir.toPath()).iterator(); iter.hasNext(); ) + { + Path path = iter.next(); + if (path.getFileName().toFile().getName().startsWith(sPrefix)) + { + if (--cLimit <= 0) + { + return null; + } + } + } + File file = File.createTempFile(sPrefix, ".hprof", fileDir); + sFileName = file.getCanonicalPath(); + file.delete(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + + return dumpHeap(sFileName, true); + } + + return null; + } + + /** + * Collect a heap dump of live objects and write it into a dynamically named file in the temp directory, + * or -Dcom.oracle.coherence.common.internal.util.HeapDump.dir=path if defined. + * + * @return the name of the stored file + */ + public static String dumpHeap() + { + return dumpHeap(null, true); + } + + /** + * Collect a heap dump and write it into the specified file. + * + * @param sFileName the file in which to store the heap dump, or directory in which a dynamically named + * hprof file will be saved, or null for a dynamic file in the system temp directory + * @param fLive true if only live/reachable objects should be represented in the dump. + * + * @return the name of the stored file + */ + public static String dumpHeap(String sFileName, boolean fLive) + { + // initialize hotspot diagnostic MBean + initHotspotMBean(); + try + { + if (sFileName == null) + { + try + { + sFileName = System.getProperty(HeapDump.class.getName() + ".dir", + System.getProperty("java.io.tmpdir", ".")); + } + catch (Throwable t) + { + sFileName = "."; + } + } + + File file = new File(sFileName); + if (file.isDirectory()) + { + file = File.createTempFile("heapdump-", ".hprof", file); + sFileName = file.getCanonicalPath(); + file.delete(); + } + s_hotspotDiagMBean.dumpHeap(sFileName, fLive); + } + catch (RuntimeException re) + { + throw re; + } + catch (Exception exp) + { + throw new RuntimeException(exp); + } + + return sFileName; + } + + // initialize the hotspot diagnostic MBean field + private static void initHotspotMBean() + { + if (s_hotspotDiagMBean == null) + { + synchronized (HeapDump.class) + { + if (s_hotspotDiagMBean == null) + { + try + { + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + HotSpotDiagnosticMXBean bean = + ManagementFactory.newPlatformMXBeanProxy(server, + HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class); + s_hotspotDiagMBean = bean; + } + catch (RuntimeException re) + { + throw re; + } + catch (Exception exp) + { + throw new RuntimeException(exp); + } + } + } + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of the HotSpot Diagnostic MBean + */ + private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic"; + + /** + * The hotspot diagnostic MBean + */ + private static volatile HotSpotDiagnosticMXBean s_hotspotDiagMBean; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/Histogram.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/Histogram.java new file mode 100644 index 0000000000000..211056b4ec2e3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/Histogram.java @@ -0,0 +1,445 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.util; + +import com.oracle.coherence.common.base.Converter; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintStream; + +/** + * Class for tracking samples in a Histogram. + * + * Note this implementation is not thread-safe. + * + * @author mf 2006.07.05 + */ +public class Histogram + implements Cloneable, Externalizable + { + /** + * Construct a histogram of a given size. + * + * @param cLabels the maximum array size for the histogram + */ + public Histogram(int cLabels) + { + m_alResults = new long[cLabels]; + } + + public Object clone() + throws CloneNotSupportedException + { + return super.clone(); + } + + public Histogram setFormatter(Converter formatter) + { + m_formatter = formatter; + return this; + } + + /** + * Return the histogram index for a given sample. + * + * @param nSample the sample + * + * @return the histogram index for the given sample + */ + public int getIndex(int nSample) + { + return nSample; + } + + /** + * Return the minimum value for a label representing a range + * + * @param i the histogram index + * + * @return the minimum value for the given index + */ + public int getLabelMin(int i) + { + return i; + } + + /** + * Return the maximum value for a label representing a range + * + * @param i the histogram index + * + * @return the maximum value for the given index + */ + public int getLabelMax(int i) + { + return getLabelMin(i + 1) - 1; + } + + /** + * Return the median value for a given histogram index. + * + * @param i the index + * + * @return the median value for the given index + */ + public int getLabelMedian(int i) + { + int nMin = getLabelMin(i); + int nMax = getLabelMax(i); + return nMin + (nMax - nMin) / 2; + } + + /** + * Return the textual label corresponding to a given histogram index. + * + * @param i the index + * + * @return the textual label for the given index + */ + public String getLabelText(int i) + { + Converter formatter = m_formatter; + if (i < 0) + { + return "n/a"; + } + else if (i == 0) + { + // zero just means < 1 + return "<" + formatter.convert((double) getLabelMin(1)); + } + else if (i == m_alResults.length - 1) + { + // result was over the max supported by the histogram + return ">" + formatter.convert((double) getLabelMin(i)); + } + int nMin = getLabelMin(i); + int nMax = getLabelMax(i); + int nMedian = getLabelMedian(i); + int nRange = nMax - nMin; + + if (nRange > 1) + { + return formatter.convert((double) nMedian) + " +/-" + formatter.convert((double) nRange / 2); + } + + return formatter.convert((double) nMedian); + } + + /** + * Add a sample value to the histogram. + * + * @param nSampleValue the sampled value. + */ + public void addSample(int nSampleValue) + { + long[] alResults = m_alResults; + // compute index + int i = getIndex(nSampleValue); + i = Math.max(0, Math.min(i, alResults.length - 1)); + + ++alResults[i]; + } + + /** + * Add all the samples from the supplied Histogram to this Histogram. + * + * @param histThat the samples to add + */ + public void addSamples(Histogram histThat) + { + long[] alResultThat = histThat.getResults(); + long[] alResult = m_alResults; + for (int i = 0, c = alResultThat.length; i < c; ++i) + { + alResult[i] += alResultThat[i]; + } + } + + /** + * Return a new Histogram which is the result of subtracting the supplied Histogram samples from this Histograms + * samples + * + * @param histThat the samples to subtract + * + * @return the resulting Histogram + */ + public Histogram compare(Histogram histThat) + { + Histogram histDiff; + + try + { + histDiff = (Histogram) clone(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + + if (histThat == null) + { + histDiff.m_alResults = (long[]) getResults().clone(); + } + else + { + + long[] alResultThat = histThat.getResults(); + long[] alResult = m_alResults; + long[] alResultDiff = histDiff.m_alResults = new long[alResultThat.length]; + for (int i = 0, c = alResultThat.length; i < c; ++i) + { + alResultDiff[i] = alResult[i] - alResultThat[i]; + } + } + + return histDiff; + } + + public long[] getResults() + { + return m_alResults; + } + + public long getSampleCount() + { + long[] alResults = m_alResults; + long cSamples = 0; + + for (int i = 0, c = alResults.length; i < c; ++i) + { + long cSample = alResults[i]; + if (cSample > 0) + { + cSamples += cSample; + } + } + + return cSamples; + } + + public String toString() + { + long[] alResults = m_alResults; + long cSamples = 0; + long cTotal = 0; + int iMin = -1; + int iMax = 0; + double cTotalSq = 0; + + for (int i = 0, c = alResults.length; i < c; ++i) + { + long cSample = alResults[i]; + if (cSample > 0) + { + if (iMin == -1) + { + iMin = i; + } + iMax = i; + cSamples += cSample; + + int nMedian = getLabelMedian(i); + + cTotal += cSample * nMedian; + cTotalSq += cSample * nMedian * nMedian; + } + } + + // second pass compute percentiles + double[] adPercentage = new double[]{.33, .66, .99, .999, .9999, .99999, 1}; + String[] asPercentage = new String[]{"33%", "66%", "99%", "99.9%", "99.99%", "99.999%"}; + int[] anPercentile = new int[adPercentage.length - 1]; + + long cRunningTotal = 0; + long lLimit = (long) (adPercentage[0] * cSamples); + for (int i = 0, c = alResults.length, p = 0; i < c && p < anPercentile.length; ++i) + { + cRunningTotal += alResults[i]; + while (cRunningTotal > lLimit && p < anPercentile.length) + { + anPercentile[p] = i; + lLimit = (long) (adPercentage[++p] * cSamples); + } + } + + double dAvg = cTotal / (double) cSamples; + double dStddev = Math.sqrt( + ((cSamples * (double) cTotalSq) - (cTotal * (double) cTotal)) + / (cSamples * (double) (cSamples - 1))); + + dAvg = ((int) (dAvg * 1000)) / 1000D; + dStddev = ((int) (dStddev * 1000)) / 1000D; + + Converter formatter = m_formatter; + + StringBuffer sb = new StringBuffer(); + sb.append("samples ").append(cSamples); + sb.append("; avg ").append(formatter.convert(dAvg)); + sb.append("; stddev ").append(formatter.convert(dStddev)); + sb.append("; min ").append(getLabelText(iMin)); + for (int i = 0, c = anPercentile.length; i < c; ++i) + { + if (i == anPercentile.length - 1 || anPercentile[i] != anPercentile[i + 1]) + { + sb.append("; " + asPercentage[i] + " <" + formatter.convert((double) getLabelMax(anPercentile[i]))); + } + // else; information is redundant with the next thing to be printed + } + sb.append("; max ").append(getLabelText(iMax)); + + return sb.toString(); + } + + /** + * Write histogram in spreadsheet loadable format. + * + * @param out the output PrintStream + */ + public void writeReport(PrintStream out) + { + long[] alResults = m_alResults; + for (int i = 0, c = alResults.length; i < c; ++i) + { + long cSample = alResults[i]; + if (cSample > 0) + { + out.println(getLabelText(i) + '\t' + cSample); + } + } + } + + /** + * Write histogram header in spreadsheet loadable format. + * + * @param out the output PrintStream + */ + public void writeReportHeaderHz(PrintStream out) + { + long[] alResults = m_alResults; + for (int i = 0, c = alResults.length; i < c; ++i) + { + out.print(getLabelText(i) + '\t'); + } + out.println(); + } + + + /** + * Write horizontal histogram in spreadsheet loadable format. + * + * @param out the output PrintStream + */ + public void writeReportHz(PrintStream out) + { + // all results are written so that we may build a table of + // multiple histograms and compare them + long[] alResults = m_alResults; + int cSkip = 0; + for (int i = 0, c = alResults.length; i < c; ++i) + { + long cSample = alResults[i]; + if (cSample == 0) + { + // avoid a huge string of 0s at the end + ++cSkip; + } + else + { + for (; cSkip > 0; --cSkip) + { + out.print("0\t"); + } + out.print(alResults[i] + "\t"); + } + } + out.println(); + } + + @Override + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException + { + readExternal((DataInput) in); + } + + public void readExternal(DataInput dataInput) + throws IOException + { + int c = dataInput.readInt(); + long[] alResults = m_alResults = new long[c]; + for (int i = 0; i < c; ) + { + long n = dataInput.readLong(); + if (n < 0) + { + // skip over gaps + i += -n; + continue; + } + alResults[i++] = n; + } + } + + @Override + public void writeExternal(ObjectOutput out) + throws IOException + { + writeExternal((DataOutput) out); + } + + public void writeExternal(DataOutput dataOutput) + throws IOException + { + long[] alResults = m_alResults; + int c = alResults.length; + dataOutput.writeInt(c); + int iSkipStart = -1; + for (int i = 0; i < c; ++i) + { + long n = alResults[i]; + if (n == 0) + { + if (iSkipStart < 0) + { + iSkipStart = i; + } + } + else + { + if (iSkipStart >= 0) + { + dataOutput.writeLong(iSkipStart - i); + iSkipStart = -1; + } + dataOutput.writeLong(n); + } + } + + if (iSkipStart >= 0) + { + dataOutput.writeLong(iSkipStart - c); + } + } + + protected long[] m_alResults; + protected Converter m_formatter = new Converter() + { + @Override + public String convert(Double value) + { + return value.toString(); + } + }; + } + + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/LoggingBridge.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/LoggingBridge.java new file mode 100644 index 0000000000000..4385e0d9d19d3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/LoggingBridge.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.util; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.util.Base; + +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.logging.StreamHandler; + +/** + * A bridge between the JDK standard logger and the Coherence logger. + * + * @author mf 2018.06.11 + */ +public class LoggingBridge + { + /** + * Construct an anonymous logger which will direct its output to the Coherence logger. + * + * @return a bridge logger + */ + public static Logger createBridge() + { + Logger logger = Logger.getAnonymousLogger(); + + int nLevel = 0; + for (; nLevel < 9 && CacheFactory.isLogEnabled(nLevel); ++nLevel) + { + } + + Level level; + if (CacheFactory.isLogEnabled(9)) + { + level = Level.ALL; + } + else if (CacheFactory.isLogEnabled(7)) + { + level = Level.FINER; + } + else if (CacheFactory.isLogEnabled(5)) + { + level = Level.FINE; + } + else if (CacheFactory.isLogEnabled(4)) + { + level = Level.CONFIG; + } + else if (CacheFactory.isLogEnabled(Base.LOG_INFO)) + { + level = Level.INFO; + } + else if (CacheFactory.isLogEnabled(Base.LOG_WARN)) + { + level = Level.WARNING; + } + else // if (CacheFactory.isLogEnabled(Base.LOG_ERR)) + { + level = Level.SEVERE; + } + + logger.setLevel(level); + logger.setUseParentHandlers(false); + + logger.addHandler(new StreamHandler() + { + @Override + public void publish(LogRecord record) + { + int nLevelSrc = record.getLevel().intValue(); + int nLevelDst = nLevelSrc <= FINEST ? 9 + : nLevelSrc <= FINER ? 7 + : nLevelSrc <= FINE ? 5 + : nLevelSrc <= CONFIG ? 4 + : nLevelSrc <= INFO ? CacheFactory.LOG_INFO + : nLevelSrc <= WARNING ? CacheFactory.LOG_WARN + : /*SEVERE*/ CacheFactory.LOG_ERR; + + if (CacheFactory.isLogEnabled(nLevelDst)) + { + Throwable ex = record.getThrown(); + if (ex == null) + { + CacheFactory.log(getFormatter().formatMessage(record), nLevelDst); + } + else + { + CacheFactory.log(getFormatter().formatMessage(record) + "\n" + + Base.printStackTrace(ex), nLevelDst); + } + } + } + + @Override + public void flush() + { + } + + @Override + public void close() + throws SecurityException + { + } + }); + + return logger; + } + + // ----- constants ------------------------------------------------------ + + protected static final int FINEST = Level.FINEST.intValue(); + protected static final int FINER = Level.FINER.intValue(); + protected static final int FINE = Level.FINE.intValue(); + protected static final int CONFIG = Level.CONFIG.intValue(); + protected static final int INFO = Level.INFO.intValue(); + protected static final int WARNING = Level.WARNING.intValue(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/ScaledHistogram.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/ScaledHistogram.java new file mode 100644 index 0000000000000..1e90ea93f9f34 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/ScaledHistogram.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.util; + + +/** + * A Histogram whose values are scaled such that low values have higher + * resolution then high values. + * + * @author mf 2006.07.05 + */ +public class ScaledHistogram + extends Histogram + { + public ScaledHistogram() + { + this(1000000); + } + + /** + * Construct a ScaledHistogram. + * + * @param cMax the maximum supported value + */ + public ScaledHistogram(int cMax) + { + this (cMax, 100); + } + + /** + * Construct a ScaledHistogram. + * + * @param cMax the maximum supported value + * @param cHighRes the number of singular values to store in an unscaled fashion + */ + public ScaledHistogram(int cMax, int cHighRes) + { + super(computeSlot(cHighRes, cMax)); + + f_cHighRes = cHighRes; + } + + @Override + public int getIndex(int nValue) + { + return computeSlot(f_cHighRes, nValue); + } + + @Override + public int getLabelMin(int nSlot) + { + return nSlot <= f_cHighRes + ? nSlot + : f_cHighRes + (int) Math.ceil(Math.pow(10, ((nSlot - f_cHighRes) / STRETCH))); + } + + + /** + * Helper for computing slot for a given sample value. + * + * @param cHigh the number of high precision slots + * @param nValue the value + * + * @return the slot + */ + protected static int computeSlot(int cHigh, int nValue) + { + return nValue <= cHigh + ? nValue + : cHigh + (int) ((Math.log(nValue - cHigh) / LOG10) * STRETCH); + } + + protected final int f_cHighRes; + public static final double LOG10 = Math.log(10); + protected static final double STRETCH = 10; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/Timer.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/Timer.java new file mode 100644 index 0000000000000..b993aba389c6f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/Timer.java @@ -0,0 +1,668 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.util; + +import java.util.Arrays; +import java.util.Date; + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.util.SafeClock; + +import java.util.concurrent.atomic.AtomicInteger; + +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * Modification of the Java Timer class respects insertion order of + * TimerTask instances when ordering them for execution as well as + * protects against clock rollback. + * + * @author jh 2014.07.23 + */ +public class Timer { + /** + * The timer task queue. This data structure is shared with the timer + * thread. The timer produces tasks, via its various schedule calls, + * and the timer thread consumes, executing timer tasks as appropriate, + * and removing them from the queue when they're obsolete. + */ + private final TaskQueue queue = new TaskQueue(); + + /** + * The timer thread. + */ + private final TimerThread thread = new TimerThread(queue); + + /** + * This object causes the timer's task execution thread to exit + * gracefully when there are no live references to the Timer object and no + * tasks in the timer queue. It is used in preference to a finalizer on + * Timer as such a finalizer would be susceptible to a subclass's + * finalizer forgetting to call it. + */ + private final Object threadReaper = new Object() { + protected void finalize() throws Throwable { + synchronized(queue) { + thread.newTasksMayBeScheduled = false; + queue.notify(); // In case queue is empty. + } + } + }; + + /** + * This ID is used to generate thread names. + */ + private final static AtomicInteger nextSerialNumber = new AtomicInteger(0); + private static int serialNumber() { + return nextSerialNumber.getAndIncrement(); + } + + /** + * Creates a new timer. The associated thread does not + * {@linkplain Thread#setDaemon run as a daemon}. + */ + public Timer() { + this("Timer-" + serialNumber()); + } + + /** + * Creates a new timer whose associated thread may be specified to + * {@linkplain Thread#setDaemon run as a daemon}. + * A daemon thread is called for if the timer will be used to + * schedule repeating "maintenance activities", which must be + * performed as long as the application is running, but should not + * prolong the lifetime of the application. + * + * @param isDaemon true if the associated thread should run as a daemon. + */ + public Timer(boolean isDaemon) { + this("Timer-" + serialNumber(), isDaemon); + } + + /** + * Creates a new timer whose associated thread has the specified name. + * The associated thread does not + * {@linkplain Thread#setDaemon run as a daemon}. + * + * @param name the name of the associated thread + * @throws NullPointerException if {@code name} is null + * @since 1.5 + */ + public Timer(String name) { + thread.setName(name); + thread.start(); + } + + /** + * Creates a new timer whose associated thread has the specified name, + * and may be specified to + * {@linkplain Thread#setDaemon run as a daemon}. + * + * @param name the name of the associated thread + * @param isDaemon true if the associated thread should run as a daemon + * @throws NullPointerException if {@code name} is null + * @since 1.5 + */ + public Timer(String name, boolean isDaemon) { + thread.setName(name); + thread.setDaemon(isDaemon); + thread.start(); + } + + /** + * Schedules the specified task for execution after the specified delay. + * + * @param task task to be scheduled. + * @param delay delay in milliseconds before task is to be executed. + * @throws IllegalArgumentException if delay is negative, or + * delay + SafeClock.getSafeTimeMillis() is negative. + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} is null + */ + public void schedule(TimerTask task, long delay) { + if (delay < 0) + throw new IllegalArgumentException("Negative delay."); + sched(task, SafeClock.INSTANCE.getSafeTimeMillis()+delay, 0); + } + + /** + * Schedules the specified task for execution at the specified time. If + * the time is in the past, the task is scheduled for immediate execution. + * + * @param task task to be scheduled. + * @param time time at which task is to be executed. + * @throws IllegalArgumentException if time.getTime() is negative. + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} or {@code time} is null + */ + public void schedule(TimerTask task, Date time) { + sched(task, time.getTime(), 0); + } + + /** + * Schedules the specified task for repeated fixed-delay execution, + * beginning after the specified delay. Subsequent executions take place + * at approximately regular intervals separated by the specified period. + * + *

In fixed-delay execution, each execution is scheduled relative to + * the actual execution time of the previous execution. If an execution + * is delayed for any reason (such as garbage collection or other + * background activity), subsequent executions will be delayed as well. + * In the long run, the frequency of execution will generally be slightly + * lower than the reciprocal of the specified period (assuming the system + * clock underlying Object.wait(long) is accurate). + * + *

Fixed-delay execution is appropriate for recurring activities + * that require "smoothness." In other words, it is appropriate for + * activities where it is more important to keep the frequency accurate + * in the short run than in the long run. This includes most animation + * tasks, such as blinking a cursor at regular intervals. It also includes + * tasks wherein regular activity is performed in response to human + * input, such as automatically repeating a character as long as a key + * is held down. + * + * @param task task to be scheduled. + * @param delay delay in milliseconds before task is to be executed. + * @param period time in milliseconds between successive task executions. + * @throws IllegalArgumentException if {@code delay < 0}, or + * {@code delay + SafeClock.getSafeTimeMillis() < 0}, or + * {@code period <= 0} + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} is null + */ + public void schedule(TimerTask task, long delay, long period) { + if (delay < 0) + throw new IllegalArgumentException("Negative delay."); + if (period <= 0) + throw new IllegalArgumentException("Non-positive period."); + sched(task, SafeClock.INSTANCE.getSafeTimeMillis()+delay, -period); + } + + /** + * Schedules the specified task for repeated fixed-delay execution, + * beginning at the specified time. Subsequent executions take place at + * approximately regular intervals, separated by the specified period. + * + *

In fixed-delay execution, each execution is scheduled relative to + * the actual execution time of the previous execution. If an execution + * is delayed for any reason (such as garbage collection or other + * background activity), subsequent executions will be delayed as well. + * In the long run, the frequency of execution will generally be slightly + * lower than the reciprocal of the specified period (assuming the system + * clock underlying Object.wait(long) is accurate). As a + * consequence of the above, if the scheduled first time is in the past, + * it is scheduled for immediate execution. + * + *

Fixed-delay execution is appropriate for recurring activities + * that require "smoothness." In other words, it is appropriate for + * activities where it is more important to keep the frequency accurate + * in the short run than in the long run. This includes most animation + * tasks, such as blinking a cursor at regular intervals. It also includes + * tasks wherein regular activity is performed in response to human + * input, such as automatically repeating a character as long as a key + * is held down. + * + * @param task task to be scheduled. + * @param firstTime First time at which task is to be executed. + * @param period time in milliseconds between successive task executions. + * @throws IllegalArgumentException if {@code firstTime.getTime() < 0}, or + * {@code period <= 0} + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} or {@code firstTime} is null + */ + public void schedule(TimerTask task, Date firstTime, long period) { + if (period <= 0) + throw new IllegalArgumentException("Non-positive period."); + sched(task, firstTime.getTime(), -period); + } + + /** + * Schedules the specified task for repeated fixed-rate execution, + * beginning after the specified delay. Subsequent executions take place + * at approximately regular intervals, separated by the specified period. + * + *

In fixed-rate execution, each execution is scheduled relative to the + * scheduled execution time of the initial execution. If an execution is + * delayed for any reason (such as garbage collection or other background + * activity), two or more executions will occur in rapid succession to + * "catch up." In the long run, the frequency of execution will be + * exactly the reciprocal of the specified period (assuming the system + * clock underlying Object.wait(long) is accurate). + * + *

Fixed-rate execution is appropriate for recurring activities that + * are sensitive to absolute time, such as ringing a chime every + * hour on the hour, or running scheduled maintenance every day at a + * particular time. It is also appropriate for recurring activities + * where the total time to perform a fixed number of executions is + * important, such as a countdown timer that ticks once every second for + * ten seconds. Finally, fixed-rate execution is appropriate for + * scheduling multiple repeating timer tasks that must remain synchronized + * with respect to one another. + * + * @param task task to be scheduled. + * @param delay delay in milliseconds before task is to be executed. + * @param period time in milliseconds between successive task executions. + * @throws IllegalArgumentException if {@code delay < 0}, or + * {@code delay + SafeClock.getSafeTimeMillis() < 0}, or + * {@code period <= 0} + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} is null + */ + public void scheduleAtFixedRate(TimerTask task, long delay, long period) { + if (delay < 0) + throw new IllegalArgumentException("Negative delay."); + if (period <= 0) + throw new IllegalArgumentException("Non-positive period."); + sched(task, SafeClock.INSTANCE.getSafeTimeMillis()+delay, period); + } + + /** + * Schedules the specified task for repeated fixed-rate execution, + * beginning at the specified time. Subsequent executions take place at + * approximately regular intervals, separated by the specified period. + * + *

In fixed-rate execution, each execution is scheduled relative to the + * scheduled execution time of the initial execution. If an execution is + * delayed for any reason (such as garbage collection or other background + * activity), two or more executions will occur in rapid succession to + * "catch up." In the long run, the frequency of execution will be + * exactly the reciprocal of the specified period (assuming the system + * clock underlying Object.wait(long) is accurate). As a + * consequence of the above, if the scheduled first time is in the past, + * then any "missed" executions will be scheduled for immediate "catch up" + * execution. + * + *

Fixed-rate execution is appropriate for recurring activities that + * are sensitive to absolute time, such as ringing a chime every + * hour on the hour, or running scheduled maintenance every day at a + * particular time. It is also appropriate for recurring activities + * where the total time to perform a fixed number of executions is + * important, such as a countdown timer that ticks once every second for + * ten seconds. Finally, fixed-rate execution is appropriate for + * scheduling multiple repeating timer tasks that must remain synchronized + * with respect to one another. + * + * @param task task to be scheduled. + * @param firstTime First time at which task is to be executed. + * @param period time in milliseconds between successive task executions. + * @throws IllegalArgumentException if {@code firstTime.getTime() < 0} or + * {@code period <= 0} + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} or {@code firstTime} is null + */ + public void scheduleAtFixedRate(TimerTask task, Date firstTime, + long period) { + if (period <= 0) + throw new IllegalArgumentException("Non-positive period."); + sched(task, firstTime.getTime(), period); + } + + /** + * Schedule the specified timer task for execution at the specified + * time with the specified period, in milliseconds. If period is + * positive, the task is scheduled for repeated execution; if period is + * zero, the task is scheduled for one-time execution. Time is specified + * in Date.getTime() format. This method checks timer state, task state, + * and initial execution time, but not period. + * + * @throws IllegalArgumentException if time is negative. + * @throws IllegalStateException if task was already scheduled or + * cancelled, timer was cancelled, or timer thread terminated. + * @throws NullPointerException if {@code task} is null + */ + private void sched(TimerTask task, long time, long period) { + if (time < 0) + throw new IllegalArgumentException("Illegal execution time."); + + // Constrain value of period sufficiently to prevent numeric + // overflow while still being effectively infinitely large. + if (Math.abs(period) > (Long.MAX_VALUE >> 1)) + period >>= 1; + + synchronized(queue) { + if (!thread.newTasksMayBeScheduled) + throw new IllegalStateException("Timer already cancelled."); + + synchronized(task.lock) { + if (task.state != TimerTask.VIRGIN) + throw new IllegalStateException( + "Task already scheduled or cancelled"); + task.nextExecutionTime = time; + task.period = period; + task.state = TimerTask.SCHEDULED; + } + + queue.add(task); + if (queue.getMin() == task) + queue.notify(); + } + } + + /** + * Terminates this timer, discarding any currently scheduled tasks. + * Does not interfere with a currently executing task (if it exists). + * Once a timer has been terminated, its execution thread terminates + * gracefully, and no more tasks may be scheduled on it. + * + *

Note that calling this method from within the run method of a + * timer task that was invoked by this timer absolutely guarantees that + * the ongoing task execution is the last task execution that will ever + * be performed by this timer. + * + *

This method may be called repeatedly; the second and subsequent + * calls have no effect. + */ + public void cancel() { + synchronized(queue) { + thread.newTasksMayBeScheduled = false; + queue.clear(); + queue.notify(); // In case queue was already empty. + } + } + + /** + * Removes all cancelled tasks from this timer's task queue. Calling + * this method has no effect on the behavior of the timer, but + * eliminates the references to the cancelled tasks from the queue. + * If there are no external references to these tasks, they become + * eligible for garbage collection. + * + *

Most programs will have no need to call this method. + * It is designed for use by the rare application that cancels a large + * number of tasks. Calling this method trades time for space: the + * runtime of the method may be proportional to n + c log n, where n + * is the number of tasks in the queue and c is the number of cancelled + * tasks. + * + *

Note that it is permissible to call this method from within a + * a task scheduled on this timer. + * + * @return the number of tasks removed from the queue. + * @since 1.5 + */ + public int purge() { + int result = 0; + + synchronized(queue) { + for (int i = queue.size(); i > 0; i--) { + if (queue.get(i).state == TimerTask.CANCELLED) { + queue.quickRemove(i); + result++; + } + } + + if (result != 0) + queue.heapify(); + } + + return result; + } +} + +/** + * This "helper class" implements the timer's task execution thread, which + * waits for tasks on the timer queue, executions them when they fire, + * reschedules repeating tasks, and removes cancelled tasks and spent + * non-repeating tasks from the queue. + */ +class TimerThread extends Thread { + /** + * This flag is set to false by the reaper to inform us that there + * are no more live references to our Timer object. Once this flag + * is true and there are no more tasks in our queue, there is no + * work left for us to do, so we terminate gracefully. Note that + * this field is protected by queue's monitor! + */ + boolean newTasksMayBeScheduled = true; + + /** + * Our Timer's queue. We store this reference in preference to + * a reference to the Timer so the reference graph remains acyclic. + * Otherwise, the Timer would never be garbage-collected and this + * thread would never go away. + */ + private TaskQueue queue; + + TimerThread(TaskQueue queue) { + this.queue = queue; + } + + public void run() { + try { + mainLoop(); + } finally { + // Someone killed this Thread, behave as if Timer cancelled + synchronized(queue) { + newTasksMayBeScheduled = false; + queue.clear(); // Eliminate obsolete references + } + } + } + + /** + * The main timer loop. (See class comment.) + */ + private void mainLoop() { + while (true) { + TimerTask task = null; + try { + boolean taskFired; + synchronized(queue) { + // Wait for queue to become non-empty + while (queue.isEmpty() && newTasksMayBeScheduled) + Blocking.wait(queue); + if (queue.isEmpty()) + break; // Queue is empty and will forever remain; die + + // Queue nonempty; look at first evt and do the right thing + long currentTime, executionTime; + task = queue.getMin(); + synchronized(task.lock) { + if (task.state == TimerTask.CANCELLED) { + queue.removeMin(); + continue; // No action required, poll queue again + } + currentTime = SafeClock.INSTANCE.getSafeTimeMillis(); + executionTime = task.nextExecutionTime; + if (taskFired = (executionTime<=currentTime)) { + if (task.period == 0) { // Non-repeating, remove + queue.removeMin(); + task.state = TimerTask.EXECUTED; + } else { // Repeating task, reschedule + queue.rescheduleMin( + task.period<0 ? currentTime - task.period + : executionTime + task.period); + } + } + } + if (!taskFired) // Task hasn't yet fired; wait + Blocking.wait(queue, executionTime - currentTime); + } + if (taskFired) // Task fired; run it, holding no locks + task.run(); + } + catch(InterruptedException e) {} + catch(Throwable e) { + Logger.getLogger(Timer.class.getName()).log(Level.WARNING, + "Ignoring unhandled exception from the timer task " + task, e); + } + } + } +} + +/** + * This class represents a timer task queue: a priority queue of TimerTasks, + * ordered on nextExecutionTime. Each Timer object has one of these, which it + * shares with its TimerThread. Internally this class uses a heap, which + * offers log(n) performance for the add, removeMin and rescheduleMin + * operations, and constant time performance for the getMin operation. + */ +class TaskQueue { + /** + * Priority queue represented as a balanced binary heap: the two children + * of queue[n] are queue[2*n] and queue[2*n+1]. The priority queue is + * ordered on the nextExecutionTime field: The TimerTask with the lowest + * nextExecutionTime is in queue[1] (assuming the queue is nonempty). For + * each node n in the heap, and each descendant of n, d, n.compareTo(d) <= 0. + */ + private TimerTask[] queue = new TimerTask[128]; + + /** + * The number of tasks in the priority queue. (The tasks are stored in + * queue[1] up to queue[size]). + */ + private int size = 0; + + /** + * The number of tasks added to this priority queue. + */ + private long addCount = 0; + + /** + * Returns the number of tasks currently on the queue. + */ + int size() { + return size; + } + + /** + * Adds a new task to the priority queue. + */ + void add(TimerTask task) { + // Grow backing store if necessary + if (size + 1 == queue.length) + queue = Arrays.copyOf(queue, 2*queue.length); + + task.scheduleOrder = addCount++; + queue[++size] = task; + fixUp(size); + } + + /** + * Return the "head task" of the priority queue. (The head task is an + * task with the lowest nextExecutionTime.) + */ + TimerTask getMin() { + return queue[1]; + } + + /** + * Return the ith task in the priority queue, where i ranges from 1 (the + * head task, which is returned by getMin) to the number of tasks on the + * queue, inclusive. + */ + TimerTask get(int i) { + return queue[i]; + } + + /** + * Remove the head task from the priority queue. + */ + void removeMin() { + queue[1] = queue[size]; + queue[size--] = null; // Drop extra reference to prevent memory leak + fixDown(1); + } + + /** + * Removes the ith element from queue without regard for maintaining + * the heap invariant. Recall that queue is one-based, so + * 1 <= i <= size. + */ + void quickRemove(int i) { + assert i <= size; + + queue[i] = queue[size]; + queue[size--] = null; // Drop extra ref to prevent memory leak + } + + /** + * Sets the nextExecutionTime associated with the head task to the + * specified value, and adjusts priority queue accordingly. + */ + void rescheduleMin(long newTime) { + queue[1].nextExecutionTime = newTime; + fixDown(1); + } + + /** + * Returns true if the priority queue contains no elements. + */ + boolean isEmpty() { + return size==0; + } + + /** + * Removes all elements from the priority queue. + */ + void clear() { + // Null out task references to prevent memory leak + for (int i=1; i<=size; i++) + queue[i] = null; + + size = 0; + } + + /** + * Establishes the heap invariant (described above) assuming the heap + * satisfies the invariant except possibly for the leaf-node indexed by k + * (which may have a nextExecutionTime less than its parent's). + * + * This method functions by "promoting" queue[k] up the hierarchy + * (by swapping it with its parent) repeatedly until queue[k]'s + * nextExecutionTime is greater than or equal to that of its parent. + */ + private void fixUp(int k) { + while (k > 1) { + int j = k >> 1; + if (queue[j].compareTo(queue[k]) <= 0) + break; + TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; + k = j; + } + } + + /** + * Establishes the heap invariant (described above) in the subtree + * rooted at k, which is assumed to satisfy the heap invariant except + * possibly for node k itself (which may have a nextExecutionTime greater + * than its children's). + * + * This method functions by "demoting" queue[k] down the hierarchy + * (by swapping it with its smaller child) repeatedly until queue[k]'s + * nextExecutionTime is less than or equal to those of its children. + */ + private void fixDown(int k) { + int j; + while ((j = k << 1) <= size && j > 0) { + if (j < size && + queue[j].compareTo(queue[j+1]) > 0) + j++; // j indexes smallest kid + if (queue[k].compareTo(queue[j]) <= 0) + break; + TimerTask tmp = queue[j]; queue[j] = queue[k]; queue[k] = tmp; + k = j; + } + } + + /** + * Establishes the heap invariant (described above) in the entire tree, + * assuming nothing about the order of the elements prior to the call. + */ + void heapify() { + for (int i = size/2; i >= 1; i--) + fixDown(i); + } +} diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/TimerTask.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/TimerTask.java new file mode 100644 index 0000000000000..76f87680950b5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/internal/util/TimerTask.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.internal.util; + +import com.oracle.coherence.common.base.Disposable; + +import java.util.Date; + +/** + * Modification of the Java TimerTask class that is comparable based + * upon next execution time and insertion order. + * + * @author jh 2014.07.23 + */ +public abstract class TimerTask + implements Runnable, Comparable, Disposable + { + /** + * This object is used to control access to the TimerTask internals. + */ + final Object lock = new Object(); + + /** + * The state of this task, chosen from the constants below. + */ + int state = VIRGIN; + + /** + * This task has not yet been scheduled. + */ + static final int VIRGIN = 0; + + /** + * This task is scheduled for execution. If it is a non-repeating task, + * it has not yet been executed. + */ + static final int SCHEDULED = 1; + + /** + * This non-repeating task has already executed (or is currently + * executing) and has not been cancelled. + */ + static final int EXECUTED = 2; + + /** + * This task has been cancelled (with a call to TimerTask.cancel). + */ + static final int CANCELLED = 3; + + /** + * Next execution time for this task in the format returned by + * System.currentTimeMillis, assuming this task is scheduled for execution. + * For repeating tasks, this field is updated prior to each task execution. + */ + long nextExecutionTime; + + /** + * Period in milliseconds for repeating tasks. A positive value indicates + * fixed-rate execution. A negative value indicates fixed-delay execution. + * A value of 0 indicates a non-repeating task. + */ + long period = 0; + + /** + * The order in which this task was scheduled. + */ + long scheduleOrder; + + /** + * Creates a new timer task. + */ + protected TimerTask() { + } + + /** + * The action to be performed by this timer task. + */ + public abstract void run(); + + /** + * Cancels this timer task. If the task has been scheduled for one-time + * execution and has not yet run, or has not yet been scheduled, it will + * never run. If the task has been scheduled for repeated execution, it + * will never run again. (If the task is running when this call occurs, + * the task will run to completion, but will never run again.) + * + *

Note that calling this method from within the run method of + * a repeating timer task absolutely guarantees that the timer task will + * not run again. + * + *

This method may be called repeatedly; the second and subsequent + * calls have no effect. + * + * @return true if this task is scheduled for one-time execution and has + * not yet run, or this task is scheduled for repeated execution. + * Returns false if the task was scheduled for one-time execution + * and has already run, or if the task was never scheduled, or if + * the task was already cancelled. (Loosely speaking, this method + * returns true if it prevents one or more scheduled + * executions from taking place.) + */ + public boolean cancel() { + synchronized(lock) { + boolean result = (state == SCHEDULED); + state = CANCELLED; + return result; + } + } + + @Override + public void dispose() + { + cancel(); + } + + /** + * Returns the scheduled execution time of the most recent + * actual execution of this task. (If this method is invoked + * while task execution is in progress, the return value is the scheduled + * execution time of the ongoing task execution.) + * + *

This method is typically invoked from within a task's run method, to + * determine whether the current execution of the task is sufficiently + * timely to warrant performing the scheduled activity: + *

+     *   public void run() {
+     *       if (SafeClock.getSafeTimeMillis() - scheduledExecutionTime() >=
+     *           MAX_TARDINESS)
+     *               return;  // Too late; skip this execution.
+     *       // Perform the task
+     *   }
+     * 
+ * This method is typically not used in conjunction with + * fixed-delay execution repeating tasks, as their scheduled + * execution times are allowed to drift over time, and so are not terribly + * significant. + * + * @return the time at which the most recent execution of this task was + * scheduled to occur, in the format returned by Date.getTime(). + * The return value is undefined if the task has yet to commence + * its first execution. + * @see Date#getTime() + */ + public long scheduledExecutionTime() { + synchronized(lock) { + return (period < 0 ? nextExecutionTime + period + : nextExecutionTime - period); + } + } + + // ---- Comparable interface -------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(TimerTask that) { + long nextExecutionTimeThis = this.nextExecutionTime; + long nextExecutionTimeThat = that.nextExecutionTime; + if (nextExecutionTimeThis == nextExecutionTimeThat) { + long scheduleOrderThis = this.scheduleOrder; + long scheduleOrderThat = that.scheduleOrder; + return scheduleOrderThis == scheduleOrderThat + ? 0 + : scheduleOrderThis < scheduleOrderThat + ? -1 + : 1; + } else { + return nextExecutionTimeThis == nextExecutionTimeThat + ? 0 + : nextExecutionTimeThis < nextExecutionTimeThat + ? -1 + : 1; + } + } +} diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferManager.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferManager.java new file mode 100644 index 0000000000000..260a08a52fa33 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferManager.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import com.oracle.coherence.common.base.Disposable; + +import java.nio.ByteBuffer; + + +/** + * BufferManager defines a mechanism for efficient buffer re-use. + * + * @author mf 2010.12.02 + */ +public interface BufferManager + extends Disposable + { + /** + * Acquire a free ByteBuffer. + *

+ * The returned buffer will be at least the requested size. If a larger + * buffer is returned the limit will have been pre-set to cbMin, + * though the entire capacity is available to the caller. + * + * @param cbMin the minimal required size + * + * @return the ByteBuffer with {@link ByteBuffer#remaining} equal to + * cbMin + * + * @throws OutOfMemoryError if the request cannot be immediately satisfied + */ + public ByteBuffer acquire(int cbMin); + + /** + * Acquire a free ByteBuffer, of any size. + *

+ * The intended use of this method is to allow the buffer manager to + * satisfy a large memory request of a known size over a series of + * allocations. The caller may need to chain a series of buffers together + * to ultimately fulfill their required buffer size. + *

+ * If a larger buffer is returned the limit will have been pre-set such to + * cbPref, though the entire capacity is available to the caller. + * + * @param cbPref the preferred size + * + * @return the ByteBuffer with {@link ByteBuffer#remaining} less than or + * equal to cbPref + * + * @throws OutOfMemoryError if the request cannot be immediately satisfied + */ + public ByteBuffer acquirePref(int cbPref); + + /** + * Acquire a free ByteBuffer, of any size. + *

+ * The intended use of this method is to allow the buffer manager to + * satisfy the allocation of a potentially large but unknown size memory + * request, for instance the serialization of a complex object. + * + * @param cbSum the running total of prior acquisitions + * + * @return the ByteBuffer of any size + * + * @throws OutOfMemoryError if the request cannot be immediately satisfied + */ + public ByteBuffer acquireSum(int cbSum); + + /** + * Truncate a formerly allocated buffer, returning a buffer whose size + * more closely matches the amount of space used (as indicated by {@link + * ByteBuffer#remaining}) in the specified buffer. + *

+ * The returned buffer will have the same number of remaining bytes as + * the original, and those bytes will have the same content. + *

+ * If a new buffer is returned the old buffer will have been automatically + * released to the manager. + * + * @param buff the buffer to truncate + * + * @return a new buffer, or the same buffer if no exchange was deemed + * necessary. + * + * @throws IllegalArgumentException may be thrown if the specified buffer + * was not acquired from this BufferManager + */ + public ByteBuffer truncate(ByteBuffer buff); + + /** + * Release a formerly acquired ByteBuffer. + * + * @param buff the buffer + * + * @throws IllegalArgumentException may be thrown if the specified buffer + * was not acquired from this BufferManager + */ + public void release(ByteBuffer buff); + + /** + * Return the maximum capacity (in bytes) for this BufferManager, or Long.MAX_VALUE if it has no limit. + * + * @return the maximum capacity + */ + public long getCapacity(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferManagers.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferManagers.java new file mode 100644 index 0000000000000..52e9bc0fc7157 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferManagers.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import com.oracle.coherence.common.internal.io.AbstractBufferManager; +import com.oracle.coherence.common.internal.io.SegmentedBufferManager; +import com.oracle.coherence.common.internal.io.CheckedBufferManager; +import com.oracle.coherence.common.internal.io.SlabBufferManager; +import com.oracle.coherence.common.internal.io.WrapperBufferManager; +import com.oracle.coherence.common.util.MemorySize; + +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * BufferManagers provides access to pre-defined system-wide managers. + *

+ * The default size of each of the pools may be specified via the com.oracle.common.io.BufferManagers.pool + * system property. Additionally com.oracle.common.io.BufferManagers.checked can be used to default all + * managers to utilize checked implementations to watch for pool usage issues. + *

+ * + * @author mf 2010.12.02 + */ +public final class BufferManagers + { + /** + * Return the heap ByteBuffer based BufferManager. + *

+ * The maximum size of this buffer manager pool may be specified via the + * com.oracle.common.io.BufferManagers.heap.pool system property. Setting this + * value to 0 results in a non-pooled implementation. The default + * value is a small percentage of the JVM's total heap size. + *

+ * For pooled implementations setting com.oracle.common.io.BufferManagers.heap.checked + * to true will provide pool which provides more stringent checks + * in an attempt to ensure that the application doesn't misuse the pool, + * for instance by double releasing a buffer. The default value is false. + * + * @return the heap BufferManager + */ + public static BufferManager getHeapManager() + { + return HeapManagerHolder.INSTANCE; + } + + /** + * Return the direct ByteBuffer based BufferManager. + *

+ * The maximum size of this buffer manager pool may be specified via the + * com.oracle.common.io.BufferManagers.direct.pool system property. Setting this + * value to 0 results in a non-pooled implementation. The default + * value is a small percentage of the JVM's total heap size. + *

+ * For pooled implementations setting com.oracle.common.io.BufferManagers.direct.checked + * to true will provide pool which provides more stringent checks + * in an attempt to ensure that the application doesn't misuse the pool, + * for instance by double releasing a buffer. The default value is false. + * + * @return the direct BufferManager + */ + public static BufferManager getDirectManager() + { + return DirectManagerHolder.INSTANCE; + } + + /** + * Return the network optimized direct ByteBuffer based BufferManager. + *

+ * Compared with the {@link #getDirectManager DirectManager} this implementation + * may provide buffers which are more optimal for use in network operations. + * On some platforms this method may simply return the DirectManager. + *

+ * The maximum size of this buffer manager pool may be specified via the + * com.oracle.common.io.BufferManagers.network.pool system property. Setting this + * value to 0 results in a non-pooled implementation. The default + * value is a small percentage of the JVM's total heap size. + *

+ * For pooled implementations setting com.oracle.common.io.BufferManagers.network.checked + * to true will provide pool which provides more stringent checks + * in an attempt to ensure that the application doesn't misuse the pool, + * for instance by double releasing a buffer. The default value is false. + * + * @return the network direct BufferManager + */ + public static BufferManager getNetworkDirectManager() + { + return NetworkDirectManagerHolder.INSTANCE; + } + + /** + * Holder for the heap based BufferManager. + */ + private static class HeapManagerHolder + { + /** + * The heap based BufferManager. + */ + public static final BufferManager INSTANCE; + + + static + { + BufferManager mgr; + String sHeap = System.getProperty(BufferManagers.class.getName() + + ".heap.pool", System.getProperty(BufferManagers.class.getName() + + ".pool", Long.toString(DEFAULT_POOL_SIZE))); + int cbBuf = (int) new MemorySize(System.getProperty(BufferManagers.class.getName() + ".heap.base", + System.getProperty(BufferManagers.class.getName() + ".base", + Long.toString(SegmentedBufferManager.DEFAULT_BUF_SIZE)))).getByteCount(); + + if (sHeap.startsWith("-")) // negative implies infinite which maps + // to the WeakBufferManager + { + //TODO initialize the WeakSegmentedBufferManager here + throw new IllegalStateException("Negative heap pool size"); + } + else // sHeap is positive + { + long lcbPool = new MemorySize(sHeap).getByteCount(); + if (lcbPool > 0L) + { + SegmentedBufferManager mgrSeg = new SegmentedBufferManager(new SegmentedBufferManager.BufferAllocator() + { + public ByteBuffer allocate(int cb) + { + return ByteBuffer.allocate(cb); + } + + public void release(ByteBuffer buff) + { + if (buff.isDirect()) + { + throw new IllegalArgumentException(); + } + // nothing to do + } + + public String toString() + { + return "HeapBufferAllocator"; + } + }, cbBuf, lcbPool); + + mgrSeg.setName("HeapBufferManager"); + mgr = mgrSeg; + } + else // lcbPool == 0 + { + mgr = new AbstractBufferManager() + { + public ByteBuffer acquire(int cb) + { + return ByteBuffer.allocate(cb); + } + + public void dispose() + { + // no-op; top level manager + } + + protected void ensureCompatibility(ByteBuffer buff) + { + if (buff.isDirect()) + { + throw new IllegalArgumentException(); + } + } + }; + } + } + + mgr = new NonDisposableBufferManager(mgr); + LOGGER.log(Level.FINE, "initialized HeapBufferManager " + mgr); + INSTANCE = Boolean.valueOf(System.getProperty(BufferManagers.class.getName() + ".heap.checked", + System.getProperty(BufferManagers.class.getName() + ".checked"))) + ? new CheckedBufferManager(mgr) : mgr; + } + } + + /** + * Holder for the direct BufferManager. + */ + private static class DirectManagerHolder + { + /** + * The direct BufferManager. + */ + public static final BufferManager INSTANCE; + + static + { + BufferManager mgr; + long lcbPool = new MemorySize(System.getProperty(BufferManagers.class.getName() + ".direct.pool", + System.getProperty(BufferManagers.class.getName() + ".pool", + Long.toString(DEFAULT_POOL_SIZE)))).getByteCount(); // default is doced above + int cbBuf = (int) new MemorySize(System.getProperty(BufferManagers.class.getName() + ".direct.base", + System.getProperty(BufferManagers.class.getName() + ".base", + Long.toString(SegmentedBufferManager.DEFAULT_BUF_SIZE)))).getByteCount(); + + if (lcbPool > 0L) + { + // for DirectByteBuffers we use a slab based buffer manager as DBB allocations are page aligned and thus + // consume significantly more memory then is requested. + SlabBufferManager mgrSlab = new SlabBufferManager( + new SlabBufferManager.DirectBufferAllocator(), cbBuf, lcbPool); + + mgrSlab.setName("DirectBufferManager"); + mgr = mgrSlab; + } + else // lcbPool == 0 + { + mgr = new AbstractBufferManager() + { + public ByteBuffer acquire(int cb) + { + return ByteBuffer.allocateDirect(cb); + } + + public void dispose() + { + // no-op; top level manager + } + + protected void ensureCompatibility(ByteBuffer buff) + { + if (!buff.isDirect()) + { + throw new IllegalArgumentException(); + } + } + }; + } + + mgr = new NonDisposableBufferManager(mgr); + LOGGER.log(Level.FINE, "initialized DirectBufferManager " + mgr); + INSTANCE = Boolean.valueOf(System.getProperty(BufferManagers.class.getName() + ".direct.checked", + System.getProperty(BufferManagers.class.getName() + ".checked"))) + ? new CheckedBufferManager(mgr) : mgr; + } + } + + /** + * Holder for the network direct BufferManager. + */ + private static class NetworkDirectManagerHolder + { + /** + * The network direct BufferManager. + */ + public static final BufferManager INSTANCE = DirectManagerHolder.INSTANCE; + } + + /** + * A {@link WrapperBufferManager} which cannot be disposed. + */ + private static class NonDisposableBufferManager + extends WrapperBufferManager + { + /** + * Create a new NonDisposableBufferManager wrapping the passed + * BufferManager. + * + * @param delegate the BufferManager to wrap + */ + public NonDisposableBufferManager(BufferManager delegate) + { + super(delegate); + } + + @Override + public void dispose() + { + throw new UnsupportedOperationException(); + } + } + + /** + * The default amount of memory for each pool. + * + * The goal is to select a size which won't consume too much of the total java heap. We target a limit + * which is 5% of the max heap size -Xmx, but also defend against configurations which may not have specified + * -Xmx in which case we limit ourselves to decent portion of the initial heap size, since the heap has no + * apparent upper bound this is also a small portion. + */ + private static final long DEFAULT_POOL_SIZE = Math.min(/*~Xms*/ Runtime.getRuntime().totalMemory() / 4, + /* Xmx*/Runtime.getRuntime().maxMemory() / 20); + + /** + * The Logger for the managers. + */ + private static final Logger LOGGER = Logger.getLogger(BufferManagers.class.getName()); + + /** + * Indicates if buffers should have their contents zeroed out upon release. + * + * There is a performance impact to enabling this, it effectively negates the benefits of zcopy + */ + public static final boolean ZERO_ON_RELEASE = Boolean.getBoolean( + BufferManagers.class.getName() + ".zeroed"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferSequence.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferSequence.java new file mode 100644 index 0000000000000..21ca6165cc4c8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferSequence.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import com.oracle.coherence.common.base.Disposable; + +import java.nio.ByteBuffer; + + +/** + * BufferSequence represents a series of ByteBuffers. + *

+ * Positional changes to a returned ByteBuffer will not change the sequence, + * whereas data changes made to a {@link ByteBuffer#isReadOnly mutable} + * ByteBuffer will change the sequence. The result of these requirements dictates + * that a BufferSequence not return the same ByteBuffer instance more then + * once for a given index. If an index is requested more than once then + * an equivalent ByteBuffer must be returned, in the case of mutable buffers + * this implies that a {@link ByteBuffer#duplicate duplicate} is returned. + * + * @author mf/gg/cp 2010.10.13 + */ +public interface BufferSequence + extends Disposable + { + /** + * Return the byte length of the sequence. + * + * @return the byte length of the sequence + */ + public long getLength(); + + /** + * Return the number of ByteBuffers contained in the sequence. + * + * @return the number of ByteBuffers contained in the sequence + */ + public int getBufferCount(); + + /** + * Return the ByteBuffer for a given index. + * + * @param iBuffer the zero based offset into the sequence + * + * @return a {@link ByteBuffer#duplicate duplicate} of the ByteBuffer at + * the specified index + * + * @throws IndexOutOfBoundsException if iBuffer is not in + * [0 .. {@link #getLength}) + */ + public ByteBuffer getBuffer(int iBuffer); + + /** + * Return an unsafe ByteBuffer for a given index. + *

+ * The positional attributes of the returned buffer may or may not reflect + * the proper ones for the given buffer. The caller must not modify these + * attributes, and must be prepared for them to be changed. The correct + * values can be obtained via {@link #getBufferPosition} and {@link #getBufferLimit(int)}. + * + * @param iBuffer the zero based offset into the sequence + * + * @return an unsafe ByteBuffer at the specified index + * + * @throws IndexOutOfBoundsException if iBuffer is not in + * [0 .. {@link #getLength}) + */ + public ByteBuffer getUnsafeBuffer(int iBuffer); + + /** + * Return the length of the ByteBuffer at a given index. + * + * @param iBuffer the zero based offset into the sequence + * + * @return the buffer length + * + * @throws IndexOutOfBoundsException if iBuffer is not in + * [0 .. {@link #getLength}) + */ + public int getBufferLength(int iBuffer); + + /** + * Return the position of the ByteBuffer at a given index. + * + * @param iBuffer the zero based offset into the sequence + * + * @return the buffer position + * + * @throws IndexOutOfBoundsException if iBuffer is not in + * [0 .. {@link #getLength}) + */ + public int getBufferPosition(int iBuffer); + + /** + * Return the limit of the ByteBuffer at a given index. + * + * @param iBuffer the zero based offset into the sequence + * + * @return the buffer limit + * + * @throws IndexOutOfBoundsException if iBuffer is not in + * [0 .. {@link #getLength}) + */ + public int getBufferLimit(int iBuffer); + + /** + * Return an array of ByteBuffers representing the sequence. + * + * @return a new array of ByteBuffer {@link ByteBuffer#duplicate duplicates} + * representing the sequence + */ + public ByteBuffer[] getBuffers(); + + /** + * Copy ByteBuffer {@link ByteBuffer#duplicate duplicates} into the + * supplied array. + * + * @param iBuffer the index within the sequence at which to start copying + * @param cBuffers the number of buffers to copy + * @param abufDest the destination array + * @param iDest the start index in the destination array + * + * @throws IndexOutOfBoundsException if either + * [iBuffer .. iBuffer+cBuffers) is outside of the + * sequence range, or if [iDest .. iDest+cBuffers) + * is outside of the destination array range + */ + public void getBuffers(int iBuffer, int cBuffers, ByteBuffer[] abufDest, int iDest); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferSequenceInputStream.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferSequenceInputStream.java new file mode 100644 index 0000000000000..f49bd93feaa3f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferSequenceInputStream.java @@ -0,0 +1,738 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import java.io.InputStream; +import java.io.IOException; +import java.io.DataInput; +import java.io.EOFException; +import java.io.DataInputStream; +import java.io.UTFDataFormatException; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + + +/** +* An InputStream implementation on top of a BufferSequence. +* +* @author cp/mf 2010.12.08 +*/ +public class BufferSequenceInputStream + extends InputStream + implements DataInput + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a BufferSequenceInputStream over a BufferSequence object. + * + * If it is desired for the BufferSequence to be disposed of when the stream + * is closed then {@link #BufferSequenceInputStream(BufferSequence, boolean)} + * should be used. + * + * @param bufseq the BufferSequence to read the data from + */ + public BufferSequenceInputStream(BufferSequence bufseq) + { + this (bufseq, false); + } + + /** + * Construct a BufferSequenceInputStream over a BufferSequence object. + * + * @param bufseq the BufferSequence to read the data from + * @param fAutoDispose true if the sequence should be disposed of when the stream is closed, + * or an unmarked stream is fully consumed + */ + public BufferSequenceInputStream(BufferSequence bufseq, boolean fAutoDispose) + { + m_bufseq = bufseq; + m_cb = bufseq.getLength(); + m_cbMark = m_cb; + f_fAutoDispose = fAutoDispose; + } + + // ----- BufferSequenceInputStream interface ---------------------------- + + /** + * Fill the supplied buffer using the contents of the stream. + * + * @param bufDst the buffer to fill, the position will be updated per {@link ByteBuffer#put} + */ + public void read(ByteBuffer bufDst) + { + for (ByteBuffer bufUnsafe = ensureBuffer(); bufDst.hasRemaining() && bufUnsafe != null; bufUnsafe = ensureBuffer()) + { + int nPosSrc = m_nPosBuf; + int cbCopy = Math.min(m_nLimBuf - nPosSrc, bufDst.remaining()); + + consumeBytes(cbCopy); + Buffers.copy(bufUnsafe, nPosSrc, cbCopy, bufDst); + m_nPosBuf += cbCopy; + } + } + + /** + * Return the current buffer which is being read from. + *

+ * Note that the validity of the returned ByteBuffer is tied to that of the BufferSequence. + * As this is the buffer currently being consumed by the stream any positional changes made + * to this buffer may invalidate the stream, thus in general it should either be used for + * "peeking", or just before closing the stream. + * + * @return the current buffer which is being read from. + */ + public ByteBuffer getCurrentBuffer() + { + ByteBuffer bufUnsafe = m_bufUnsafe; + if (bufUnsafe == null) + { + bufUnsafe = ensureBuffer(); + } + bufUnsafe = bufUnsafe.duplicate(); + bufUnsafe.position(m_nPosBuf).limit(m_nLimBuf); + + return bufUnsafe; + } + + /** + * Reset the stream to operate on a new BufferSequence. + *

+ * This is the equivalent of closing this stream and opening a new one. + *

+ * + * @param bufseq the new sequence to operate against + * + * @return this stream + */ + public BufferSequenceInputStream reset(BufferSequence bufseq) + { + close(); + + m_bufseq = bufseq; + m_cb = bufseq.getLength(); + m_cbMark = m_cb; + m_ofNext = 0; + m_ofMark = 0; + m_posMark = 0; + m_bufUnsafe = Buffers.getEmptyBuffer(); + m_cbLimit = 0; + m_nPosBuf = 0; + m_nLimBuf = 0; + + return this; + } + + + // ----- DataInput interface -------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readFully(byte[] ab) + throws IOException + { + readFully(ab, 0, ab.length); + } + + /** + * {@inheritDoc} + */ + @Override + public void readFully(byte[] ab, int off, int len) + throws IOException + { + if (read(ab, off, len) < len) + { + throw new EOFException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int skipBytes(int n) + throws IOException + { + return (int) skip(n); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean readBoolean() + throws IOException + { + int n = read(); + if (n < 0) + { + throw new EOFException(); + } + return n != 0; + } + + /** + * {@inheritDoc} + */ + @Override + public byte readByte() + throws IOException + { + int n = read(); + if (n < 0) + { + throw new EOFException(); + } + return (byte) n; + } + + /** + * {@inheritDoc} + */ + @Override + public int readUnsignedByte() + throws IOException + { + int n = read(); + if (n < 0) + { + throw new EOFException(); + } + return n; + } + + /** + * {@inheritDoc} + */ + @Override + public short readShort() + throws IOException + { + ByteBuffer buf = ensureBuffer(2); + return buf.getShort(buf == m_bufTmp ? 0 : m_nPosBuf - 2); + } + + /** + * {@inheritDoc} + */ + @Override + public int readUnsignedShort() + throws IOException + { + int ch1 = read(); + int ch2 = read(); + if ((ch1 | ch2) < 0) + throw new EOFException(); + return (ch1 << 8) + ch2; + } + + /** + * {@inheritDoc} + */ + @Override + public char readChar() + throws IOException + { + ByteBuffer buf = ensureBuffer(2); + return buf.getChar(buf == m_bufTmp ? 0 : m_nPosBuf - 2); + } + + /** + * {@inheritDoc} + */ + @Override + public int readInt() + throws IOException + { + ByteBuffer buf = ensureBuffer(4); + return buf.getInt(buf == m_bufTmp ? 0 : m_nPosBuf - 4); + } + + /** + * {@inheritDoc} + */ + @Override + public long readLong() + throws IOException + { + ByteBuffer buf = ensureBuffer(8); + return buf.getLong(buf == m_bufTmp ? 0 : m_nPosBuf - 8); + } + + /** + * {@inheritDoc} + */ + @Override + public float readFloat() + throws IOException + { + ByteBuffer buf = ensureBuffer(4); + return buf.getFloat(buf == m_bufTmp ? 0 : m_nPosBuf - 4); + } + + /** + * {@inheritDoc} + */ + @Override + public double readDouble() + throws IOException + { + ByteBuffer buf = ensureBuffer(8); + return buf.getDouble(buf == m_bufTmp ? 0 : m_nPosBuf - 8); + } + + /** + * The readLine functionality was {@link DataInputStream#readLine + * deprecated} as of JDK 1.1. This implementation will always throw an + * UnsupportedOperationException. + * + * @throws UnsupportedOperationException always + */ + @Override + @Deprecated + public String readLine() + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public String readUTF() + throws IOException + { + // Note: implementation borrowed from java.io.DataInputStream + + int utflen = readUnsignedShort(); + byte[] bytearr = new byte[utflen]; + char[] chararr = new char[utflen]; + + int c, char2, char3; + int count = 0; + int chararr_count = 0; + + readFully(bytearr, 0, utflen); + + while (count < utflen) + { + c = (int) bytearr[count] & 0xff; + if (c > 127) + { + break; + } + count++; + chararr[chararr_count++] = (char) c; + } + + while (count < utflen) + { + c = (int) bytearr[count] & 0xff; + switch (c >> 4) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + /* 0xxxxxxx*/ + count++; + chararr[chararr_count++] = (char) c; + break; + case 12: + case 13: + /* 110x xxxx 10xx xxxx*/ + count += 2; + if (count > utflen) + { + throw new UTFDataFormatException( + "malformed input: partial character at end"); + } + char2 = (int) bytearr[count - 1]; + if ((char2 & 0xC0) != 0x80) + { + throw new UTFDataFormatException( + "malformed input around byte " + count); + } + chararr[chararr_count++] = (char) (((c & 0x1F) << 6) | + (char2 & 0x3F)); + break; + case 14: + /* 1110 xxxx 10xx xxxx 10xx xxxx */ + count += 3; + if (count > utflen) + { + throw new UTFDataFormatException( + "malformed input: partial character at end"); + } + char2 = (int) bytearr[count - 2]; + char3 = (int) bytearr[count - 1]; + if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) + { + throw new UTFDataFormatException( + "malformed input around byte " + (count - 1)); + } + chararr[chararr_count++] = (char) (((c & 0x0F) << 12) | + ((char2 & 0x3F) << 6) | (char3 & 0x3F)); + break; + default: + /* 10xx xxxx, 1111 xxxx */ + throw new UTFDataFormatException( + "malformed input around byte " + count); + } + } + // The number of chars produced may be less than utflen + return new String(chararr, 0, chararr_count); + } + + + // ----- InputStream implementation ------------------------------------- + + /** + * {@inheritDoc} + */ + public int read() + throws IOException + { + ByteBuffer buf = ensureBuffer(); + if (m_nPosBuf < m_nLimBuf) + { + consumeBytes(1); + return ((int) buf.get(m_nPosBuf++)) & 0xFF; + } + + return -1; // for read() and read(byte[]) we don't throw + } + + /** + * {@inheritDoc} + */ + public int read(byte abDest[], int ofDest, int cbDest) + throws IOException + { + if (abDest == null || ofDest < 0 || cbDest < 0 || ofDest + cbDest > abDest.length) + { + if (abDest == null) + { + throw new IllegalArgumentException("null byte array"); + } + else + { + throw new IllegalArgumentException( + "abDest.length=" + abDest.length + ", ofDest=" + ofDest + ", cbDest=" + cbDest); + } + } + + int cbResult = 0; + do + { + ByteBuffer bufDupe = ensureBuffer().duplicate(); // duplicate so we can use bulk-get below + bufDupe.position(m_nPosBuf).limit(m_nLimBuf); + + int posStart = bufDupe.position(); + bufDupe.get(abDest, ofDest, Math.min(cbDest, bufDupe.remaining())); + + int cbRead = bufDupe.position() - posStart; + if (cbRead == 0 && m_cb == 0) + { + break; // EOS + } + + consumeBytes(cbRead); // must be in loop to allow for above check m_cb check to work + + m_nPosBuf += cbRead; + cbResult += cbRead; + ofDest += cbRead; + cbDest -= cbRead; + } + while (cbDest > 0); + + return cbResult == 0 && m_cb == 0 ? -1 : cbResult; + } + + /** + * {@inheritDoc} + */ + public long skip(long lcb) + throws IOException + { + if (lcb <= 0) + { + return 0; + } + + int cb = (int) Math.min(Integer.MAX_VALUE, lcb); + int cbResult = 0; + while (cb > 0) + { + ensureBuffer(); // we don't actually use the buffer, but this advances it if empty + int cbSkip = Math.min(cb, m_nLimBuf - m_nPosBuf); + + if (cbSkip == 0 && m_cb == 0) + { + break; + } + + consumeBytes(cbSkip); // must be in loop for above m_cb check + + m_nPosBuf += cbSkip; + cb -= cbSkip; + cbResult += cbSkip; + } + + return cbResult; + } + + /** + * {@inheritDoc} + */ + public int available() + throws IOException + { + return (int) Math.min(Integer.MAX_VALUE, m_cb); // 0 on EOS + } + + /** + * {@inheritDoc} + */ + public void mark(int readlimit) + { + ensureBuffer(); + m_cbLimit = readlimit; + m_cbMark = m_cb; + m_ofMark = m_ofNext - 1; + m_posMark = m_nPosBuf; + } + + /** + * {@inheritDoc} + */ + public void reset() + throws IOException + { + long cbMark = m_cbMark; + BufferSequence bufseq = m_bufseq; + long cb = m_cb; + long cbBack = cbMark - cb; + + if (cbBack == 0) + { + // noop, we only do this check in case we are doing a mark/reset on an auto-closed stream + // we want to avoid it looking any different then having done it on an EOS + return; + } + else if (cbBack > m_cbLimit) + { + // Note we could just treat the limit as a hint, but since we can auto-dispose from it we treat it + // as a true limit in order to provide consistent behavior + throw new IOException("Invalid mark, limit exceeded"); + } + else if (bufseq == null) // explicitly closed stream + { + throw new IOException("Closed stream"); + } + + int of = m_ofMark; + m_bufUnsafe = bufseq.getUnsafeBuffer(of); + m_nLimBuf = bufseq.getBufferLimit(of); + m_nPosBuf = m_posMark; + m_ofNext = of + 1; + m_cb = cbMark; + } + + /** + * {@inheritDoc} + */ + public boolean markSupported() + { + return true; + } + + /** + * {@inheritDoc} + */ + public void close() + { + BufferSequence bufSeq = m_bufseq; + + m_bufUnsafe = null; + m_bufseq = null; + m_bufTmp = null; + m_cb = 0; // for subsequent read(byte[]) calls + m_nLimBuf = 0; + m_nPosBuf = 0; + + if (bufSeq != null && f_fAutoDispose) + { + bufSeq.dispose(); + } + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Consume the specified number of bytes. + * + * @param cb the number of bytes consumed. + */ + protected final void consumeBytes(int cb) + { + if ((m_cb -= cb) == 0 && f_fAutoDispose && m_cbMark > m_cbLimit) + { + close(); + } + } + + /** + * Obtain the next ByteBuffer that this InputStream is based on. + * + * The returned buffer will be big endian, and the caller must not modify the buffers positional properties, and use/update m_nPosBuf and m_nLimBuf instead. + * + * @return the underlying ByteBuffer + */ + protected final ByteBuffer ensureBuffer() + { + BufferSequence bufseq = m_bufseq; + if (bufseq == null) + { + m_nPosBuf = m_nLimBuf = 0; + return Buffers.getEmptyBuffer(); + } + + ByteBuffer bufUnsafe = m_bufUnsafe; + int of = m_ofNext; + int cBuf = bufseq.getBufferCount(); + while (m_nPosBuf == m_nLimBuf && of < cBuf) + { + bufUnsafe = bufseq.getUnsafeBuffer(of); + m_bufUnsafe = bufUnsafe.order() == ByteOrder.BIG_ENDIAN ? bufUnsafe : bufUnsafe.duplicate().order(ByteOrder.BIG_ENDIAN); + m_nPosBuf = bufseq.getBufferPosition(of); + m_nLimBuf = bufseq.getBufferLimit(of); + ++of; + } + m_ofNext = of; + + return bufUnsafe; + } + + + /** + * Helper function which returns a buffer containing at least the specified number of readable bytes which + * will have already been logically consumed from the stream. + * + * If the returned buffer == m_bufTmp then a read may be performed from position 0, otherwise the read + * must be at location m_nPos - cb. + * + * @param cb the number of bytes to read, maximum of 8 + * + * @return the temp buffer + */ + private final ByteBuffer ensureBuffer(int cb) + throws IOException + { + int cbUnsafe = m_nLimBuf - m_nPosBuf; + ByteBuffer bufUnsafe = m_bufUnsafe; + + if (cbUnsafe == 0) + { + bufUnsafe = ensureBuffer(); + cbUnsafe = m_nLimBuf - m_nPosBuf; + } + + if (cbUnsafe >= cb) + { + consumeBytes(cb); + m_nPosBuf += cb; + return bufUnsafe; + } + else + { + ByteBuffer buffTmp = m_bufTmp; + if (buffTmp == null) + { + m_bufTmp = buffTmp = ByteBuffer.allocate(8); + } + + buffTmp.position(0).limit(cb); // will throw if cb > 8 + + readFully(buffTmp.array(), 0, cb); // updates m_nPosBuf + + return buffTmp; + } + } + + // ----- data members --------------------------------------------------- + + /** + * The BufferSequence object from which data is read. + */ + protected BufferSequence m_bufseq; + + /** + * A temporary byte buffer which can be used for decoding purposes. + */ + private ByteBuffer m_bufTmp; + + /** + * The number of bytes remaining in the stream. + */ + protected long m_cb; + + /** + * The offset of the next ByteBuffer to read from. + */ + protected int m_ofNext; + + /** + * The offset of the buffer associated with the mark. + */ + protected int m_ofMark; + + /** + * The buffer position of the buffer associated with the mark. + */ + protected int m_posMark; + + /** + * The number of bytes remaining in the stream after the mark. + */ + protected long m_cbMark; + + /** + * The limit associated with the mark. + */ + protected int m_cbLimit; + + /** + * True if the sequence should be disposed of when the stream is closed. + */ + protected final boolean f_fAutoDispose; + + /** + * The ByteBuffer object from which data is read. + */ + protected ByteBuffer m_bufUnsafe = Buffers.getEmptyBuffer(); + + /** + * The position in the m_bufUnsafe + */ + protected int m_nPosBuf; + + /** + * the limit in m_bufUnsafe; + */ + protected int m_nLimBuf; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferSequenceOutputStream.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferSequenceOutputStream.java new file mode 100644 index 0000000000000..004995f4b37b0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/BufferSequenceOutputStream.java @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import java.io.OutputStream; +import java.io.IOException; +import java.io.DataOutput; +import java.io.UTFDataFormatException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.List; +import java.util.ArrayList; + + +/** + * BufferSequenceOutputStream is an implementation of an OutputStream which + * which produces a BufferSequence. + * + * @author mf 2010.12.08 + */ +public class BufferSequenceOutputStream + extends OutputStream + implements DataOutput + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a BufferSequenceOutputStream. + * + * @param manager the BufferManager to acquire buffers from + */ + public BufferSequenceOutputStream(BufferManager manager) + { + this (manager, 0); + } + + /** + * Construct a BufferSequenceOutputStream. + * + * @param manager the BufferManager to acquire buffers from + * @param cb the anticipated sequence size, or zero + */ + public BufferSequenceOutputStream(BufferManager manager, long cb) + { + if (manager == null) + { + throw new IllegalArgumentException("manager cannot be null"); + } + + m_manager = manager; + if (cb > 0) + { + int ncb = (int) cb; + m_buffer = (ByteBuffer) manager.acquirePref(ncb < 0 ? Integer.MAX_VALUE : ncb).clear(); + } + } + + + // ----- BufferSequenceOutputStream interface --------------------------- + + /** + * Write the specified buffer to the stream. + *

+ * The positional properties of the buffer will be modified, and thus at + * the completion of the operation src.remaining() will be zero. + *

+ * + * @param buf the buffer to write + * + * @throws IOException if an I/O error occurs + */ + public void writeBuffer(ByteBuffer buf) + throws IOException + { + writeBuffer(buf, 0); + } + + /** + * Write the specified BufferSequence to the stream. + * + * @param bufseq the BufferSequence to write + * + * @throws IOException if an I/O error occurs + */ + public void writeBufferSequence(BufferSequence bufseq) + throws IOException + { + long cbHint = bufseq.getLength(); + for (int i = 0, c = bufseq.getBufferCount(); i < c; ++i) + { + cbHint = writeBuffer(bufseq.getBuffer(i), cbHint); + } + } + + /** + * Close the stream and return its contents as a BufferSequence. + *

+ * It is the responsibility of the caller to eventually {@link + * BufferSequence#dispose dispose} of the returned BufferSequence. + * + * @return the BufferSequence + * + * @throws IOException if an I/O error occurs + */ + public BufferSequence toBufferSequence() + throws IOException + { + BufferManager manager = m_manager; + List listBuffers = m_listBuffers; + ByteBuffer bufLast = m_buffer; + + flush(); + + m_listBuffers = null; + m_buffer = null; + m_manager = null; + + if (listBuffers == null) + { + // never flushed; avoid creating an unnecessary List + if (bufLast == null || bufLast.position() == 0) + { + if (bufLast != null) + { + manager.release(bufLast); + } + return Buffers.getEmptyBufferSequence(); + } + bufLast.flip(); + return new SingleBufferSequence(manager, manager.truncate(bufLast)); + } + else + { + bufLast.flip(); + listBuffers.add(manager.truncate(bufLast)); + return Buffers.createBufferSequence(manager, listBuffers.toArray(new ByteBuffer[listBuffers.size()])); + } + } + + + // ----- DataOutput interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void writeBoolean(boolean v) + throws IOException + { + write(v ? 1 : 0); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeByte(int v) + throws IOException + { + write(v); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeShort(int v) + throws IOException + { + flush(ensureBuffer(Short.SIZE / 8).putShort((short) v)); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeChar(int v) + throws IOException + { + flush(ensureBuffer(Character.SIZE / 8).putChar((char) v)); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeInt(int v) + throws IOException + { + flush(ensureBuffer(Integer.SIZE / 8).putInt(v)); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeLong(long v) + throws IOException + { + flush(ensureBuffer(Long.SIZE / 8).putLong(v)); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeFloat(float v) + throws IOException + { + flush(ensureBuffer(Float.SIZE / 8).putFloat(v)); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeDouble(double v) + throws IOException + { + flush(ensureBuffer(Double.SIZE / 8).putDouble(v)); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeBytes(String s) + throws IOException + { + int i = 0; + int c = s.length(); + + // optimize by writing in chunks, note BSOS is always big endian + while (i <= c - 8) + { + long lch; + lch = ((long) (0x0FF & s.charAt(i++)) << 56); + lch |= ((long) (0x0FF & s.charAt(i++)) << 48); + lch |= ((long) (0x0FF & s.charAt(i++)) << 40); + lch |= ((long) (0x0FF & s.charAt(i++)) << 32); + lch |= ((long) (0x0FF & s.charAt(i++)) << 24); + lch |= ((long) (0x0FF & s.charAt(i++)) << 16); + lch |= ((long) (0x0FF & s.charAt(i++)) << 8); + lch |= (0x0FF & s.charAt(i++)); + + writeLong(lch); + } + + if (i <= c - 4) + { + int nch; + nch = ((0x0FF & s.charAt(i++)) << 24); + nch |= ((0x0FF & s.charAt(i++)) << 16); + nch |= ((0x0FF & s.charAt(i++)) << 8); + nch |= (0x0FF & s.charAt(i++)); + + writeInt(nch); + } + + if (i <= c - 2) + { + int nch; + nch = ((0x0FF & s.charAt(i++)) << 8); + nch |= (0x0FF & s.charAt(i++)); + + writeShort(nch); + } + + if (i < c) + { + writeByte(s.charAt(i)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void writeChars(String s) + throws IOException + { + int i = 0; + int c = s.length(); + + // optimize by writing in chunks, note BSOS is always big endian + while (i <= c - 4) + { + long lch; + lch = ((long) (0x0FFFF & s.charAt(i++)) << 48); + lch |= ((long) (0x0FFFF & s.charAt(i++)) << 32); + lch |= ((long) (0x0FFFF & s.charAt(i++)) << 16); + lch |= (0x0FFFF & s.charAt(i++)); + + writeLong(lch); + } + + if (i <= c - 2) + { + int nch; + nch = ((0x0FFFF & s.charAt(i++)) << 16); + nch |= (0x0FFFF & s.charAt(i++)); + + writeInt(nch); + } + + if (i < c) + { + writeChar(s.charAt(i)); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void writeUTF(String str) + throws IOException + { + // Note: implementation borrowed from java.io.DataOutputStream + int strlen = str.length(); + int utflen = 0; + int c, count = 0; + + /* use charAt instead of copying String to char array */ + for (int i = 0; i < strlen; i++) + { + c = str.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) + { + utflen++; + } + else if (c > 0x07FF) + { + utflen += 3; + } + else + { + utflen += 2; + } + } + + if (utflen > 65535) + { + throw new UTFDataFormatException( + "encoded string too long: " + utflen + " bytes"); + } + + byte[] bytearr = new byte[utflen+2]; + + bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF); + bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF); + + int i; + for (i = 0; i < strlen; i++) + { + c = str.charAt(i); + if (!((c >= 0x0001) && (c <= 0x007F))) + { + break; + } + bytearr[count++] = (byte) c; + } + + for ( ; i < strlen; i++) + { + c = str.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) + { + bytearr[count++] = (byte) c; + } + else if (c > 0x07FF) + { + bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); + bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F)); + bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } + else + { + bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); + bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); + } + } + + write(bytearr, 0, utflen+2); + } + + + // ----- OutputStream interface ----------------------------------------- + + /** + * {@inheritDoc} + */ + public void write(int b) + throws IOException + { + ensureBuffer().put((byte) b); + } + + /** + * {@inheritDoc} + */ + @Override + public void write(byte[] ab, int of, int cb) + throws IOException + { + if (ab == null || of < 0 || cb < 0 || of + cb > ab.length) + { + if (ab == null) + { + throw new IllegalArgumentException("null byte array"); + } + else + { + throw new IllegalArgumentException( + "ab.length=" + ab.length + + ", of=" + of + ", cb=" + cb); + } + } + + while (cb > 0) + { + ByteBuffer buff = ensureSpace(cb); + int cbCopy = Math.min(buff.remaining(), cb); + + buff.put(ab, of, cbCopy); + cb -= cbCopy; + of += cbCopy; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void flush() + throws IOException + { + if (m_manager == null) + { + throw new IOException("stream closed"); + } + + // no-op + } + + /** + * {@inheritDoc} + */ + @Override + public void close() + { + if (m_manager != null) + { + try + { + toBufferSequence().dispose(); + } + catch (IOException e) + { + // already closed + } + } + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Write the specified ByteBuffer to the stream. + * + * @param buf the buffer to write out + * @param cbHint an optional hint as to how big to resize the stream's buffer if necessary + * + * @return the updated hint size after having written the buffer + * + * @throws IOException if an I/O error occurs + */ + private long writeBuffer(ByteBuffer buf, long cbHint) + throws IOException + { + ByteBuffer bufDst = ensureSpace(cbHint); + int nLimit = buf.limit(); + int cb = buf.remaining(); + + cbHint = Math.max(cb, cbHint); + while (cb > bufDst.remaining()) + { + int cbDst = bufDst.remaining(); + + buf.limit(buf.position() + cbDst); + bufDst.put(buf); + buf.limit(nLimit); + + cb -= cbDst; + cbHint -= cbDst; + bufDst = ensureSpace(cbHint); + } + + bufDst.put(buf); + return cbHint - cb; + } + + /** + * Return the current ByteBuffer with some remaining capacity. + * + * @return the current ByteBuffer. + * + * @throws IOException on I/O error + */ + protected final ByteBuffer ensureBuffer() + throws IOException + { + return ensureSpace(1); + } + + /** + * Return the current ByteBuffer with some remaining capacity. + * + * @param cbHint a hint as to how much additional space is required + * + * @return the current ByteBuffer. + * + * @throws IOException on I/O error + */ + protected final ByteBuffer ensureSpace(long cbHint) + throws IOException + { + BufferManager manager = m_manager; + if (manager == null) + { + throw new IOException("stream closed"); + } + + ByteBuffer buffer = m_buffer; + if (buffer != null && !buffer.hasRemaining()) + { + buffer.flip(); + + m_cb += buffer.remaining(); + ensureBufferList().add(buffer); + buffer = null; + } + + if (buffer == null) + { + m_buffer = buffer = manager.acquireSum((int) Math.min( + Integer.MAX_VALUE, Math.max(m_cb, cbHint))); + } + + return buffer; + } + + /** + * Return a big-endian ByteBuffer of at least the specified size. + * + * The caller must "flush" any writes to the buffer via a call to {@link #flush(java.nio.ByteBuffer)}. + * + * @param cb the required byte size, maximum value of 8 + * + * @return the temp buffer + * + * @throws IOException if an IO error occurs + */ + protected final ByteBuffer ensureBuffer(int cb) + throws IOException + { + ByteBuffer buff = ensureSpace(/*cbHint*/ cb); + if (buff.remaining() >= cb && buff.order() == ByteOrder.BIG_ENDIAN) + { + return buff; + } + else + { + ByteBuffer buffTmp = m_buffTmp; + if (buffTmp == null) + { + m_buffTmp = buffTmp = ByteBuffer.allocate(8); + } + return buffTmp; + } + } + + /** + * Write the contents of the temp buffer to the stream. + * + * @param buff the temp buffer to flush + * + * @throws IOException if an IO error occurs + */ + protected final void flush(ByteBuffer buff) + throws IOException + { + if (buff == m_buffTmp) + { + write(buff.array(), 0, buff.position()); + buff.position(0); + } + // else; otherwise the buffer is part of the stream and doesn't need flushing + } + + /** + * Return the buffer list, creating it if necessary. + * + * @return the buffer list + */ + protected List ensureBufferList() + { + List listBuffers = m_listBuffers; + if (listBuffers == null) + { + m_listBuffers = listBuffers = new ArrayList(); + } + return listBuffers; + } + + + // ----- data members --------------------------------------------------- + + /** + * The BufferManager to use in producing the sequence, or null if closed. + */ + protected BufferManager m_manager; + + /** + * The current "unflushed" buffer. + */ + protected ByteBuffer m_buffer; + + /** + * A temporary byte buffer which can be used for encoding purposes. + */ + protected ByteBuffer m_buffTmp; + + /** + * The sequence length. + */ + protected long m_cb; + + /** + * The list of flushed buffers. + */ + protected List m_listBuffers; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/Buffers.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/Buffers.java new file mode 100644 index 0000000000000..fc772494854c9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/Buffers.java @@ -0,0 +1,996 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import com.oracle.coherence.common.net.exabus.MemoryBus; +import com.oracle.coherence.common.base.Collector; + + +import java.nio.ByteBuffer; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; +import java.util.LinkedList; +import java.util.List; + +import java.util.concurrent.atomic.AtomicLong; + +import java.util.zip.CRC32; + + +/** + * Buffers contains a number of Buffer related helpers. + * + * @author mf 2010.12.08 + */ +public class Buffers + { + /** + * Return an empty heap based ByteBuffer. + * + * @return an empty heap based ByteBuffer + */ + public static ByteBuffer getEmptyBuffer() + { + return EmptyBuffer.INSTANCE; + } + + /** + * Return an empty direct ByteBuffer. + * + * @return an empty direct ByteBuffer + */ + public static ByteBuffer getEmptyDirectBuffer() + { + return EmptyDirectBuffer.INSTANCE; + } + + /** + * Return the empty ByteBuffer array singleton. + * + * @return the empty ByteBuffer array singleton + */ + public static ByteBuffer[] getEmptyBufferArray() + { + return EmptyBufferArray.INSTANCE; + } + + /** + * Return the empty BufferSequence singleton. + * + * @return the empty BufferSequence singleton + */ + public static BufferSequence getEmptyBufferSequence() + { + return EmptySequenceHolder.INSTANCE; + } + + /** + * Allocate a direct ByteBuffer based BufferSequence of the specified size. + *

+ * Note: The BufferSequence will be word aligned as required by + * {@link MemoryBus#setBufferSequence(BufferSequence)} + *

+ * + * @param mgr the buffer manager to acquire from, or null to allocate via {@link ByteBuffer#allocateDirect(int)} + * @param cb the required size + * + * @return the BufferSequence + */ + public static BufferSequence allocateDirect(BufferManager mgr, long cb) + { + if (cb == 0) + { + return new SingleBufferSequence(null, Buffers.getEmptyDirectBuffer()); + } + + final int cbMaxAlligned = Integer.MAX_VALUE - 7; + int cbAttempt = cb > cbMaxAlligned ? cbMaxAlligned : (int) cb; + List listBuffers = null; + ByteBuffer buffer = null; + for (long ofLocal = 0; ofLocal < cb; ) + { + try + { + if (mgr == null) + { + buffer = ByteBuffer.allocateDirect(cbAttempt); + } + else + { + buffer = mgr.acquirePref(cbAttempt); + cbAttempt = buffer.remaining(); + + if (!buffer.isDirect()) + { + mgr.release(buffer); + if (listBuffers != null) + { + for (ByteBuffer bufRelease : listBuffers) + { + mgr.release(bufRelease); + } + } + throw new IllegalArgumentException("BufferManager must supply direct ByteBuffers"); + } + + if (ofLocal + cbAttempt < cb) + { + // take full buffer if a larger was returned + if (cbAttempt < buffer.limit()) + { + buffer.limit(buffer.limit()); + cbAttempt = buffer.remaining(); + } + + // ensure word alignment + if (cbAttempt % 8 != 0) + { + buffer.limit(buffer.limit() - (cbAttempt % 8)); + cbAttempt = buffer.remaining(); + } + } + } + + ofLocal += cbAttempt; + cbAttempt = (int) Math.min((long) Math.max(1024 * 1024, cbAttempt), cb - ofLocal); + if (listBuffers == null) + { + if (ofLocal < cb) + { + listBuffers = new LinkedList(); + listBuffers.add(buffer); + } + // else; single BufferSequence + } + else + { + listBuffers.add(buffer); + } + } + catch (OutOfMemoryError e) + { + if (cbAttempt <= 1024) + { + // time to give up + if (listBuffers != null) + { + for (ByteBuffer bufRelease : listBuffers) + { + mgr.release(bufRelease); + } + } + + throw e; + } + cbAttempt /= 2; + if ((cbAttempt % 8) != 0) + { + // for MemoryBus CAS and atomic ADD we must ensure that every block (except the last one) is divisible by 8. + cbAttempt += (8 - (cbAttempt % 8)); + } + } + } + + if (mgr != null) + { + buffer = mgr.truncate(buffer); + if (listBuffers != null) + { + listBuffers.set(listBuffers.size() -1, buffer); + } + } + + return listBuffers == null + ? new SingleBufferSequence(mgr, buffer) + : new MultiBufferSequence(mgr, listBuffers.toArray(new ByteBuffer[listBuffers.size()])); + } + + /** + * Allocate a series of BufferSequences and add them to the specified collector. + * + * @param mgr the buffer manager to acquire from + * @param collBufSeq the collector to add each sequence to, {@link Collector#flush flush} will not be invoked + * @param cSeq the desired number of sequences + * @param cb the size of each BufferSequence + * @param cBufLimit the maximum number of ByteBuffers per sequence + * + * @return the number of allocated sequences, may be larger or smaller then the requested count + */ + public static int allocate(final BufferManager mgr, final Collector collBufSeq, final int cSeq, final int cb, final int cBufLimit) + { + if (cSeq <= 0) + { + return 0; + } + else if (cBufLimit <= 0) + { + throw new IllegalArgumentException("cBufLimit must be > zero"); + } + else if (cb <= 0) + { + throw new IllegalArgumentException("cb must be > zero"); + } + + int cSeqAlloc = 0; + AtomicLong atlShared = null; + ByteBuffer bufShared = null; + final int cbWaste = cb / 64; // tolerate at most this much waste per sequence; waste a little to avoid buffer sharing + BufferSequence bufferSeq = null; + + try + { + // allocate at least the requested number of sequences, but more iff not doing so would be wasteful + // note we may allocate extra sequences, but only as many as will fit in the last allocation, allocating + // more then that can result in a much greater allocation then the user had expected and can hurt performance + for (int i = 0; i < cSeq || (bufShared != null && bufShared.remaining() >= cb); ++i) + { + for (int iBuf = 0, cbReq = cb; cbReq > 0 && iBuf < cBufLimit; ++iBuf) + { + BufferSequence bufSeqNew = null; + if (bufShared == null) + { + ByteBuffer buf = iBuf == cBufLimit - 1 + ? mgr.acquire(cbReq) // last buffer in sequence, get all + : mgr.acquirePref(cbReq); // TODO: consider allocating enough for cSeq? + + if (buf.capacity() - cbReq > cbWaste) + { + // share this new buffer with other sequences + bufShared = buf; + atlShared = new AtomicLong(1); // initial count is for this method + + // fall through to shared; position/limit already set appropriately + } + else + { + // non-shared buffer, mostly only used for intermediate buffers in the sequence + // or for close fit allocations, i.e. avoid cost of sharing when memory savings is minimal + bufSeqNew = new SingleBufferSequence(mgr, buf); + } + } + else // use next (portion) of shared buffer + { + // update position and limit based on cbReq + bufShared.limit(Math.min(bufShared.capacity(), bufShared.position() + cbReq)); + } + + if (bufSeqNew == null) // use shared buffer + { + final ByteBuffer bufDispose = bufShared; + final AtomicLong atlDispose = atlShared; + + if (atlDispose.incrementAndGet() <= 0) + { + throw new IllegalStateException(); + } + + bufSeqNew = new SingleBufferSequence(null, bufDispose.slice()) + { + @Override + public void dispose() + { + super.dispose(); + safeRelease(mgr, bufDispose, atlDispose); + } + }; + + // prep bufShared for next sequence (if possible) + bufShared.position(bufShared.limit()).limit(bufShared.capacity()); + if (bufShared.remaining() == 0 || (cBufLimit == 1 && bufShared.remaining() < cb)) + { + safeRelease(mgr, bufShared, atlShared); + bufShared = null; + atlShared = null; + } + } + + // rather then further complicating this code with MultiBufferSequence we make use of layers + // of CompositeBufferSequences assuming the total depth will be quite low, note that single + // buffer sequences naturally avoid creating a composite + bufferSeq = bufferSeq == null ? bufSeqNew : new CompositeBufferSequence(bufferSeq, bufSeqNew); + cbReq -= bufSeqNew.getLength(); + } + + collBufSeq.add(bufferSeq); + bufferSeq = null; // mark as consumed + ++cSeqAlloc; + } + } + finally + { + if (atlShared != null) + { + safeRelease(mgr, bufShared, atlShared); + } + if (bufferSeq != null) + { + bufferSeq.dispose(); + } + } + + return cSeqAlloc; + } + + /** + * Releasse a shared buffer. + * + * @param mgr the manager to release to + * @param bufShared the shared buffer + * @param counter the reference count + */ + private static void safeRelease(BufferManager mgr, ByteBuffer bufShared, AtomicLong counter) + { + long c = counter.decrementAndGet(); + if (c == 0) + { + mgr.release(bufShared); + } + else if (c < 0) + { + throw new IllegalStateException(); + } + } + + /** + * Construct a BufferSequence from a single ByteBuffer. + * + * @param manager the BufferManager responsible for the ByteBuffer + * @param buffer the buffer + * + * @return a BufferSequence around the supplied buffer + */ + public static BufferSequence createBufferSequence(BufferManager manager, ByteBuffer buffer) + { + return new SingleBufferSequence(manager, buffer); + } + + /** + * Construct a BufferSequence from two ByteBuffers. + * + * @param manager the BufferManager responsible for the ByteBuffers + * @param bufferA the first buffer + * @param bufferB the second buffer + * + * @return a BufferSequence around the supplied buffers + */ + public static BufferSequence createBufferSequence(BufferManager manager, ByteBuffer bufferA, ByteBuffer bufferB) + { + return new DoubleBufferSequence(manager, bufferA, bufferB); + } + + /** + * Construct a BufferSequence from two ByteBuffers. + * + * @param manager the BufferManager responsible for the ByteBuffers + * @param bufferA the first buffer + * @param bufferB the second buffer + * @param bufferC the third buffer + * + * @return a BufferSequence around the supplied buffers + */ + public static BufferSequence createBufferSequence(BufferManager manager, ByteBuffer bufferA, ByteBuffer bufferB, ByteBuffer bufferC) + { + return new TripleBufferSequence(manager, bufferA, bufferB, bufferC); + } + + /** + * Construct a BufferSequence from an array of ByteBuffers. + * + * @param manager the BufferManager responsible for the ByteBuffers + * @param aBuffer the ByteBuffer array + * + * @return a BufferSequence around the supplied buffers + */ + public static BufferSequence createBufferSequence(BufferManager manager, ByteBuffer ... aBuffer) + { + switch (aBuffer.length) + { + case 0: + return Buffers.getEmptyBufferSequence(); + case 1: + return new SingleBufferSequence(manager, aBuffer[0]); + case 2: + return new DoubleBufferSequence(manager, aBuffer[0], aBuffer[1]); + case 3: + return new TripleBufferSequence(manager, aBuffer[0], aBuffer[1], aBuffer[2]); + default: + return new MultiBufferSequence(manager, aBuffer); + } + } + + /** + * Return a ByteBuffer slice of the specified BufferSequence + * + * @param bufseq the sequence to slice + * @param of the starting byte offset of the slice + * @param cb the length of the slice + * + * @return the buffer slice + * + * @throws IllegalArgumentException if the requested region is outside the bounds of the BufferSequence + */ + public static ByteBuffer slice(BufferSequence bufseq, long of, int cb) + { + if (of < 0 || cb < 0) + { + throw new IllegalArgumentException(); + } + + for (int i = 0, c = bufseq.getBufferCount(); i < c; ++i) + { + ByteBuffer buffer = bufseq.getBuffer(i); + int cbBuf = buffer.remaining(); + if (of - cbBuf <= 0) + { + // this buffer contains the region to be sliced + buffer.position((int) of).limit(cb); + return buffer.slice(); + } + of -= cbBuf; + } + + throw new IllegalArgumentException(); + } + + /** + * Copy the contents from one BufferSequence to another as space permits. + * + * @param bufseqSrc the source sequence + * @param bufseqDest the destination sequence + */ + public static void copy(BufferSequence bufseqSrc, BufferSequence bufseqDest) + { + ByteBuffer bufSrc = getEmptyBuffer(); + ByteBuffer bufDst = getEmptyBuffer(); + + for (int iSrc = 0, iDst = 0, cSrc = bufseqSrc.getBufferCount(), cDst = bufseqDest.getBufferCount(); iSrc < cSrc; ) + { + if (!bufSrc.hasRemaining()) + { + bufSrc = bufseqSrc.getBuffer(iSrc++); + } + + while (!bufDst.hasRemaining()) + { + if (iDst == cDst) + { + return; + } + bufDst = bufseqDest.getBuffer(iDst++); + } + + bufDst.put(bufSrc); + } + } + + /** + * Copy a portion of the source buffer into the destination buffer + * + * @param src the source buffer, note the position will not be updated + * @param ofSrc the position at which to start the copy + * @param cb the number of bytes to copy + * @param dst the buffer to copy into, note the position will be updated + */ + public static void copy(ByteBuffer src, int ofSrc, int cb, ByteBuffer dst) + { + if (src.hasArray()) // common case for HeapByteBuffers, or mixed heap & direct + { + dst.put(src.array(), src.arrayOffset() + ofSrc, cb); + } + else if (cb > 128) // assumed most efficient for large DirectByteBuffer copies + { + // only produce garbage for "large" copies, note garbage is small + src = src.duplicate(); + src.position(ofSrc).limit(ofSrc + cb); + dst.put(src); + } + else if (src.order() == dst.order()) // common case for DirectByteBuffers, doesn't produce garbage + { + // copy in chunks + int nPos = dst.position(); + for (; cb >= 8; cb -= 8, nPos += 8, ofSrc += 8) + { + dst.putLong(nPos, src.getLong(ofSrc)); + } + + if (cb >= 4) + { + dst.putInt(nPos, src.getInt(ofSrc)); + cb -= 4; + nPos += 4; + ofSrc += 4; + } + + if (cb >= 2) + { + dst.putShort(nPos, src.getShort(ofSrc)); + cb -= 2; + nPos += 2; + ofSrc += 2; + } + + if (cb > 0) + { + dst.put(nPos, src.get(ofSrc)); + } + + dst.position(nPos); + } + else // rare, small copy between buffers with different endian order + { + while (cb-- > 0) + { + dst.put(src.get(ofSrc++)); + } + } + } + + /** + * Compare two BufferSequences for byte sequence equality. + * + * @param bufseqA the first BufferSequence + * @param bufseqB the second BufferSequence + * + * @return true iff the two sequences represent the same sequence of bytes + */ + public static boolean equals(BufferSequence bufseqA, BufferSequence bufseqB) + { + if (bufseqA == bufseqB) + { + return true; + } + else if (bufseqA == null || bufseqB == null) + { + return false; + } + + long cb = bufseqA.getLength(); + if (cb == bufseqB.getLength()) + { + if (bufseqA.getBufferCount() == 1 && bufseqB.getBufferCount() == 1) + { + return bufseqA.getBuffer(0).equals(bufseqB.getBuffer(0)); + } + + InputStream streamA = new BufferSequenceInputStream(bufseqA); + InputStream streamB = new BufferSequenceInputStream(bufseqB); + + try + { + for (; cb > 0 && streamA.read() == streamB.read(); --cb) + {} + return cb == 0; + } + catch (IOException e) + { + // should not be possible, we already verified that they have + // the same length + return false; + } + finally + { + try + { + streamA.close(); + streamB.close(); + } + catch (IOException e) {} + } + } + return false; + } + + /** + * Return true if the specified portions of the buffers are equal. + * + * @param bufA the first buffer to compare + * @param ofA the starting offset of the first buffer + * @param bufB the second buffer to compare + * @param ofB the starting offset of the second buffer + * @param cb the number of bytes to compare + * + * @return true if the ranges are equal + * + * @throws IndexOutOfBoundsException if either buffer's length is insufficient for the comparison to be performed + */ + public static boolean equals(ByteBuffer bufA, int ofA, ByteBuffer bufB, int ofB, int cb) + { + while (cb-- > 0) + { + if (bufA.get(ofA++) != bufB.get(ofB++)) + { + return false; + } + } + + return true; + } + + /** + * Zero out the buffer's content, between the position and limit. + *

+ * The buffer's position and limit while respected will not be updated. + *

+ * + * @param buffer the buffer to zero out + */ + public static void zero(ByteBuffer buffer) + { + int of = buffer.position(); + int ofEnd = buffer.limit(); + ByteOrder order = null; + + if (of == 0 && ofEnd == buffer.capacity()) + { + // only when clearing the entire buffer can we assume that it is safe to temporarily change the + // byte order, if we are clearing a portion and other threads are operating on other portions + // this would be unsafe + order = buffer.order(); + buffer.order(ByteOrder.nativeOrder()); // avoid byte-swapping during zeroing + } + + for (; ofEnd - of >= 8; of += 8) + { + buffer.putLong(of, 0); + } + + if (ofEnd - of >= 4) + { + buffer.putInt(of, 0); + of += 4; + } + + if (ofEnd - of >= 2) + { + buffer.putShort(of, (short) 0); + of += 2; + } + + if (ofEnd - of == 1) + { + buffer.put(of, (byte) 0); + } + + if (order != null) + { + buffer.order(order); + } + } + + /** + * Return a String containing the {@link ByteBuffer#remaining remaining} contents of the specified ByteBuffer. + * + * @param buf the buffer to format + * + * @return the string representation of the buffer + */ + public static String toString(ByteBuffer buf) + { + StringBuilder sb = new StringBuilder(); + sb.append('['); + + for (int i = buf.position(), e = buf.limit(); i < e; ++i) + { + sb.append(String.format(" %02X", buf.get(i) & 0xFF)); + } + + sb.append(" ]"); + + return sb.toString(); + } + + /** + * Return a String containing the contents of the specified BufferSequence. + * + * @param buf the buffer to format + * @param cbLimit the maximum number of bytes to output + * + * @return the string representation of the sequence + */ + public static String toString(ByteBuffer buf, int cbLimit) + { + return toString(new SingleBufferSequence(null, buf), false, cbLimit); + } + + + /** + * Return a String containing the contents of the specified BufferSequence. + * + * @param bufseq the sequence to format + * + * @return the string representation of the sequence + */ + public static String toString(BufferSequence bufseq) + { + return toString(bufseq, false); + } + + /** + * Return a String containing the contents of the specified BufferSequence. + * + * @param bufseq the sequence to format + * @param fBufDelimit if the internal ByteBuffers within the sequence should be indicated + * + * @return the string representation of the sequence + */ + public static String toString(BufferSequence bufseq, boolean fBufDelimit) + { + return toString(bufseq, fBufDelimit, Long.MAX_VALUE); + } + + /** + * Return a String containing the contents of the specified BufferSequence. + * + * @param bufseq the sequence to format + * @param fBufDelimit if the internal ByteBuffers within the sequence should be indicated + * @param cbLimit the maximum number of bytes to output + * + * @return the string representation of the sequence + */ + public static String toString(BufferSequence bufseq, boolean fBufDelimit, long cbLimit) + { + StringBuilder sb = new StringBuilder(); + sb.append("["); + + long cb = bufseq.getLength(); + + for (ByteBuffer buf : bufseq.getBuffers()) + { + for (int i = buf.position(), e = buf.limit(); i < e && --cbLimit >= 0; ++i) + { + sb.append(String.format(" %02X", buf.get(i) & 0xFF)); + --cb; + } + + if (cbLimit == 0 && cb > 0) + { + sb.append(" ... " + cb + " bytes truncated]"); + return sb.toString(); + } + else if (fBufDelimit) + { + sb.append(';'); // indicates empty buffers in sequence + } + } + + sb.append(" ]"); + + return sb.toString(); + } + + /** + * Decode an int which may span multiple buffers. + * + * Note the position of the buffers will be advanced. + * + * @param aBuffer the array of buffers + * @param of the offset of the buffer to read at + * + * @return the int + */ + public static int getInt(ByteBuffer[] aBuffer, int of) + { + ByteBuffer buff = aBuffer[of]; + if (buff.remaining() >= 4) + { + return buff.getInt(); + } + + int nResult = 0; + for (int n = 24; n >= 0; n -= 8) + { + for (; !buff.hasRemaining(); buff = aBuffer[++of]) + {} + + nResult |= ((((int) buff.get()) & 0xFF) << n); + } + + return nResult; + } + + /** + * Decode a long which may span multiple buffers. + * + * Note the position of the buffers will be advanced. + * + * @param aBuffer the array of buffers + * @param of the offset of the buffer to read at + * + * @return the long + */ + public static long getLong(ByteBuffer[] aBuffer, int of) + { + ByteBuffer buff = aBuffer[of]; + if (buff.remaining() >= 8) + { + return buff.getLong(); + } + + long lResult = 0; + for (int n = 56; n >= 0; n -= 8) + { + for (; !buff.hasRemaining(); buff = aBuffer[++of]) + {} + + lResult |= ((((long) buff.get()) & 0xFF) << n); + } + + return lResult; + } + + /** + * Update the CRC based on the content of the specified buffer, and return + * the CRC's updated value. The specified buf's position is temporarily + * advanced, but then restored. + * Note, the CRC will not be reset before or after use. + * + * @param buf the ByteBuffer + * @param crc32 the CRC32 to update + * + * @return the CRC value + */ + public static int updateCrc(CRC32 crc32, ByteBuffer buf) + { + int nPos = buf.position(); + + crc32.update(buf); + + buf.position(nPos); + return (int) crc32.getValue(); + } + + /** + * Update the CRC based on the content of the buffer array within the + * specified boundaries. + * + * @param crc32 the CRC32 to update + * @param aBuf array of ByteBuffer to compute + * @param of the starting offset within the buffer array + * @param cb the number of bytes to evaluate + * + * @return the CRC value + */ + public static int updateCrc(CRC32 crc32, ByteBuffer[] aBuf, int of, long cb) + { + for (; cb > 0; ++of) + { + ByteBuffer buf = aBuf[of]; + if (buf.remaining() > cb) + { + updateCrc(crc32, buf, cb); + break; + } + else + { + updateCrc(crc32, buf); + cb -= buf.remaining(); + } + } + + return (int) crc32.getValue(); + } + + /** + * Update the CRC of the specified ByteBuffer. + * + * @param crc32 the CRC32 to update + * @param buf the ByteBuffer to check on + * @param cb the length of bytes to calculate on + * + * @return the CRC value + */ + public static int updateCrc(CRC32 crc32, ByteBuffer buf, long cb) + { + int nLimit = buf.limit(); + + buf.limit(buf.position() + (int) cb); + int lCrc = updateCrc(crc32, buf); + + buf.limit(nLimit); + + return (int) lCrc; + } + + // ----- singleton holders ---------------------------------------------- + + /** + * Holder for EmptyBuffer + */ + private static class EmptyBuffer + { + public static ByteBuffer INSTANCE = ByteBuffer.allocate(0); + } + + /** + * Holder for EmptyDirectBuffer + */ + private static class EmptyDirectBuffer + { + public static ByteBuffer INSTANCE = ByteBuffer.allocateDirect(0); + } + + /** + * Holder for the EmptyBufferArray + */ + private static class EmptyBufferArray + { + /** + * EmptyBufferArray singleton. + */ + public static ByteBuffer[] INSTANCE = new ByteBuffer[0]; + } + + /** + * Holder for the EmptyBufferSequence + */ + private static class EmptySequenceHolder + { + /** + * EmptyBufferSequence singleton. + */ + public static BufferSequence INSTANCE = new BufferSequence() + { + public long getLength() + { + return 0; + } + + public int getBufferCount() + { + return 0; + } + + public ByteBuffer getBuffer(int iBuffer) + { + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuffer getUnsafeBuffer(int iBuffer) + { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getBufferPosition(int iBuffer) + { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getBufferLimit(int iBuffer) + { + throw new IndexOutOfBoundsException(); + } + + public int getBufferLength(int iBuffer) + { + throw new IndexOutOfBoundsException(); + } + + public ByteBuffer[] getBuffers() + { + return getEmptyBufferArray(); + } + + public void getBuffers(int iBuffer, int cBuffers, + ByteBuffer[] abufDest, int iDest) + { + if (iBuffer < 0 || cBuffers != 0 || iDest >= abufDest.length) + { + throw new IndexOutOfBoundsException(); + } + } + + public void dispose() + { + } + }; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/CompositeBufferSequence.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/CompositeBufferSequence.java new file mode 100644 index 0000000000000..14c1dc3327c4b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/CompositeBufferSequence.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + +import java.nio.ByteBuffer; + +/** + * CompositeBufferSequence is a BufferSequence which is composed of two underlying BufferSequences. + * + * @author mf 2014.02.19 + */ +public class CompositeBufferSequence + implements BufferSequence + { + /** + * Construct a CompositeBufferSequence from two BufferSequences. + * + * @param bufSeqA the first sequence + * @param bufSeqB the second sequence + */ + public CompositeBufferSequence(BufferSequence bufSeqA, BufferSequence bufSeqB) + { + f_bufSeqA = bufSeqA; + f_bufSeqB = bufSeqB; + } + + @Override + public long getLength() + { + return f_bufSeqA.getLength() + f_bufSeqB.getLength(); + } + + @Override + public int getBufferCount() + { + return f_bufSeqA.getBufferCount() + f_bufSeqB.getBufferCount(); + } + + @Override + public ByteBuffer getBuffer(int iBuffer) + { + int cBufA = f_bufSeqA.getBufferCount(); + return iBuffer < cBufA + ? f_bufSeqA.getBuffer(iBuffer) + : f_bufSeqB.getBuffer(iBuffer - cBufA); + } + + @Override + public ByteBuffer getUnsafeBuffer(int iBuffer) + { + int cBufA = f_bufSeqA.getBufferCount(); + return iBuffer < cBufA + ? f_bufSeqA.getUnsafeBuffer(iBuffer) + : f_bufSeqB.getUnsafeBuffer(iBuffer - cBufA); + } + + @Override + public int getBufferPosition(int iBuffer) + { + int cBufA = f_bufSeqA.getBufferCount(); + return iBuffer < cBufA + ? f_bufSeqA.getBufferPosition(iBuffer) + : f_bufSeqB.getBufferPosition(iBuffer - cBufA); + } + + @Override + public int getBufferLimit(int iBuffer) + { + int cBufA = f_bufSeqA.getBufferCount(); + return iBuffer < cBufA + ? f_bufSeqA.getBufferLimit(iBuffer) + : f_bufSeqB.getBufferLimit(iBuffer - cBufA); + } + + @Override + public int getBufferLength(int iBuffer) + { + int cBufA = f_bufSeqA.getBufferCount(); + return iBuffer < cBufA + ? f_bufSeqA.getBufferLength(iBuffer) + : f_bufSeqB.getBufferLength(iBuffer - cBufA); + } + + @Override + public ByteBuffer[] getBuffers() + { + int cBufA = f_bufSeqA.getBufferCount(); + int cBufB = f_bufSeqB.getBufferCount(); + ByteBuffer[] aBuff = new ByteBuffer[cBufA + cBufB]; + + f_bufSeqA.getBuffers(0, cBufA, aBuff, 0); + f_bufSeqB.getBuffers(0, cBufB, aBuff, cBufA); + + return aBuff; + } + + @Override + public void getBuffers(int iBuffer, int cBuffers, ByteBuffer[] abufDest, int iDest) + { + int cBufA = f_bufSeqA.getBufferCount(); + + if (iBuffer < cBufA) + { + int cBufCopy = Math.min(cBuffers, cBufA - iBuffer); + + f_bufSeqA.getBuffers(iBuffer, cBufCopy, abufDest, iDest); + + iDest += cBufCopy; + iBuffer += cBufCopy; + cBuffers -= cBufCopy; + } + + if (cBuffers > 0) + { + f_bufSeqB.getBuffers(iBuffer - cBufA, cBuffers, abufDest, iDest); + } + } + + @Override + public void dispose() + { + f_bufSeqA.dispose(); + f_bufSeqB.dispose(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The first part of the compound sequence. + */ + final BufferSequence f_bufSeqA; + + /** + * The second part of the compound sequence. + */ + final BufferSequence f_bufSeqB; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/DoubleBufferSequence.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/DoubleBufferSequence.java new file mode 100644 index 0000000000000..50c443092e360 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/DoubleBufferSequence.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * DoubleBufferSequence is a thread-safe BufferSequence implementation which + * wraps two ByteBuffers. + * + * @author mf 2014.10.23 + */ +public class DoubleBufferSequence + extends AtomicInteger // cheaper then having it as a data member on this short lived object + implements BufferSequence + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a DoubleBufferSequence. + *

+ * The BufferSequence will directly reference the supplied buffers, + * subsequent modifications to the buffers or their positions are not allowed. + * + * @param manager the manager responsible for the buffers if they are to be released during dispose + * @param bufferA the first buffer + * @param bufferB the second buffer + */ + public DoubleBufferSequence(BufferManager manager, ByteBuffer bufferA, ByteBuffer bufferB) + { + if (bufferA == null || bufferB == null) + { + throw new IllegalArgumentException("buffers cannot be null"); + } + + f_manager = manager; + m_bufferA = bufferA; + m_bufferB = bufferB; + f_nPositionA = bufferA.position(); + f_nLimitA = bufferA.limit(); + f_nPositionB = bufferB.position(); + f_nLimitB = bufferB.limit(); + } + + // ----- BufferSequence interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public long getLength() + { + return (f_nLimitA - f_nPositionA) + (f_nLimitB - f_nPositionB); + } + + /** + * {@inheritDoc} + */ + @Override + public int getBufferCount() + { + return 2; + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer getBuffer(int iBuffer) + { + switch (iBuffer) + { + case 0: + return getFirstSafeBuffer(); + case 1: + return getSecondSafeBuffer(); + default: + throw new IndexOutOfBoundsException(); + } + } + + @Override + public ByteBuffer getUnsafeBuffer(int iBuffer) + { + switch (iBuffer) + { + case 0: + return m_bufferA; + case 1: + return m_bufferB; + default: + throw new IndexOutOfBoundsException(); + } + } + + @Override + public int getBufferPosition(int iBuffer) + { + switch (iBuffer) + { + case 0: + return f_nPositionA; + case 1: + return f_nPositionB; + default: + throw new IndexOutOfBoundsException(); + } + } + + @Override + public int getBufferLimit(int iBuffer) + { + switch (iBuffer) + { + case 0: + return f_nLimitA; + case 1: + return f_nLimitB; + default: + throw new IndexOutOfBoundsException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int getBufferLength(int iBuffer) + { + switch (iBuffer) + { + case 0: + return f_nLimitA - f_nPositionA; + case 1: + return f_nLimitB - f_nPositionB; + default: + throw new IndexOutOfBoundsException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer[] getBuffers() + { + return new ByteBuffer[] {getFirstSafeBuffer(), getSecondSafeBuffer()}; + } + + /** + * {@inheritDoc} + */ + @Override + public void getBuffers(int iBuffer, int cBuffers, ByteBuffer[] abufDest, + int iDest) + { + if (cBuffers-- > 0) + { + abufDest[iDest++] = getBuffer(iBuffer++); + + if (cBuffers > 0) + { + abufDest[iDest] = getBuffer(iBuffer); + } + } + } + + + // ----- Disposable interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + ByteBuffer buffA = m_bufferA; + ByteBuffer buffB = m_bufferB; + if (buffA == null) + { + throw new IllegalStateException("already disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if (TRACK_DISPOSE) + { + m_stackDispose = new IOException("disposed at"); + } + m_bufferA = null; + m_bufferB = null; + + BufferManager manager = f_manager; + if (manager != null) + { + manager.release(buffA); + manager.release(buffB); + } + } + + // ----- Object interface ----------------------------------------------- + + @Override + public String toString() + { + return Buffers.toString(this); + } + + + // ----- helper methods ------------------------------------------------- + + /** + * Return true if the BufferSequence has been disposed. + * + * @return true if the BufferSequence has been disposed. + */ + public boolean isDisposed() + { + return m_bufferA == null; + } + + /** + * Return a view of the first ByteBuffer which has it's position and limit set + * to their original values. + * + * @return a view of the ByteBuffer + */ + protected final ByteBuffer getFirstSafeBuffer() + { + // we use a CAS on an updater rather then sync as typically this is only called + // once per BufferSequence and the cost of the first sync can be high + ByteBuffer buff = m_bufferA; + int n = get(); + if (buff == null) + { + throw new IllegalStateException("disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if ((n & 0x01) == 0 && compareAndSet(n, n | 0x01)) + { + return buff; // common path + } + + buff = buff.duplicate(); + buff.limit(f_nLimitA).position(f_nPositionA); + return buff; + } + + /** + * Return a view of the second ByteBuffer which has it's position and limit set + * to their original values. + * + * @return a view of the ByteBuffer + */ + protected final ByteBuffer getSecondSafeBuffer() + { + // we use a CAS on an updater rather then sync as typically this is only called + // once per BufferSequence and the cost of the first sync can be high + ByteBuffer buff = m_bufferB; + int n = get(); + if (buff == null) + { + throw new IllegalStateException("disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if ((n & 0x10) == 0 && compareAndSet(n, n | 0x10)) + { + return buff; // common path + } + + buff = buff.duplicate(); + buff.limit(f_nLimitB).position(f_nPositionB); + return buff; + } + + // ----- data members --------------------------------------------------- + + /** + * The BufferManager + */ + protected final BufferManager f_manager; + + /** + * The first ByteBuffer. + */ + protected ByteBuffer m_bufferA; + + /** + * The second ByteBuffer. + */ + protected ByteBuffer m_bufferB; + + /** + * The first buffer's original position. + */ + protected final int f_nPositionA; + + /** + * The first buffer's original limit. + */ + protected final int f_nLimitA; + + /** + * The second buffer's original position. + */ + protected final int f_nPositionB; + + /** + * The second buffer's original limit. + */ + protected final int f_nLimitB; + + /** + * The stack at which the sequence was destroyed, if trackDestroy is enabled. + */ + protected IOException m_stackDispose; + + /** + * True iff dispose locations should be tracked (for debugging) + */ + private static final boolean TRACK_DISPOSE = Boolean.getBoolean( + BufferSequence.class.getName() + ".trackDispose"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/Files.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/Files.java new file mode 100644 index 0000000000000..b0c6956b95222 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/Files.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; + +import java.util.StringTokenizer; + +/** + * File related utility methods. + * + * @author mf 2017.07.26 + */ +public class Files + { + /** + * Return true if the specified file or path appears to be on a locally mounted filesystem. + * + * Note: By default the implementation will return a value of false if it cannot determine that + * the file is local, this can be overridden by setting the com.oracle.common.io.Files.assumeLocal + * system property to true. + * + * @param file the file or path to query + * + * @return true if local + */ + public static boolean isLocal(File file) + { + return isLocal(file, ASSUME_LOCAL); + } + + /** + * Return true if the specified file or path appears to be on a locally mounted filesystem. + * + * @param file the file or path to query + * @param fAssumeLocal the value to return if a definitive answer cannot be determined + * + * @return true if local + */ + public static boolean isLocal(File file, final boolean fAssumeLocal) + { + try + { + file.exists(); // trigger auto-mounter if any so that the mount point will be resolvable + + String sDir = file.isDirectory() ? file.getCanonicalPath() : file.getCanonicalFile().getParent(); + String sOS = new StringTokenizer(System.getProperty("os.name").toLowerCase().trim()).nextToken(); + + String sMountTable; + switch (sOS) + { + case "windows": + // we can at least assume that anything on the boot drive is local + String sSystemDrive = System.getenv("SystemDrive"); + if (sSystemDrive != null) + { + int ofDrive = sDir.indexOf(":"); + if (ofDrive != -1) + { + String sDrive = sDir.substring(0, ofDrive + 1); + if (sDrive.equalsIgnoreCase(sSystemDrive)) + { + return true; + } + } + } + return fAssumeLocal; + + case "mac": // Mac OS + // we can at least assume that anything on the boot drive is local + // Note: we could execute "mount" and parse the output for a more accurate result, but this + // really should be sufficient + return !sDir.startsWith("/Volumes/") || fAssumeLocal; + + case "linux": + sMountTable = "/proc/mounts"; + break; + + default: // solaris, hpux, other unixes + sMountTable = "/etc/mnttab"; + break; + } + + String sPointBest = ""; + boolean fLocal = fAssumeLocal; + try (BufferedReader in = new BufferedReader(new FileReader(new File(sMountTable)))) + { + if (!sDir.endsWith("/")) + { + sDir += "/"; + } + + for (;;) + { + String sLine = in.readLine(); + if (sLine == null) + { + break; + } + + sLine = sLine.trim(); + if (sLine.isEmpty() || sLine.startsWith("#")) + { + continue; + } + + StringTokenizer sMount = new StringTokenizer(sLine); + String sDevice = sMount.nextToken(); + String sPoint = sMount.nextToken(); + String sFs = sMount.nextToken(); + + if (!sPoint.endsWith("/")) + { + sPoint += "/"; + } + + if (sDir.startsWith(sPoint) && sPoint.length() >= sPointBest.length()) // mountpoint can appear multiple times + { + sPointBest = sPoint; + fLocal = sDevice.equals ("rootfs") || // linux systemd root mount + sDevice.startsWith("/dev/") || // linux/solaris normal local mounts + sDevice.startsWith("rpool/"); // solaris root pool local mounts + + if (sFs.equals("lofs")) + { + // solaris loopback filesystems, sDevice is the source mount; common for /export/home -> /home + fLocal = isLocal(new File(sDevice), fAssumeLocal); + } + } + } + } + + return fLocal; + } + catch (Throwable e) + { + return fAssumeLocal; + } + } + + + // ----- constants ------------------------------------------------------ + + /** + * The default value to assume if it cannot be determined if a path is local or not. + */ + protected static final boolean ASSUME_LOCAL = Boolean.getBoolean(Files.class.getCanonicalName() + ".assumeLocal"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/InputStreaming.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/InputStreaming.java new file mode 100644 index 0000000000000..feca6b44be86b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/InputStreaming.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + +import java.io.IOException; + + +/** +* This is the interface represented by the Java InputStream class. +* +* @author cp 2005.01.18 +*/ +public interface InputStreaming + { + // ----- InputStream methods -------------------------------------------- + + /** + * Read the next byte of data from the InputStream. The value byte is + * returned as an int in the range 0 to + * 255. If the end of the stream has been reached, the value + * -1 is returned. + *

+ * This method blocks until input data is available, the end of the stream + * is detected, or an exception is thrown. + * + * @return the next byte of data, or -1 if the end of the + * stream has been reached + * + * @exception java.io.IOException if an I/O error occurs + */ + public int read() + throws IOException; + + /** + * Read some number of bytes from the input stream and store them into the + * passed array ab. The number of bytes actually read is + * returned. + *

+ * This method blocks until input data is available, the end of the stream + * is detected, or an exception is thrown. + * + * @param ab the array to store the bytes which are read from the stream + * + * @return the number of bytes read from the stream, or -1 + * if no bytes were read from the stream because the end of the + * stream had been reached + * + * @exception NullPointerException if the passed array is null + * @exception IOException if an I/O error occurs + */ + public int read(byte ab[]) + throws IOException; + + /** + * Read up to cb bytes from the input stream and store them + * into the passed array ab starting at offset + * of. The number of bytes actually read is returned. + *

+ * This method blocks until input data is available, the end of the stream + * is detected, or an exception is thrown. + * + * @param ab the array to store the bytes which are read from the stream + * @param of the offset into the array that the read bytes will be stored + * @param cb the maximum number of bytes to read + * + * @return the number of bytes read from the stream, or -1 + * if no bytes were read from the stream because the end of the + * stream had been reached + * + * @exception NullPointerException if the passed array is null + * @exception IndexOutOfBoundsException if of or + * cb is negative, or of+cb is + * greater than the length of the ab + * @exception IOException if an I/O error occurs + */ + public int read(byte ab[], int of, int cb) + throws IOException; + + /** + * Skips over up to the specified number of bytes of data from this + * InputStream. The number of bytes actually skipped over may be fewer + * than the number specified to skip, and may even be zero; this can be + * caused by an end-of-file condition, but can also occur even when there + * is data remaining in the InputStream. As a result, the caller should + * check the return value from this method, which indicates the actual + * number of bytes skipped. + * + * @param cb the maximum number of bytes to skip over + * + * @return the actual number of bytes that were skipped over + * + * @exception IOException if an I/O error occurs + */ + public long skip(long cb) + throws IOException; + + /** + * Returns the number of bytes that can be read (or skipped over) from + * this input stream without causing a blocking I/O condition to occur. + * This method reflects the assumed implementation of various buffering + * InputStreams, which can guarantee non-blocking reads up to the extent + * of their buffers, but beyond that the read operations will have to read + * from some underlying (and potentially blocking) source. + * + * @return the number of bytes that can be read from this InputStream + * without blocking + * + * @exception IOException if an I/O error occurs + */ + public int available() + throws IOException; + + /** + * Close the InputStream and release any system resources associated with + * it. + * + * @exception IOException if an I/O error occurs + */ + public void close() + throws IOException; + + /** + * Marks the current read position in the InputStream in order to support + * the stream to be later "rewound" (using the {@link #reset} method) to + * the current position. The caller passes in the maximum number of bytes + * that it expects to read before calling the {@link #reset} method, thus + * indicating the upper bounds of the responsibility of the stream to be + * able to buffer what it has read in order to support this functionality. + * + * @param cbReadLimit the maximum number of bytes that caller expects the + * InputStream to be able to read before the mark + * position becomes invalid + */ + public void mark(int cbReadLimit); + + /** + * Rewind this stream to the position at the time the {@link #mark} method + * was last called on this InputStream. If the InputStream cannot fulfill + * this contract, it should throw an IOException. + * + * @exception IOException if an I/O error occurs, for example if this + * has not been marked or if the mark has been + * invalidated + */ + public void reset() + throws IOException; + + /** + * Determine if this InputStream supports the {@link #mark} and + * {@link #reset} methods. + * + * @return true if this InputStream supports the mark and + * reset method; false otherwise + */ + public boolean markSupported(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/MultiBufferSequence.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/MultiBufferSequence.java new file mode 100644 index 0000000000000..2270f87440bff --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/MultiBufferSequence.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * MultiBufferSequence is a thread-safe BufferSequence implementation based on + * an array of ByteBuffers. + *

+ * For BufferSequences of just a few elements it is recommended to either directly + * instantate a {@link SingleBufferSequence}, {@link DoubleBufferSequence}, + * {@link TripleBufferSequence}, or to use the the + * {@link Buffers#createBufferSequence} method, as these will produce more space optimized + * alternatives to the MultiBufferSequence. + *

+ * + * @author mf 2010.12.04 + */ +public class MultiBufferSequence + extends AtomicInteger // cheaper then having it as a data member on this short lived object + implements BufferSequence + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a MultiBufferSequence from an array of ByteBuffers. + *

+ * The BufferSequence will directly reference the supplied array and + * buffers, subsequent modifications to the array, buffers and buffer + * positions are not allowed. + * + * It is recommended that {@link Buffers#createBufferSequence} be used as an alternative + * to this constructor as it can produce a more optimized result for + * small array sizes. + * + * @param manager the BufferManager responsible for the ByteBuffers + * @param aBuffer the ByteBuffer array + */ + public MultiBufferSequence(BufferManager manager, ByteBuffer[] aBuffer) + { + this(manager, aBuffer, 0, aBuffer.length); + } + + /** + * Construct a MultiBufferSequence from an array of ByteBuffers. + *

+ * The BufferSequence will directly reference the supplied array and + * buffers, subsequent modifications to the specified array portion, its + * buffers and buffer positions are not allowed. + * + * @param manager the BufferManager responsible for the ByteBuffers + * @param aBuffer the ByteBuffer array + * @param of the offset into the array at which to start the sequence + * @param cBuffer the number of buffers in the sequence + */ + public MultiBufferSequence(BufferManager manager, ByteBuffer[] aBuffer, int of, int cBuffer) + { + this(manager, aBuffer, of, cBuffer, computeLength(aBuffer, of, cBuffer)); + } + + /** + * Construct a MultiBufferSequence from an array of ByteBuffers. + *

+ * The BufferSequence will directly reference the supplied array and + * buffers, subsequent modifications to the specified array portion, its + * buffers and buffer positions are not allowed. + * + * @param manager the BufferManager responsible for the ByteBuffers + * @param aBuffer the ByteBuffer array + * @param of the offset into the array at which to start the sequence + * @param cBuffer the number of buffers in the sequence + * @param cbBuffer the number of bytes in the sequence + */ + public MultiBufferSequence(BufferManager manager, ByteBuffer[] aBuffer, int of, int cBuffer, long cbBuffer) + { + if (aBuffer == null) + { + throw new IllegalArgumentException("buffer array cannot be null"); + } + + int[] aPosLimit = new int[cBuffer * 2]; + for (int i = 0; i < cBuffer; ++i) + { + ByteBuffer buff = aBuffer[of + i]; + aPosLimit[i] = buff.position(); + aPosLimit[cBuffer + i] = buff.limit(); + } + + f_manager = manager; + m_aBuffer = aBuffer; + f_ofBuffer = of; + f_cBuffer = cBuffer; + f_cbBuffer = cbBuffer; + f_aPosLimit = aPosLimit; + } + + // ----- BufferSequence interface --------------------------------------- + + /** + * {@inheritDoc} + */ + public long getLength() + { + return f_cbBuffer; + } + + /** + * {@inheritDoc} + */ + public int getBufferCount() + { + return f_cBuffer; + } + + /** + * {@inheritDoc} + */ + public ByteBuffer getBuffer(int iBuffer) + { + if (iBuffer < 0 || iBuffer >= f_cBuffer) + { + throw new IndexOutOfBoundsException(); + } + return getSafeBuffer(iBuffer); + } + + @Override + public ByteBuffer getUnsafeBuffer(int iBuffer) + { + if (iBuffer < 0 || iBuffer >= f_cBuffer) + { + throw new IndexOutOfBoundsException(); + } + return m_aBuffer[f_ofBuffer + iBuffer]; + } + + @Override + public int getBufferPosition(int iBuffer) + { + if (iBuffer < 0 || iBuffer >= f_cBuffer) + { + throw new IndexOutOfBoundsException(); + } + return f_aPosLimit[f_ofBuffer + iBuffer]; + } + + @Override + public int getBufferLimit(int iBuffer) + { + if (iBuffer < 0 || iBuffer >= f_cBuffer) + { + throw new IndexOutOfBoundsException(); + } + return f_aPosLimit[f_ofBuffer + f_cBuffer + iBuffer]; + } + + @Override + public int getBufferLength(int iBuffer) + { + return f_aPosLimit[f_ofBuffer + f_cBuffer + iBuffer] - f_aPosLimit[f_ofBuffer + iBuffer]; + } + + /** + * {@inheritDoc} + */ + public ByteBuffer[] getBuffers() + { + int cBuffer = f_cBuffer; + ByteBuffer[] aBufferNew = new ByteBuffer[cBuffer]; + + for (int i = 0; i < cBuffer; ++i) + { + aBufferNew[i] = getSafeBuffer(i); + } + + return aBufferNew; + } + + /** + * {@inheritDoc} + */ + public void getBuffers(int iBuffer, int cBuffers, ByteBuffer[] abufDest, + int iDest) + { + int cBufferSrc = f_cBuffer; + + if (iBuffer < 0 || iBuffer + cBuffers > cBufferSrc) + { + throw new IndexOutOfBoundsException(); + } + + for (int eDest = iDest + cBuffers; iDest < eDest; ++iDest, ++iBuffer) + { + abufDest[iDest] = getSafeBuffer(iBuffer); + } + } + + + // ----- Disposable interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + ByteBuffer[] aBuffer = m_aBuffer; + if (aBuffer == null) + { + throw new IllegalStateException("already disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if (TRACK_DISPOSE) + { + m_stackDispose = new IOException("disposed at"); + } + m_aBuffer = null; + + BufferManager manager = f_manager; + if (manager != null) + { + for (int of = f_ofBuffer, eof = of + f_cBuffer; of < eof; ++of) + { + manager.release(aBuffer[of]); + } + } + } + + + // ----- Object interface ----------------------------------------------- + + @Override + public String toString() + { + return Buffers.toString(this); + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Return true if the BufferSequence has been disposed. + * + * @return true if the BufferSequence has been disposed. + */ + public boolean isDisposed() + { + return m_aBuffer == null; + } + + /** + * Return the number of remaning bytes in the supplied ByteBuffer array. + * + * @param aBuffer the buffer array + * @param of the start offset + * @param cBuffer the number of buffers to consider + * + * @return the byte count + */ + protected static long computeLength(ByteBuffer[] aBuffer, int of, int cBuffer) + { + long cb = 0; + for (int e = of + cBuffer; of < e; ++of) + { + cb += aBuffer[of].remaining(); + } + return cb; + } + + /** + * Return a view of the ByteBuffer which has it's position and limit set + * to their original values. + * + * @return a view of the ByteBuffer + */ + private final ByteBuffer getSafeBuffer(int i) + { + ByteBuffer[] aBuffer = m_aBuffer; + if (aBuffer == null) + { + throw new IllegalStateException("disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + ByteBuffer buff = aBuffer[f_ofBuffer + i]; + + // we use a CAS on an updater rather then sync as typically this is only called + // once per BufferSequence and the cost of the first sync can be high + if (compareAndSet(i, i + 1)) + { + return buff; // common path + } + + buff = buff.duplicate(); + buff.limit(f_aPosLimit[f_cBuffer + i]).position(f_aPosLimit[i]); + return buff; + } + + + // ----- data members --------------------------------------------------- + + /** + * The BufferManager + */ + protected final BufferManager f_manager; + + /** + * The ByteBuffer array. + */ + protected ByteBuffer[] m_aBuffer; + + /** + * The offset of the first buffer. + */ + protected final int f_ofBuffer; + + /** + * The number of ByteBuffers in the sequence. + */ + protected final int f_cBuffer; + + /** + * The number of bytes in the sequence. + */ + protected final long f_cbBuffer; + + /** + * An array of the original positions and limits of each buffer. + */ + protected final int[] f_aPosLimit; + + /** + * The stack at which the sequence was destroyed, if trackDestroy is enabled. + */ + protected IOException m_stackDispose; + + /** + * True iff dispose locations should be tracked (for debugging) + */ + private static final boolean TRACK_DISPOSE = Boolean.getBoolean( + BufferSequence.class.getName() + ".trackDispose"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/OutputStreaming.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/OutputStreaming.java new file mode 100644 index 0000000000000..99bd81c1c4edb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/OutputStreaming.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import java.io.IOException; + + +/** +* This is the interface represented by the Java OutputStream class. +* +* @author cp 2005.01.18 +*/ +public interface OutputStreaming + { + // ----- OutputStream methods ------------------------------------------- + + /** + * Writes the eight low-order bits of the argument b. The 24 + * high-order bits of b are ignored. + * + * @param b the byte to write (passed as an integer) + * + * @exception java.io.IOException if an I/O error occurs + */ + public void write(int b) + throws IOException; + + /** + * Writes all the bytes in the array ab. + * + * @param ab the byte array to write + * + * @exception IOException if an I/O error occurs + * @exception NullPointerException if ab is + * null + */ + public void write(byte ab[]) + throws IOException; + + /** + * Writes cb bytes starting at offset of from + * the array ab. + * + * @param ab the byte array to write from + * @param of the offset into ab to start writing from + * @param cb the number of bytes from ab to write + * + * @exception IOException if an I/O error occurs + * @exception NullPointerException if ab is + * null + * @exception IndexOutOfBoundsException if of is negative, + * or cb is negative, or of+cb is + * greater than ab.length + */ + public void write(byte ab[], int of, int cb) + throws IOException; + + /** + * Flushes this OutputStream and forces any buffered output bytes to be + * written. + * + * @exception IOException if an I/O error occurs + */ + public void flush() + throws IOException; + + /** + * Closes this OutputStream and releases any associated system resources. + * + * @exception IOException if an I/O error occurs + */ + public void close() + throws IOException; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/SingleBufferSequence.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/SingleBufferSequence.java new file mode 100644 index 0000000000000..d6945f2e30299 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/SingleBufferSequence.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * SingleBufferSequence is a thread-safe BufferSequence implementation which + * wraps a single ByteBuffer. + * + * @author mf 2010.12.04 + */ +public class SingleBufferSequence + extends AtomicInteger // cheaper then having it as a data member on this short lived object + implements BufferSequence + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a SingleBufferSequence around a single ByteBuffer. + *

+ * The BufferSequence will directly reference the supplied buffer, + * subsequent modifications to the buffer or its positions are not allowed. + * + * @param manager the manager responsible for the buffer if it is to be released during dispose + * @param buffer the buffer + */ + public SingleBufferSequence(BufferManager manager, ByteBuffer buffer) + { + if (buffer == null) + { + throw new IllegalArgumentException("buffer cannot be null"); + } + + f_manager = manager; + m_buffer = buffer; + f_nPosition = buffer.position(); + f_nLimit = buffer.limit(); + } + + /** + * Construct an "unsafe" SingleBufferSequence around a single ByteBuffer. + *

+ * The BufferSequence will directly reference the supplied buffer, + * subsequent modifications to the buffer are not allowed. The buffer's positions will + * not be relied upon and thus are safe to update externally. + * + * @param manager the manager responsible for the buffer if it is to be released during dispose + * @param buffer the buffer + * @param nPos the position within the buffer + * @param cb the number of bytes + */ + public SingleBufferSequence(BufferManager manager, ByteBuffer buffer, int nPos, int cb) + { + super(1); // mark the BufferSequence as immediately unsafe + if (buffer == null) + { + throw new IllegalArgumentException("buffer cannot be null"); + } + else if (nPos < 0 || cb < 0 || nPos + cb > buffer.capacity()) + { + throw new IllegalArgumentException("specified position and length exceed buffer capacity"); + } + + f_manager = manager; + m_buffer = buffer; + f_nPosition = nPos; + f_nLimit = nPos + cb; + } + + + // ----- BufferSequence interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public long getLength() + { + return f_nLimit - f_nPosition; + } + + /** + * {@inheritDoc} + */ + @Override + public int getBufferCount() + { + return 1; + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer getBuffer(int iBuffer) + { + if (iBuffer == 0) + { + return getSafeBuffer(); + } + throw new IndexOutOfBoundsException(); + } + + @Override + public ByteBuffer getUnsafeBuffer(int iBuffer) + { + if (iBuffer == 0) + { + return m_buffer; + } + throw new IndexOutOfBoundsException(); + } + + @Override + public int getBufferPosition(int iBuffer) + { + if (iBuffer == 0) + { + return f_nPosition; + } + throw new IndexOutOfBoundsException(); + } + + @Override + public int getBufferLimit(int iBuffer) + { + if (iBuffer == 0) + { + return f_nLimit; + } + throw new IndexOutOfBoundsException(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getBufferLength(int iBuffer) + { + if (iBuffer == 0) + { + return f_nLimit - f_nPosition; + } + throw new IndexOutOfBoundsException(); + } + + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer[] getBuffers() + { + return new ByteBuffer[] {getSafeBuffer()}; + } + + /** + * {@inheritDoc} + */ + @Override + public void getBuffers(int iBuffer, int cBuffers, ByteBuffer[] abufDest, + int iDest) + { + if (iBuffer != 0 || cBuffers > 1) + { + throw new IndexOutOfBoundsException(); + } + + if (cBuffers == 1) + { + abufDest[iDest] = getSafeBuffer(); + } + } + + + // ----- Disposable interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + ByteBuffer buff = m_buffer; + if (buff == null) + { + throw new IllegalStateException("already disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if (TRACK_DISPOSE) + { + m_stackDispose = new IOException("disposed at"); + } + m_buffer = null; + + BufferManager manager = f_manager; + if (manager != null) + { + manager.release(buff); + } + } + + // ----- Object interface ----------------------------------------------- + + @Override + public String toString() + { + return Buffers.toString(this); + } + + + // ----- helper methods ------------------------------------------------- + + /** + * Return true if the BufferSequence has been disposed. + * + * @return true if the BufferSequence has been disposed. + */ + public boolean isDisposed() + { + return m_buffer == null; + } + + /** + * Return a view of the ByteBuffer which has it's position and limit set + * to their original values. + * + * @return a view of the ByteBuffer + */ + protected final ByteBuffer getSafeBuffer() + { + // we use a CAS on an updater rather then sync as typically this is only called + // once per BufferSequence and the cost of the first sync can be high + ByteBuffer buff = m_buffer; + if (buff == null) + { + throw new IllegalStateException("disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if (compareAndSet(0, 1)) + { + return buff; // common path + } + + buff = buff.duplicate(); + buff.limit(f_nLimit).position(f_nPosition); + return buff; + } + + // ----- data members --------------------------------------------------- + + /** + * The BufferManager + */ + protected final BufferManager f_manager; + + /** + * The ByteBuffer. + */ + protected ByteBuffer m_buffer; + + /** + * The buffer's original position. + */ + protected final int f_nPosition; + + /** + * The buffer's original limit. + */ + protected final int f_nLimit; + + /** + * The stack at which the sequence was destroyed, if trackDestroy is enabled. + */ + protected IOException m_stackDispose; + + /** + * True iff dispose locations should be tracked (for debugging) + */ + private static final boolean TRACK_DISPOSE = Boolean.getBoolean( + BufferSequence.class.getName() + ".trackDispose"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/TripleBufferSequence.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/TripleBufferSequence.java new file mode 100644 index 0000000000000..f5c9db2283c27 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/TripleBufferSequence.java @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.io; + + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + * TripleBufferSequence is a thread-safe BufferSequence implementation which + * wraps three ByteBuffers. + * + * @author mf 2014.10.23 + */ +public class TripleBufferSequence + extends AtomicInteger // cheaper then having it as a data member on this short lived object + implements BufferSequence + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a TripleBufferSequence. + *

+ * The BufferSequence will directly reference the supplied buffers, + * subsequent modifications to the buffers or their positions are not allowed. + * + * @param manager the manager responsible for the buffers if they are to be released during dispose + * @param bufferA the first buffer + * @param bufferB the second buffer + * @param bufferC the third buffer + */ + public TripleBufferSequence(BufferManager manager, ByteBuffer bufferA, ByteBuffer bufferB, ByteBuffer bufferC) + { + if (bufferA == null || bufferB == null || bufferC == null) + { + throw new IllegalArgumentException("buffers cannot be null"); + } + + f_manager = manager; + m_bufferA = bufferA; + m_bufferB = bufferB; + m_bufferC = bufferC; + f_nPositionA = bufferA.position(); + f_nLimitA = bufferA.limit(); + f_nPositionB = bufferB.position(); + f_nLimitB = bufferB.limit(); + f_nPositionC = bufferC.position(); + f_nLimitC = bufferC.limit(); + } + + // ----- BufferSequence interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public long getLength() + { + return (f_nLimitA - f_nPositionA) + (f_nLimitB - f_nPositionB) + (f_nLimitC - f_nPositionC); + } + + /** + * {@inheritDoc} + */ + @Override + public int getBufferCount() + { + return 3; + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer getBuffer(int iBuffer) + { + switch (iBuffer) + { + case 0: + return getFirstSafeBuffer(); + case 1: + return getSecondSafeBuffer(); + case 2: + return getThirdSafeBuffer(); + default: + throw new IndexOutOfBoundsException(); + } + } + @Override + public ByteBuffer getUnsafeBuffer(int iBuffer) + { + switch (iBuffer) + { + case 0: + return m_bufferA; + case 1: + return m_bufferB; + case 2: + return m_bufferC; + default: + throw new IndexOutOfBoundsException(); + } + } + + @Override + public int getBufferPosition(int iBuffer) + { + switch (iBuffer) + { + case 0: + return f_nPositionA; + case 1: + return f_nPositionB; + case 2: + return f_nPositionC; + default: + throw new IndexOutOfBoundsException(); + } + } + + @Override + public int getBufferLimit(int iBuffer) + { + switch (iBuffer) + { + case 0: + return f_nLimitA; + case 1: + return f_nLimitB; + case 2: + return f_nLimitC; + default: + throw new IndexOutOfBoundsException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int getBufferLength(int iBuffer) + { + switch (iBuffer) + { + case 0: + return f_nLimitA - f_nPositionA; + case 1: + return f_nLimitB - f_nPositionB; + case 2: + return f_nLimitC - f_nPositionC; + default: + throw new IndexOutOfBoundsException(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer[] getBuffers() + { + return new ByteBuffer[] {getFirstSafeBuffer(), getSecondSafeBuffer(), getThirdSafeBuffer()}; + } + + /** + * {@inheritDoc} + */ + @Override + public void getBuffers(int iBuffer, int cBuffers, ByteBuffer[] abufDest, + int iDest) + { + if (cBuffers-- > 0) + { + abufDest[iDest++] = getBuffer(iBuffer++); + + if (cBuffers-- > 0) + { + abufDest[iDest++] = getBuffer(iBuffer++); + + if (cBuffers > 0) + { + abufDest[iDest] = getBuffer(iBuffer); + } + } + } + } + + + // ----- Disposable interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + ByteBuffer buffA = m_bufferA; + ByteBuffer buffB = m_bufferB; + ByteBuffer buffC = m_bufferC; + if (buffA == null) + { + throw new IllegalStateException("already disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if (TRACK_DISPOSE) + { + m_stackDispose = new IOException("disposed at"); + } + m_bufferA = null; + m_bufferB = null; + m_bufferC = null; + + BufferManager manager = f_manager; + if (manager != null) + { + manager.release(buffA); + manager.release(buffB); + manager.release(buffC); + } + } + + // ----- Object interface ----------------------------------------------- + + @Override + public String toString() + { + return Buffers.toString(this); + } + + + // ----- helper methods ------------------------------------------------- + + /** + * Return true if the BufferSequence has been disposed. + * + * @return true if the BufferSequence has been disposed. + */ + public boolean isDisposed() + { + return m_bufferA == null; + } + + /** + * Return a view of the first ByteBuffer which has it's position and limit set + * to their original values. + * + * @return a view of the ByteBuffer + */ + protected final ByteBuffer getFirstSafeBuffer() + { + // we use a CAS on an updater rather then sync as typically this is only called + // once per BufferSequence and the cost of the first sync can be high + ByteBuffer buff = m_bufferA; + int n = get(); + if (buff == null) + { + throw new IllegalStateException("disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if ((n & 0x01) == 0 && compareAndSet(n, n | 0x01)) + { + return buff; // common path + } + + buff = buff.duplicate(); + buff.limit(f_nLimitA).position(f_nPositionA); + return buff; + } + + /** + * Return a view of the second ByteBuffer which has it's position and limit set + * to their original values. + * + * @return a view of the ByteBuffer + */ + protected final ByteBuffer getSecondSafeBuffer() + { + // we use a CAS on an updater rather then sync as typically this is only called + // once per BufferSequence and the cost of the first sync can be high + ByteBuffer buff = m_bufferB; + int n = get(); + if (buff == null) + { + throw new IllegalStateException("disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if ((n & 0x10) == 0 && compareAndSet(n, n | 0x10)) + { + return buff; // common path + } + + buff = buff.duplicate(); + buff.limit(f_nLimitB).position(f_nPositionB); + return buff; + } + + /** + * Return a view of the third ByteBuffer which has it's position and limit set + * to their original values. + * + * @return a view of the ByteBuffer + */ + protected final ByteBuffer getThirdSafeBuffer() + { + // we use a CAS on an updater rather then sync as typically this is only called + // once per BufferSequence and the cost of the first sync can be high + ByteBuffer buff = m_bufferC; + int n = get(); + if (buff == null) + { + throw new IllegalStateException("disposed (for location use -D" + + BufferSequence.class.getName() + ".trackDispose=true)", m_stackDispose); + } + else if ((n & 0x0100) == 0 && compareAndSet(n, n | 0x0100)) + { + return buff; // common path + } + + buff = buff.duplicate(); + buff.limit(f_nLimitC).position(f_nPositionC); + return buff; + } + + // ----- data members --------------------------------------------------- + + /** + * The BufferManager + */ + protected final BufferManager f_manager; + + /** + * The first ByteBuffer. + */ + protected ByteBuffer m_bufferA; + + /** + * The second ByteBuffer. + */ + protected ByteBuffer m_bufferB; + + /** + * The third ByteBuffer. + */ + protected ByteBuffer m_bufferC; + + /** + * The first buffer's original position. + */ + protected final int f_nPositionA; + + /** + * The first buffer's original limit. + */ + protected final int f_nLimitA; + + /** + * The second buffer's original position. + */ + protected final int f_nPositionB; + + /** + * The second buffer's original limit. + */ + protected final int f_nLimitB; + + /** + * The third buffer's original position. + */ + protected final int f_nPositionC; + + /** + * The thirrd buffer's original limit. + */ + protected final int f_nLimitC; + + /** + * The stack at which the sequence was destroyed, if trackDestroy is enabled. + */ + protected IOException m_stackDispose; + + /** + * True iff dispose locations should be tracked (for debugging) + */ + private static final boolean TRACK_DISPOSE = Boolean.getBoolean( + BufferSequence.class.getName() + ".trackDispose"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/package.html new file mode 100644 index 0000000000000..b7b0120e9552f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/io/package.html @@ -0,0 +1,5 @@ + +The io package provides a number of utility classes for IO related work. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetAddressComparator.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetAddressComparator.java new file mode 100644 index 0000000000000..3a0da2fa2d38d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetAddressComparator.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import java.net.InetAddress; +import java.util.Comparator; + + +/** + * Comparator implementation suitable for comparing InetAddresses. + * + * @author mf 2010.11.03 + */ +public class InetAddressComparator + implements Comparator + { + /** + * {@inheritDoc} + */ + public int compare(InetAddress addrA, InetAddress addrB) + { + if (addrA == addrB) + { + return 0; + } + else if (addrA == null) + { + return -1; + } + else if (addrB == null) + { + return 1; + } + + byte[] abA = addrA.getAddress(); + byte[] abB = addrB.getAddress(); + + // to allow ipv4 and ipv6 address to be "correctly" compared we + // will do the comparison backwards, discarding any leading zeros + + int ofA; + int cbA; + for (ofA = 0, cbA = abA.length; ofA < cbA && abA[ofA] == 0; ++ofA); + int cbsA = cbA - ofA; + + int ofB; + int cbB; + for (ofB = 0, cbB = abB.length; ofB < cbB && abB[ofB] == 0; ++ofB); + int cbsB = cbB - ofB; + + // if the significant parts are of different lengths, then the address + // cannot be equal, so just base the decision upon length + if (cbsA < cbsB) + { + return -1; + } + else if (cbsB < cbsA) + { + return 1; + } + else + { + // significant parts are of comparable length + for (int i = 0; i < cbsA; ++i) + { + int n = (0xFF & (int) abA[ofA + i]) - (0xFF & (int) abB[ofB + i]); // unsigned comparison + if (n != 0) + { + return n < 0 ? -1 : 1; + } + } + return 0; + } + } + + /** + * Reusable instance of the comparator. + */ + public static final InetAddressComparator INSTANCE = + new InetAddressComparator(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetAddressHasher.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetAddressHasher.java new file mode 100644 index 0000000000000..ba94623ba0699 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetAddressHasher.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import com.oracle.coherence.common.base.Hasher; + +import java.net.InetAddress; + + +/** + * InetAddressHasher is a Hasher which supports both IPv4 and IPv6 + * InetAddresses. + * + * @author mf 2011.01.11 + */ +public class InetAddressHasher + implements Hasher + { + /** + * {@inheritDoc} + */ + @Override + public int hashCode(InetAddress addr) + { + if (addr == null) + { + return 0; + } + + // produce a hash which will allow the ipv6 and ipv4 representaion + // of the same address to hash to the same value + int nHash = 0; + for (byte b : addr.getAddress()) + { + nHash += b; + } + return nHash; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(InetAddress addrA, InetAddress addrB) + { + return InetAddressComparator.INSTANCE.compare(addrA, addrB) == 0; + } + + + // ----- constants ------------------------------------------------------ + + /** + * Default instance of the InetAddressHasher. + */ + public static final InetAddressHasher INSTANCE = new InetAddressHasher(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetAddresses.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetAddresses.java new file mode 100644 index 0000000000000..00afb4d59d4e4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetAddresses.java @@ -0,0 +1,1470 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.base.Predicate; +import com.oracle.coherence.common.base.Timeout; +import com.oracle.coherence.common.collections.ConcurrentHashMap; +import com.oracle.coherence.common.internal.net.MultiplexedSocketProvider; +import com.oracle.coherence.common.util.Duration; + +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicReference; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStream; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + + +/** +* Helper class that encapsulates common InetAddress functionality. +*/ +public abstract class InetAddresses + { + /** + * Obtain the "best" local host address which matches the supplied predicate. + * + * @param predicate the predicate to match + * + * @return the InetAddress + * + * @throws UnknownHostException if no match can be found + */ + public static InetAddress getLocalAddress(Predicate predicate) + throws UnknownHostException + { + InetAddress addrLocal = InetAddress.getLocalHost(); + InetAddress addrBest = null; + int nMTUBest = 0; + int nMTULocal = 0; + Map mapAddr = getAllLocalMTUs(); + + for (Iterator iter = mapAddr.entrySet().iterator(); iter.hasNext();) + { + Map.Entry entry = (Map.Entry) iter.next(); + InetAddress addr = (InetAddress) entry.getKey(); + + if (!predicate.evaluate(addr)) + { + continue; + } + + // addr is acceptable, prefer a higher MTU. In the case of equal + // MTUs we compare addresses for order to ensure that we will + // return a consistent result between invocations. + Integer oMTU = (Integer) entry.getValue(); + int nMTU = oMTU == null ? 0 : oMTU.intValue(); + + if (addr.equals(addrLocal)) + { + nMTULocal = nMTU; + } + + if (nMTU > nMTUBest || + (nMTU == nMTUBest && compare(addr, addrBest) < 0)) + { + addrBest = addr; + nMTUBest = nMTU; + } + } + + if (addrBest == null) + { + throw new UnknownHostException("No local address matching " + predicate); + } + + // prefer the OS defined localhost assuming all else is equal + return nMTUBest == nMTULocal && predicate.evaluate(addrLocal) + ? addrLocal : addrBest; + } + + /** + * Return the local InetAddress represented by the specified string. + *

+ * Note: if an explicit address is supplied and it is non-local then it will be assumed to + * be an external NAT address. + *

+ * + * @param sAddr the string address, either hostname, literal ip, or subnet and mask + * + * @return the InetAddress + * + * @throws UnknownHostException if the string cannot be resolved + */ + public static InetAddress getLocalAddress(String sAddr) + throws UnknownHostException + { + if (sAddr == null || sAddr.isEmpty() || sAddr.equals("localhost")) + { + return getLocalHost(); + } + else if (sAddr.equals("0.0.0.0") || sAddr.equals("::0") || sAddr.equals("::")) // wildcard + { + return ADDR_ANY; + } + else if (sAddr.indexOf('/') != -1) // CIDR + { + return getLocalAddress(new IsSubnetMask(sAddr)); + } + else + { + return InetAddress.getByName(sAddr); + } + } + + /** + * Obtain the local host address. If at all possible, ensure that the + * returned address is not a loopback, wildcard or a link local address. + * + * @return the InetAddress that is the best fit for the local host address + * + * @throws UnknownHostException if no match can be found + * + * @see + * Sun's Bug Parade + */ + public static InetAddress getLocalHost() + throws UnknownHostException + { + InetAddress addrLocal = s_addrLocalhost; + if (addrLocal != null && + isLocalAddress(addrLocal)) // avoid returning dropped DHCP + { + return addrLocal; + } + + boolean fNonRoutable = false; + Set setExclude = null; + for (;;) + { + try + { + final boolean fNonRoutablePass = fNonRoutable; + final Set setExcludePass = setExclude; + InetAddress addr = getLocalAddress(new Predicate() + { + @Override + public boolean evaluate(InetAddress addr) + { + return (fNonRoutablePass || IsRoutable.INSTANCE.evaluate(addr)) && // avoids loopback initially + (setExcludePass == null || !setExcludePass.contains(addr)); // avoid previous rejections + } + }); + + if (!isLocalReachableAddress(addr, 300)) // avoids temporary IPv6 addresses, and VPN blocked addresses + { + if (setExclude == null) + { + setExclude = new HashSet<>(); + } + setExclude.add(addr); + continue; + } + + return s_addrLocalhost = addr; // cache and return + } + catch (UnknownHostException e) + { + if (fNonRoutable) + { + return InetAddress.getLocalHost(); + } + fNonRoutable = true; // include non-routable on next pass, i.e. allow loopback + } + } + } + + /** + * The IsRoutable predicate evaluates to true for any InetAddress which is + * externally routable. + */ + public static class IsRoutable + implements Predicate + { + /** + * {@inheritDoc} + */ + @Override + public boolean evaluate(InetAddress addr) + { + return !addr.isLoopbackAddress() && + !addr.isAnyLocalAddress() && + !addr.isLinkLocalAddress(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return "is routable"; + } + + /** + * Singleton instance. + */ + public static final IsRoutable INSTANCE = new IsRoutable(); + } + + /** + * IsSubnetMask predicate evaluates to true for any address with matches the + * pattern for the masked bits + */ + public static class IsSubnetMask + implements Predicate + { + /** + * Construct a predicate for the given pattern and mask. + * + * @param addrPattern the pattern to match + * @param addrMask the mask identifying the portion of the pattern to match + */ + public IsSubnetMask(InetAddress addrPattern, InetAddress addrMask) + { + m_abPattern = addrPattern.getAddress(); + m_abMask = addrMask.getAddress(); + m_sDescription = addrPattern + "/" + addrMask; + + if (m_abPattern.length != m_abMask.length) + { + throw new IllegalArgumentException( + "pattern and mask must be of the same byte length"); + } + } + + /** + * Construct a predicate for the given pattern and mask bit count. + * + * @param addrPattern the pattern to match + * @param cMaskBits the number of mask bits + */ + public IsSubnetMask(InetAddress addrPattern, int cMaskBits) + { + m_abPattern = addrPattern.getAddress(); + m_abMask = new byte[m_abPattern.length]; + m_sDescription = addrPattern + "/" + cMaskBits; + + setSubnetMask(m_abMask, cMaskBits); + } + + /** + * Construct a predicate for the given pattern and slash mask. + * + * @see + * CIDR Notation + * + * @param sAddr the pattern and mask + */ + public IsSubnetMask(String sAddr) + { + try + { + m_sDescription = sAddr; + + int ofSubnetMask = sAddr.indexOf('/'); + InetAddress addr = ofSubnetMask == -1 + ? InetAddress.getByName(sAddr) + : InetAddress.getByName(sAddr.substring(0, ofSubnetMask)); + + byte[] abPattern = m_abPattern = addr.getAddress(); + + if (ofSubnetMask == -1 || sAddr.indexOf('.', ofSubnetMask) == -1) + { + byte[] abMask = m_abMask = new byte[abPattern.length]; + + setSubnetMask(abMask, ofSubnetMask == -1 + ? abPattern.length * 8 // no slash == full mask + : Integer.valueOf(sAddr.substring(ofSubnetMask + 1))); + } + else + { + m_abMask = InetAddress.getByName(sAddr.substring + (ofSubnetMask + 1)).getAddress(); + } + } + catch (UnknownHostException e) + { + throw new IllegalArgumentException("dns names are not supported"); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean evaluate(InetAddress addr) + { + byte[] ab = addr.getAddress(); + byte[] abPat = m_abPattern; + byte[] abMask = m_abMask; + + if (ab.length != abPat.length) + { + return false; + } + for (int i = ab.length - 1; i >= 0; --i) + { + byte bMask = abMask[i]; + if ((ab[i] & bMask) != (abPat[i] & bMask)) + { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return "IsSubnetMask(" + m_sDescription + ")"; + } + + protected String m_sDescription; + protected byte[] m_abPattern; + protected byte[] m_abMask; + } + + /** + * Compare two InetAddresses for ordering purposes. + * + * @param addrA the first address to compare + * @param addrB the second address to compare + * + * @return a negative integer, zero, or a positive integer as the first + * argument is less than, equal to, or greater than the second + */ + public static int compare(InetAddress addrA, InetAddress addrB) + { + return InetAddressComparator.INSTANCE.compare(addrA, addrB); + } + + /** + * Return the MTU for the specified local address. + * + * @param addr the local address + * + * @return the MTU of the specified address, or 0 if the MTU can not be + * identified + */ + public static int getLocalMTU(InetAddress addr) + { + try + { + NetworkInterface ni = NetworkInterface.getByInetAddress(addr); + if (ni == null) + { + throw new IllegalArgumentException("The specified address \"" + + addr + "\" is not a local address."); + } + return getLocalMTU(ni); + } + catch (SocketException e) {} + + return 0; + } + + /** + * Return the MTU for the specified NetworkInterface. + * + * @param ni the network interface + * + * @return the MTU of the specified address, or 0 if the MTU can not be + * identified + */ + public static int getLocalMTU(NetworkInterface ni) + { + try + { + int nMTU = ni.getMTU(); + // Windows 7 + JRE 1.6 reports -1 for loopback, apparently + // converting a unsigned 32b int max value into a signed 32b int + return nMTU < 0 ? Integer.MAX_VALUE : nMTU; + } + catch (Exception e) {} + + return 0; + } + + /** + * Return this machines MTU. + * + * @return the machine's MTU + */ + public static int getLocalMTU() + { + // we couldn't find it, either because of the above described error, or because the socket is bound to a + // NAT address; just return the box's minimum MTU + + int nMtu = 65535; // start with largest allowed by IP + try + { + for (Enumeration enmrNI = NetworkInterface.getNetworkInterfaces(); + enmrNI != null && enmrNI.hasMoreElements();) + { + int nMtuNic = InetAddresses.getLocalMTU((NetworkInterface) enmrNI.nextElement()); + if (nMtuNic > 0) + { + nMtu = Math.min(nMtu, nMtuNic); + } + } + } + catch (SocketException e) {} + + return nMtu; + } + + /** + * Return a local address which is in the same subnet as the specified address. + * + * @param addr the address to find a local peer of + * + * @return a local address in the same subnet as the specified address or null if none is found + */ + public static InetAddress getLocalPeer(InetAddress addr) + { + for (InetAddress addrLocal : getAllLocalAddresses()) + { + if (isInSubnet(addr, /*addrSubnet*/ addrLocal, getLocalSubnetLength(addrLocal))) + { + return addrLocal; + } + } + + return null; + } + + /** + * Return true if the specified address is part of the specified subnet + * + * @param addr the address to test + * @param addrSubnet the subnet + * @param cBitSubnet the number of valid bits in the subnet address + * + * @return return true if the specified address is in the specified subnet + */ + public static boolean isInSubnet(InetAddress addr, InetAddress addrSubnet, int cBitSubnet) + { + byte[] ab = addr.getAddress(); + byte[] abPat = addrSubnet.getAddress(); + + if (ab.length != abPat.length) + { + return false; + } + + for (int i = 0; i < ab.length && cBitSubnet > 0; ++i) + { + byte bMask; + if (cBitSubnet < 8) + { + bMask = 0; + for (int j = 0; j < cBitSubnet; ++j) + { + bMask |= (1 << cBitSubnet - j); + } + } + else + { + bMask = (byte) 0x0FF; + } + + if ((ab[i] & bMask) != (abPat[i] & bMask)) + { + return false; + } + + cBitSubnet -= 8; + } + + return true; + } + + /** + * Return the InetAddress representing the subnet for the specified local address. + * + * @param addr the local address + * + * @return the subnet address + */ + public static InetAddress getLocalSubnetAddress(InetAddress addr) + { + int cBits = getLocalSubnetLength(addr); + byte[] abAddr = addr.getAddress(); + + for (int i = 0, c = abAddr.length; i < c; ++i) + { + if (cBits == 0) + { + // zero out entire byte + abAddr[i] = 0; + } + else if (cBits < 8) + { + // partial byte is part of subnet, zero out remainder + byte cZero = (byte) (8 - cBits); + abAddr[i] = (byte) ((abAddr[i] >> cZero) << cZero); + cBits = 0; + } + else + { + // full byte is part of subnet + cBits -= 8; + } + } + + try + { + return InetAddress.getByAddress(abAddr); + } + catch (UnknownHostException e) + { + throw new IllegalStateException(e); + } + } + + /** + * Return the bit length for the subnet of the specified local address. + * + * @param addr the local address + * + * @return the subnet address + */ + public static short getLocalSubnetLength(InetAddress addr) + { + try + { + NetworkInterface ni = NetworkInterface.getByInetAddress(addr); + if (ni == null) + { + throw new IllegalArgumentException("The specified address \"" + + addr + "\" is not a local address."); + } + + for (InterfaceAddress addrIf : ni.getInterfaceAddresses()) + { + if (addrIf.getAddress().equals(addr)) + { + short cBits = addrIf.getNetworkPrefixLength(); + + // for some reason 0 is sometimes returned for ipv4s, there is no correct + // answer here, but anything is better then 0, note in such cases the + // broadcast address is also null so we can't even try to derive it + return cBits == 0 ? 8 : cBits; + } + } + } + catch (IOException e) + { + throw new IllegalStateException(e); + } + + throw new IllegalStateException(); + } + + /** + * Return a list of all InetAddress objects bound to all the network + * interfaces on this machine. + * + * @return a list of InetAddress objects + */ + public static List getAllLocalAddresses() + { + return getLocalAddresses(o -> true); + } + + /** + * Return a list of InetAddresses bound to all the network + * interfaces on this machine matching the specified predicate. + * + * @param predicate the predicate to match + * + * @return a list of InetAddress objects + */ + public static List getLocalAddresses(Predicate predicate) + { + List listAddr = new ArrayList<>(); + + try + { + for (Enumeration enmrNI = NetworkInterface.getNetworkInterfaces(); + enmrNI != null && enmrNI.hasMoreElements();) + { + NetworkInterface ni = (NetworkInterface) enmrNI.nextElement(); + for (Enumeration enmrAddr = ni.getInetAddresses(); + enmrAddr.hasMoreElements();) + { + InetAddress addr = enmrAddr.nextElement(); + if (predicate.evaluate(addr)) + { + listAddr.add(addr); + } + } + } + } + catch (SocketException e) {} + + return listAddr; + } + + /** + * Returns a collection of all local bindable addresses. + * + * Note: this collection will also include NAT address which have been previously + * {@link #isNatLocalAddress(InetAddress, int, int, int) identified}. I.e. external addresses which can be + * bound to via {@link TcpSocketProvider#MULTIPLEXED multiplexed socket provider}. + * + * @return a list of all local bindable addresses + * + * @since 12.2.1 + */ + public static Collection getLocalBindableAddresses() + { + if (!s_fDequeAddrBindablePopulated) + { + synchronized (s_dequeAddrBindable) + { + if (!s_fDequeAddrBindablePopulated) + { + s_dequeAddrBindable.addAll(getLocalAddresses( + new Predicate() + { + public boolean evaluate(InetAddress addr) + { + if (addr.isAnyLocalAddress()) + { + return false; + } + else if (addr instanceof Inet4Address) + { + return true; + } + else + { + // only return Inet6Addresses which are bindable + try (ServerSocket socket = new ServerSocket(0, 0, addr)) + { + return true; + } + catch (IOException e) + { + } + return false; + } + } + })); + s_fDequeAddrBindablePopulated = true; + } + } + } + + return Collections.unmodifiableCollection(s_dequeAddrBindable); + } + + /** + * Return a map of all InetAddress and MTUs bound to all the network + * interfaces on this machine. If the MTU cannot be obtained a null + * value will be used. + * + * @return a map of Integer MTU values keyed by the corresponding InetAddress + */ + public static Map getAllLocalMTUs() + { + Map mapAddr = new HashMap(); + + try + { + for (Enumeration enmrNI = NetworkInterface.getNetworkInterfaces(); + enmrNI != null && enmrNI.hasMoreElements();) + { + NetworkInterface ni = (NetworkInterface) enmrNI.nextElement(); + int nMTU = getLocalMTU(ni); + Object oMTU = nMTU == 0 ? null : Integer.valueOf(nMTU); + for (Enumeration enmrAddr = ni.getInetAddresses(); + enmrAddr.hasMoreElements();) + { + mapAddr.put(enmrAddr.nextElement(), oMTU); + } + } + } + catch (SocketException e) {} + + return mapAddr; + } + + /** + * Clean an IPv6 address by removing the brackets. + * + * @param sAddr an address + * + * @return an IPv6 address without brackets or original string if not IPv6 + * literal + */ + private static String unbracketAddressString(String sAddr) + { + if (sAddr.charAt(0) == '[') + { + int ofBracket = sAddr.indexOf(']', 1); + if (ofBracket < 0) + { + throw new IllegalArgumentException("invalid IPv6 address"); + } + sAddr = sAddr.substring(1, ofBracket); + } + return sAddr; + } + + + /** + * Determine if a host string is a hostname. + * + * @param sHost the host string + * + * @return true if host string is a hostname + */ + public static boolean isHostName(String sHost) + { + return sHost.length() != 0 && sHost.indexOf(":") < 0 + && !sHost.matches("^\\d+\\.\\d+\\.\\d+\\.\\d+$"); + } + + /** + * Return true if the specified address string represents the wildcard address + * + * @param sAddr the address to test + * + * @return true if the specified address is the wildcard address + */ + public static boolean isAnyLocalAddress(String sAddr) + { + return sAddr != null && (sAddr.equals("::0") || sAddr.equals("0.0.0.0")); + } + + /** + * Return true if the supplied port is believed to be in the ephemeral port range. + * + * Note that while a port of 0 can used to request an ephemeral port it is not itself + * considered to be ephemeral. + * + * @param nPort the port to query + * + * @return true if the port is believed to be ephemeral + */ + public static boolean isEphemeral(int nPort) + { + if (nPort < 0 || nPort > 65535) // decode multiplexed ports + { + nPort = MultiplexedSocketProvider.getBasePort(nPort); + } + + int nLow; + int nHigh; + String sOS = System.getProperty("os.name").toLowerCase().trim(); + + if ("linux".equals(sOS)) + { + // we have custom linux code because it doesn't use the IANA suggested range and at least RedHat uses a + // stupid lower bound of 1024 so we're likely to run into random bind failures on RedHat and presumably its + // derivatives. + + // read /proc to get actual range and reservations, these aren't actual files on disk but rather virtual + // files served up by the kernel and access should be immediate so we don't try to cache the results + // see https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt or details on the formats of these + // files note despite the names these files are for both ipv4 and ipv6 as well as both UDP and TCP + + // try to check if it reserved, if so then it can't be ephemeral + try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/sys/net/ipv4/ip_local_reserved_ports")))) + { + StringTokenizer sTok = new StringTokenizer(in.readLine(), ","); + while (sTok.hasMoreElements()) + { + String sReserved = sTok.nextToken().trim(); + int ofRange = sReserved.indexOf('-'); + if (ofRange == -1) + { + if (Integer.parseInt(sReserved) == nPort) + { + return false; + } + } + else + { + int nLowRes = Integer.parseInt(sReserved.substring(0, ofRange).trim()); + int nHighRes = Integer.parseInt(sReserved.substring(ofRange + 1).trim()); + + if (nPort >= nLowRes && nPort <= nHighRes) + { + return false; + } + } + } + } + catch (Throwable t) {} + + // try to read configured ephemeral range + try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("/proc/sys/net/ipv4/ip_local_port_range")))) + { + StringTokenizer sTok = new StringTokenizer(in.readLine()); + nLow = Integer.parseInt(sTok.nextToken().trim()); + nHigh = Integer.parseInt(sTok.nextToken().trim()); + } + catch (Throwable t) + { + // use standard Linux defaults + nLow = 32768; + nHigh = 61000; + } + } + else // assume IANA suggested defaults which is quite typical other then for Linux + { + nLow = 49152; + nHigh = 65535; + } + + return nPort >= nLow && nPort <= nHigh; + } + + /** + * Return the InetSocketAddress represented by the specified string. + * + * @param sAddr a legal address string + * @param nPort a default port to use if sAddr does not contain one + * + * @return a non-null InetSocketAddress object + * + * @throws UnknownHostException if no match can be found + */ + public static InetSocketAddress getSocketAddress(String sAddr, int nPort) + throws UnknownHostException + { + if (sAddr == null) + { + throw new IllegalArgumentException("address cannot be null"); + } + + String sHost = unbracketAddressString(sAddr); + int ofPort; + if (sAddr.equals(sHost)) + { + // ipv4 address + // still need to parse for both host and port + ofPort = sAddr.lastIndexOf(':'); + int ofEnd = ofPort != -1 ? ofPort : sAddr.length(); + sHost = sAddr.substring(0, ofEnd); + } + else + { + // ipv6 address + // we already have the host, just grab the port if it exists + ofPort = sAddr.indexOf(':', sAddr.indexOf(']')); + } + + if (ofPort != -1) + { + // pull port off of address + nPort = Integer.parseInt(sAddr.substring(ofPort + 1)); + } + if (sHost.equals("*")) + { + return new InetSocketAddress(nPort); + } + if (sHost.equals("localhost") || sAddr.length() == 0) + { + return new InetSocketAddress(InetAddresses.getLocalHost(), nPort); + } + + InetSocketAddress addr = new InetSocketAddress(sHost, nPort); + if (addr.getAddress() == null) + { + throw new UnknownHostException("could not resolve address \"" + sAddr + "\""); + } + return addr; + } + + /** + * Obtain the {@link InetAddress} from a given {@link SocketAddress}. + *

+ * Throws an {@link IllegalArgumentException} if sockAddr is not a + * {@link InetSocketAddress} or {@link InetSocketAddress32}. + * + * @param sockAddr the {@link SocketAddress} + * + * @return the {@link InetAddress} + * + * @since 12.2.1 + */ + public static InetAddress getAddress(SocketAddress sockAddr) + { + if (sockAddr instanceof InetSocketAddress) + { + return ((InetSocketAddress) sockAddr).getAddress(); + } + if (sockAddr instanceof InetSocketAddress32) + { + return ((InetSocketAddress32) sockAddr).getAddress(); + } + throw new IllegalArgumentException("Cannot obtain an address from class " + sockAddr.getClass()); + } + + /** + * Obtain the port from a given {@link SocketAddress}. + *

+ * Throws an {@link IllegalArgumentException} if sockAddr is not a + * {@link InetSocketAddress} or {@link InetSocketAddress32}. + * + * @param sockAddr the {@link SocketAddress} + * + * @return the port + * + * @since 12.2.1 + */ + public static int getPort(SocketAddress sockAddr) + { + if (sockAddr instanceof InetSocketAddress) + { + return ((InetSocketAddress) sockAddr).getPort(); + } + if (sockAddr instanceof InetSocketAddress32) + { + return ((InetSocketAddress32) sockAddr).getPort(); + } + throw new IllegalArgumentException("Cannot obtain a port from class " + sockAddr.getClass()); + } + + /** + * Return a new SocketAddress of the same type but with the specified address + *

+ * Throws an {@link IllegalArgumentException} if sockAddr is not a + * {@link InetSocketAddress} or {@link InetSocketAddress32}. + * + * @param sockAddr the {@link SocketAddress} + * @param addr the new address + * + * @return the new SocketAddress + * + * @since 12.2.3 + */ + public static SocketAddress setAddress(SocketAddress sockAddr, InetAddress addr) + { + if (sockAddr instanceof InetSocketAddress) + { + return new InetSocketAddress(addr, ((InetSocketAddress) sockAddr).getPort()); + } + if (sockAddr instanceof InetSocketAddress32) + { + return new InetSocketAddress32(addr, ((InetSocketAddress32) sockAddr).getPort()); + } + throw new IllegalArgumentException("Cannot set address for class " + sockAddr.getClass()); + } + + + /** + * Return a new SocketAddress of the same type but with the specified port. + *

+ * Throws an {@link IllegalArgumentException} if sockAddr is not a + * {@link InetSocketAddress} or {@link InetSocketAddress32}. + * + * @param sockAddr the {@link SocketAddress} + * @param nPort the new port + * + * @return the new SocketAddress + * + * @since 12.2.3 + */ + public static SocketAddress setPort(SocketAddress sockAddr, int nPort) + { + if (sockAddr instanceof InetSocketAddress) + { + return new InetSocketAddress(((InetSocketAddress) sockAddr).getAddress(), nPort); + } + if (sockAddr instanceof InetSocketAddress32) + { + return new InetSocketAddress32(((InetSocketAddress32) sockAddr).getAddress(), nPort); + } + throw new IllegalArgumentException("Cannot set port for class " + sockAddr.getClass()); + } + + /** + * Return true if the specified address is a local address. + * + * @param addr the address to check + * + * @return true if the address is a local address + */ + public static boolean isLocalAddress(InetAddress addr) + { + try + { + return addr.isLoopbackAddress() || addr.isAnyLocalAddress() || NetworkInterface.getByInetAddress(addr) != null; + } + catch (SocketException e) + { + return false; + } + } + + /** + * Return true iff the specified address is local and bindable. + * + * @param addr the address to test + * + * @return true iff the specified address is local and bindable + */ + public static boolean isLocalBindableAddress(InetAddress addr) + { + try (ServerSocket socket = new ServerSocket(0, 0, addr)) + { + return true; + } + catch (IOException e) + { + return false; + } + } + + /** + * Return true iff the specified address is local and reachable. + * + * @param addr the address to test + * @param cMillis the timeout value in milliseconds + * + * @return true iff the specified address is local and reachable + */ + public static boolean isLocalReachableAddress(InetAddress addr, int cMillis) + { + try (ServerSocket socket = new ServerSocket(0, 0, addr)) + { + try (Socket client = new Socket()) + { + Blocking.connect(client, socket.getLocalSocketAddress(), cMillis); + return true; + } + } + catch (IOException e) + { + return false; + } + } + + /** + * Return true if any {@link #isNatLocalAddress(InetAddress, int) local NAT} addresses have been identified. + * + * @return true if any local NAT addresses have been identified + */ + public static boolean hasNatLocalAddress() + { + return s_refSetNAT.get() != null; + } + + /** + * Return true iff the specified non-local address is NAT'd to a local address. + * + * @param addr the socket address to test + * + * @return true iff the specified non-local address routes to a local address + */ + public static boolean isNatLocalAddress(SocketAddress addr) + { + return isNatLocalAddress(getAddress(addr), getPort(addr)); + } + + /** + * Return true iff the specified non-local address is NAT'd to a local address. + * + * @param addr the address to test + * @param nPort the port to test + * + * @return true iff the specified non-local address routes to a local address + */ + public static boolean isNatLocalAddress(InetAddress addr, int nPort) + { + return isNatLocalAddress(addr, nPort, nPort); + } + + /** + * Return true iff the specified non-local address is NAT'd to a local address. + * + * This method takes a range of ports to test through, though once there is a single positive match + * no further testing will be preformed. Specification of a port range is only necessary if a firewall + * may block the NAT'd traffic, or if NAT'ing is only available on specific ports. + * + * @param addr the address to test + * @param nPortMin the lower bound on the range of ports to test + * @param nPortMax the upper bound on the range of ports to test + * + * @return true iff the specified non-local address routes to a local address + */ + public static boolean isNatLocalAddress(InetAddress addr, int nPortMin, int nPortMax) + { + return isNatLocalAddress(addr, nPortMin, nPortMax, (int) NAT_CHECK_TIMEOUT); + } + + /** + * Return true iff the specified non-local address is NAT'd to a local address. + * + * This method takes a range of ports to test through, though once there is a single positive match + * no further testing will be preformed. Specification of a port range is only necessary if a firewall + * may block the NAT'd traffic, or if NAT'ing is only available on specific ports. + * + * @param addr the address to test + * @param nPortMin the lower bound on the range of ports to test + * @param nPortMax the upper bound on the range of ports to test + * @param cMillis the timeout value in milliseconds + * + * @return true iff the specified non-local address routes to a local address + */ + public static boolean isNatLocalAddress(InetAddress addr, int nPortMin, int nPortMax, int cMillis) + { + if (isLocalAddress(addr)) + { + return false; + } + + Set setNAT = s_refSetNAT.get(); + if (setNAT == null) + { + setNAT = Collections.newSetFromMap(new ConcurrentHashMap<>()); + if (!s_refSetNAT.compareAndSet(null, setNAT)) + { + setNAT = s_refSetNAT.get(); + } + } + + // It's possible we've already bound to this port and are retesting because of some other multiplexed + // socket. Here we use multiplexed sockets with ephemeral sub-ports, this ensures we aren't blocked + // by and that we don't block other concurrent users of the same port. Also we allow the specified + // ports to be multiplexed, we just strip of the sub port since ultimately we only need to check if the + // base is NAT'd. + nPortMin = MultiplexedSocketProvider.getBasePort(nPortMin); + nPortMax = Math.max(nPortMin, MultiplexedSocketProvider.getBasePort(nPortMax)); + + InetSocketAddress addrSockBase = new InetSocketAddress(addr, nPortMin); + if (setNAT.contains(addrSockBase) || // test if someone has already verified this range (from the base) + setNAT.contains(new InetSocketAddress(addr, 0))) // or has verified that all ports appear to be mapped + { + return true; + } + + try (Timeout t = Timeout.after(cMillis)) + { + try (ServerSocket server = TcpSocketProvider.MULTIPLEXED.openServerSocket()) + { + for (int nPort = nPortMin; nPort <= nPortMax && !server.isBound(); ++nPort) + { + try + { + // bind to ephemeral address, and specified port + server.bind(new InetSocketAddress32(MultiplexedSocketProvider.getPort(nPort, 0))); + } + catch (IOException e) + { + if (nPort == nPortMax) + { + // we couldn't find any free ports on which to conduct the test + return false; + } + // else; try next port + } + } + + // test if we can connect to the NAT address and port and receive the connection on our binding + try (Socket clientOut = TcpSocketProvider.MULTIPLEXED.openSocket()) + { + Blocking.connect(clientOut, new InetSocketAddress32(addr, server.getLocalPort())); + + final byte[] MAGIC = generateMagic(); + + try (OutputStream out = clientOut.getOutputStream()) + { + out.write(MAGIC); + } + + // This timeout would seem to be allowed to be set much shorter, we've already established the + // connection above so it should be ready to come out without any delay. We don't want to assume + // too much about how the underlying sockets work, and it is possible that the inbound connection + // isn't available immediately. In fact since we use multiplexed sockets this is quite true as we + // have to wait for the header, and connect finishing only ensures that the header has been sent, + // it may not have yet been received, thus the full timeout is quite relevant. + server.setSoTimeout((int) Timeout.remainingTimeoutMillis()); + + try (Socket clientIn = server.accept()) + { + byte[] abIn = new byte[MAGIC.length]; + try (InputStream is = clientIn.getInputStream()) + { + // either 8 bytes were read or the connection was closed; regardless check what was read + // and compare to what is expected + is.read(abIn); + } + + // verify that the connection is truly from us by ensuring the generated 'magic' payload + // is what we receive; we can not assume any information about source ip / port as when + // routed through a NAT that NAT can rewrite the source IP to appear like it is the NAT + // host in addition to using a different port + if (Arrays.equals(abIn, MAGIC)) + { + // cache the addr (with base port) to avoid all this mess on future checks + // we don't cache failures as most failures would be timeouts which could be transient + setNAT.add(addrSockBase); + + // we add NAT addresses to the start of the cached bindable address list to give them + // priority when used as a source in #getRoutes + synchronized (s_dequeAddrBindable) + { + InetAddress addrIP = addrSockBase.getAddress(); + if (!s_dequeAddrBindable.contains(addrIP)) + { + s_dequeAddrBindable.addFirst(addrIP); + } + } + return true; + } + + return false; + } + } + } + } + catch (IOException | InterruptedException e) + { + return false; + } + } + + /** + * Return an InetAddress object given the raw IP address. + * + * @param abAddr the raw IP address in network byte order + * + * @return the InetAddress object + * + * @throws UnknownHostException if no match can be found + */ + public static InetAddress getByAddress(byte[] abAddr) + throws UnknownHostException + { + if (abAddr == null) + { + return null; + } + + return InetAddress.getByAddress(abAddr); + } + + /** + * Converts an IPv4 compatible address to a long value. + * + * @param addr an instance of InetAddress to convert to a long + * + * @return a long value holding the IPv4 address + */ + public static long toLong(InetAddress addr) + { + byte[] ab = addr.getAddress(); + int of = ab.length == 4 ? 0 : 12; + return ((((long) ab[of + 0]) & 0xFFL) << 24) + | ((((long) ab[of + 1]) & 0xFFL) << 16) + | ((((long) ab[of + 2]) & 0xFFL) << 8) + | ((((long) ab[of + 3]) & 0xFFL) ); + } + + /** + * Select the appropriate source addresses for connecting to the specified destination addresses. + * + * @param collSource the source addresses + * @param collDest the destination addresses + * + * @return the source addresses which are believed to have routes to the destinations + */ + public static Collection getRoutes(Iterable collSource, Iterable collDest) + { + // find source address with the largest IP overlap with a destination address, i.e. most likely on the same subnet + List listAddr = new ArrayList<>(); + int cbBest = 0; + + for (InetAddress addrDest : collDest) + { + byte[] abDest = addrDest.getAddress(); + for (InetAddress addrSrc : collSource) + { + byte[] abSrc = addrSrc.getAddress(); + int ofSrc = 0; + int ofDest = 0; + if (abDest.length != abSrc.length) + { + // comparing ipv6 vs ipv4, but it could be an ipv6 encoded ipv4, skip over leading zeros + for (; ofDest < abDest.length && abDest[ofDest] == 0; ++ofDest); + for (; ofSrc < abSrc.length && abSrc [ofSrc] == 0; ++ofSrc); + + if (abDest.length - ofDest != abSrc.length - ofSrc) + { + continue; // not comparable; move onto next source + } + } + + // Note: the following comparison is byte based, technically it should be bit based though subnets + // are in practice never really split that way + int cbEqual = 0; + for (int cb = abSrc.length - ofSrc; cbEqual < cb && abSrc[ofSrc + cbEqual] == abDest[ofDest + cbEqual]; ++cbEqual); + + if (cbEqual >= cbBest) + { + // addrSrc is at least as good as anything we've found so far + // if it better then toss all others + if (cbEqual > cbBest) + { + cbBest = cbEqual; + listAddr.clear(); // discard all worse matches + } + listAddr.add(addrSrc); + } + } + } + + return listAddr; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Set the specified number of bits of a byte array representing a subnet + * mask. + * + * @param ab the array to fill + * @param cBits the number of bits to set + */ + protected static void setSubnetMask(byte[] ab, int cBits) + { + if (ab.length * 8 < cBits) + { + throw new IllegalArgumentException("subnet mask of " + cBits + + " exceeds address length of " + ab.length * 8); + } + + int cBytes = cBits / 8; + for (int i = 0; i < cBytes; ++i) + { + ab[i] = -1; + } + + for (int i = 0, c = cBits % 8; i < c; ++i) + { + ab[cBytes] |= 1 << (7 - i); + } + } + + /** + * Return a unique byte array. + * + * @return a unique byte array + */ + protected static byte[] generateMagic() + { + int nRnd = ThreadLocalRandom.current().nextInt(); + return new byte[] + { + 0x52, 0x41, 0x4A, 0x41, + (byte) ((nRnd >> 24) & 0xFF), + (byte) ((nRnd >> 16) & 0xFF), + (byte) ((nRnd >> 8) & 0xFF), + (byte) ((nRnd) & 0xFF) + }; + } + + // ----- data fields and constants -------------------------------------- + + /** + * The value of system property "java.net.preferIPv4Stack". + * + * @see + * Networking IPv6 User Guide + */ + public static final boolean PreferIPv4Stack = Boolean.getBoolean("java.net.preferIPv4Stack"); + + /** + * The value of system property "java.net.preferIPv6Addresses". + * + * @see + * Networking IPv6 User Guide + */ + public static final boolean PreferIPv6Addresses = Boolean.getBoolean("java.net.preferIPv6Addresses"); + + /** + * The wildcard address. + */ + public static final InetAddress ADDR_ANY = new InetSocketAddress((InetAddress) null, 0).getAddress(); + + /** + * Cached localhost address. + */ + private static InetAddress s_addrLocalhost; + + /** + * Cached local NAT addresses. + */ + private static final AtomicReference> s_refSetNAT = new AtomicReference<>(); + + /** + * Cached local (and NAT) bindable addresses. + */ + private static final Deque s_dequeAddrBindable = new ConcurrentLinkedDeque<>(); + + /** + * True once s_dequeAddrBindable has been populated with local dddresses. + */ + private static volatile boolean s_fDequeAddrBindablePopulated; + + /** + * The default timeout for testing if an address is NAT'd. + * + * We use a rather high default value here. The reason is that it is assumed that the isNatLocalAddress will + * normally pass as the caller must have had some indication which would suggest that it is in fact a local NAT + * address. The test will involve going out through the NAT, and as such could have packet loss so we want to + * allow TCP enough time to retry a connect if needed. So successes should be fast, but we allow them to be slow + * in case of packet loss. A failure will also normally be fast. If the NAT address references another machine + * and that machine has no listener on the port then we'll get a reject and be done quickly. So we're only slow + * on a failure if there we manage to connect to someone else, or if our connect attempt has no accept or reject. + */ + protected static final long NAT_CHECK_TIMEOUT = new Duration( + System.getProperty(InetAddresses.class.getName() + ".natCheckTimeout", "10s")) + .as(Duration.Magnitude.MILLI); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketAddress32.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketAddress32.java new file mode 100644 index 0000000000000..38e50aba615bc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketAddress32.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + +import com.oracle.coherence.common.internal.net.MultiplexedSocketProvider; + +import java.io.ObjectInputStream; +import java.io.IOException; +import java.io.InvalidObjectException; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.InetAddress; +import java.net.UnknownHostException; + + +/** + * InetSocketAddress32 is equivalent to the standard {@link java.net.InetSocketAddress} + * but supports 32 bit port numbers. As such it is not compatible with + * standard TCP based sockets. + * + * @see java.net.InetSocketAddress + * @see MultiplexedSocketProvider + */ +public class InetSocketAddress32 extends SocketAddress + { + // Note: this is a literal copy-n-past of java.net.InetAddress with the + // port restrictions removed + + /* The hostname of the Socket Address + * @serial + */ + private String hostname = null; + /* The IP address of the Socket Address + * @serial + */ + private InetAddress addr = null; + /* The port number of the Socket Address + * @serial + */ + private int port; + + private InetSocketAddress32() { + } + + /** + * Creates a socket address where the IP address is the wildcard address + * and the port number a specified value. + *

+ * A port number of zero will let the system pick up an + * ephemeral port in a bind operation. + *

+ * @param port The port number + * @throws IllegalArgumentException if the port parameter is outside the specified + * range of valid port values. + */ + public InetSocketAddress32(int port) { + this((InetAddress) null, port); + } + + /** + * + * Creates a socket address from an IP address and a port number. + *

+ * A port number of zero will let the system pick up an + * ephemeral port in a bind operation. + *

+ * A null address will assign the wildcard address. + *

+ * @param addr The IP address + * @param port The port number + * @throws IllegalArgumentException if the port parameter is outside the specified + * range of valid port values. + */ + public InetSocketAddress32(InetAddress addr, int port) { + this.port = port; + if (addr == null) + { + try + { + this.addr = InetAddress.getByAddress(new byte[]{0,0,0,0}); + } + catch (UnknownHostException e) + { + throw new RuntimeException(e); + } + } + else + { + this.addr = addr; + } + } + + /** + * Construct an InetSocketAddress32 from an InetSocketAddress. + * + * @param addr the source InetSocketAddress. + */ + public InetSocketAddress32(InetSocketAddress addr) + { + this (addr.getAddress(), addr.getPort()); + } + + /** + * + * Creates a socket address from a hostname and a port number. + *

+ * An attempt will be made to resolve the hostname into an InetAddress. + * If that attempt fails, the address will be flagged as unresolved. + *

+ * If there is a security manager, its checkConnect method + * is called with the host name as its argument to check the permissiom + * to resolve it. This could result in a SecurityException. + *

+ * A port number of zero will let the system pick up an + * ephemeral port in a bind operation. + *

+ * @param hostname the Host name + * @param port The port number + * @throws IllegalArgumentException if the port parameter is outside the range + * of valid port values, or if the hostname parameter is null. + * @throws SecurityException if a security manager is present and + * permission to resolve the host name is + * denied. + * @see #isUnresolved() + */ + public InetSocketAddress32(String hostname, int port) { + if (hostname == null) { + throw new IllegalArgumentException("hostname can't be null"); + } + try { + addr = InetAddress.getByName(hostname); + } catch(UnknownHostException e) { + this.hostname = hostname; + addr = null; + } + this.port = port; + } + + /** + * + * Creates an unresolved socket address from a hostname and a port number. + *

+ * No attempt will be made to resolve the hostname into an InetAddress. + * The address will be flagged as unresolved. + *

+ * A port number of zero will let the system pick up an + * ephemeral port in a bind operation. + *

+ * @param host the Host name + * @param port The port number + * @throws IllegalArgumentException if the port parameter is outside + * the range of valid port values, or if the hostname + * parameter is null. + * @see #isUnresolved() + * @return a InetSocketAddress32 representing the unresolved + * socket address + * @since 1.5 + */ + public static InetSocketAddress32 createUnresolved(String host, int port) { + if (host == null) { + throw new IllegalArgumentException("hostname can't be null"); + } + InetSocketAddress32 s = new InetSocketAddress32(); + s.port = port; + s.hostname = host; + s.addr = null; + return s; + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + + // Check that our invariants are satisfied + if (hostname == null && addr == null) { + throw new InvalidObjectException("hostname and addr " + + "can't both be null"); + } + } + + /** + * Gets the port number. + * + * @return the port number. + */ + public final int getPort() { + return port; + } + + /** + * + * Gets the InetAddress. + * + * @return the InetAdress or null if it is unresolved. + */ + public final InetAddress getAddress() { + return addr; + } + + /** + * Gets the hostname. + * + * @return the hostname part of the address. + */ + public final String getHostName() { + if (hostname != null) + return hostname; + if (addr != null) + return addr.getHostName(); + return null; + } + + /** + * Returns the hostname, or the String form of the address if it + * doesn't have a hostname (it was created using a litteral). + * This has the benefit of not attemptimg a reverse lookup. + * + * @return the hostname, or String representation of the address. + * @since 1.6 + */ + final String getHostString() { + if (hostname != null) + return hostname; + if (addr != null) { + if (addr.getHostName() != null) + return addr.getHostName(); + else + return addr.getHostAddress(); + } + return null; + } + + /** + * Checks whether the address has been resolved or not. + * + * @return true if the hostname couldn't be resolved into + * an InetAddress. + */ + public final boolean isUnresolved() { + return addr == null; + } + + /** + * Constructs a string representation of this InetSocketAddress32. + * This String is constructed by calling toString() on the InetAddress + * and concatenating the port number (with a colon). If the address + * is unresolved then the part before the colon will only contain the hostname. + * + * @return a string representation of this object. + */ + public String toString() { + if (isUnresolved()) { + return hostname + ":" + port; + } else { + return addr.toString() + ":" + port; + } + } + + /** + * Compares this object against the specified object. + * The result is true if and only if the argument is + * not null and it represents the same address as + * this object. + *

+ * Two instances of InetSocketAddress32 represent the same + * address if both the InetAddresses (or hostnames if it is unresolved) and port + * numbers are equal. + * If both addresses are unresolved, then the hostname and the port number + * are compared. + * + * @param obj the object to compare against. + * @return true if the objects are the same; + * false otherwise. + * @see java.net.InetAddress#equals(java.lang.Object) + */ + public final boolean equals(Object obj) { + if (obj == null || !(obj instanceof InetSocketAddress32)) + return false; + InetSocketAddress32 sockAddr = (InetSocketAddress32) obj; + boolean sameIP = false; + if (this.addr != null) + sameIP = this.addr.equals(sockAddr.addr); + else if (this.hostname != null) + sameIP = (sockAddr.addr == null) && + this.hostname.equals(sockAddr.hostname); + else + sameIP = (sockAddr.addr == null) && (sockAddr.hostname == null); + return sameIP && (this.port == sockAddr.port); + } + + /** + * Returns a hashcode for this socket address. + * + * @return a hash code value for this socket address. + */ + public final int hashCode() { + if (addr != null) + return addr.hashCode() + port; + if (hostname != null) + return hostname.hashCode() + port; + return port; + } +} diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketAddressComparator.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketAddressComparator.java new file mode 100644 index 0000000000000..dac8b40a96d97 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketAddressComparator.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import java.net.InetSocketAddress; +import java.net.InetAddress; +import java.net.SocketAddress; +import java.util.Comparator; + + +/** + * Comparator implementation suitable for comparing InetSocketAddress objects. + *

+ * Additionally this comparator supports InetSocketAddress32 objects. + * + * @author mf 2010.11.03 + */ +public class InetSocketAddressComparator + implements Comparator + { + /** + * {@inheritDoc} + */ + public int compare(SocketAddress addrA, SocketAddress addrB) + { + if (addrA == addrB) + { + return 0; + } + else if (addrA == null) + { + return -1; + } + else if (addrB == null) + { + return 1; + } + + InetAddress ipA = getAddress(addrA); + InetAddress ipB = getAddress(addrB); + int n; + + if (ipA == null && ipB == null) + { + String sHostA = getHostName(addrA); + String sHostB = getHostName(addrB); + n = sHostA == sHostB ? 0 // only for null == null + : sHostA == null ? -1 + : sHostB == null ? 1 + : sHostA.compareTo(sHostB); + } + else if (ipA != null && ipB != null) + { + // compare by IP + n = InetAddressComparator.INSTANCE.compare(ipA, ipB); + } + else + { + // not comparable + throw new IllegalArgumentException("cannot compare resolved to unresolved addresses"); + } + + return n == 0 + ? getPort(addrA) - getPort(addrB) + : n; + } + + // ----- helpers -------------------------------------------------------- + + /** + * Return the hostname for the specified SocketAddress. + * + * @param addr the SocketAddress + * + * @return the hostname + */ + static String getHostName(SocketAddress addr) + { + return addr instanceof InetSocketAddress + ? ((InetSocketAddress) addr).getHostName() + : ((InetSocketAddress32) addr).getHostName(); + } + + /** + * Return the InetAddress for the specified SocketAddress. + * + * @param addr the SocketAddress + * + * @return the InetAddress + */ + static InetAddress getAddress(SocketAddress addr) + { + return addr instanceof InetSocketAddress + ? ((InetSocketAddress) addr).getAddress() + : ((InetSocketAddress32) addr).getAddress(); + } + + /** + * Return the port for the specified SocketAddress. + * + * @param addr the SocketAddress + * + * @return the port + */ + static int getPort(SocketAddress addr) + { + return addr instanceof InetSocketAddress + ? ((InetSocketAddress) addr).getPort() + : ((InetSocketAddress32) addr).getPort(); + } + + + /** + * Reusable instance of the comparator. + */ + public static final InetSocketAddressComparator INSTANCE = + new InetSocketAddressComparator(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketAddressHasher.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketAddressHasher.java new file mode 100644 index 0000000000000..3469a00e38e2e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketAddressHasher.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import com.oracle.coherence.common.base.Hasher; +import com.oracle.coherence.common.base.NaturalHasher; + +import java.net.InetAddress; +import java.net.SocketAddress; + + +/** + * InetSocketAddressHasher is a Hasher which supports both IPv4 and IPv6 + * based InetSocketAddresses. + *

+ * Additionally this hasher supports InetSocketAddress32 objects. + * + * @author mf 2011.01.11 + */ +public class InetSocketAddressHasher + implements Hasher + { + /** + * {@inheritDoc} + */ + @Override + public int hashCode(SocketAddress addr) + { + if (addr == null) + { + // null or unsupported type + return 0; + } + InetAddress ip = InetSocketAddressComparator.getAddress(addr); + return InetSocketAddressComparator.getPort(addr) + (ip == null + ? NaturalHasher .INSTANCE.hashCode( + InetSocketAddressComparator.getHostName(addr)) + : InetAddressHasher.INSTANCE.hashCode(ip)); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(SocketAddress addrA, SocketAddress addrB) + { + try + { + return InetSocketAddressComparator.INSTANCE.compare(addrA, addrB) == 0; + } + catch (Exception e) + { + return false; + } + } + + + // ----- constants ------------------------------------------------------ + + /** + * Default instance of the InetSocketAddressHasher. + */ + public static final InetSocketAddressHasher INSTANCE = + new InetSocketAddressHasher(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketProvider.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketProvider.java new file mode 100644 index 0000000000000..84909289435ed --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/InetSocketProvider.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import java.net.SocketAddress; +import java.net.InetSocketAddress; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.net.ServerSocket; +import java.net.Socket; + + +/** + * InetSocketProvider is a SocketProvider which utilizes InetSocketAddresses. + * + * @author mf 2011.01.11 + */ +public abstract class InetSocketProvider + implements SocketProvider + { + /** + * {@inheritDoc} + */ + @Override + public SocketAddress resolveAddress(String sAddr) + { + String sHost; + int nPort; + + if (sAddr.startsWith("[")) + { + // ipv6 formatted: [addr]:port + int ofPort = sAddr.lastIndexOf("]:"); + if (ofPort == 2) + { + throw new IllegalArgumentException("address does not contain an hostname or ip"); + } + else if (ofPort == -1) + { + throw new IllegalArgumentException("address does not contain a port"); + } + + sHost = sAddr.substring(1, ofPort - 1); + nPort = Integer.parseInt(sAddr.substring(ofPort + 2)); + } + else + { + // ipv4 formatted: addr:port + int ofPort = sAddr.lastIndexOf(':'); + if (ofPort == 0) + { + throw new IllegalArgumentException("address does not contain an hostname of ip"); + } + else if (ofPort == -1) + { + throw new IllegalArgumentException("address does not contain a port"); + } + + sHost = sAddr.substring(0, ofPort); + nPort = Integer.parseInt(sAddr.substring(ofPort + 1)); + } + + return new InetSocketAddress(sHost, nPort); + } + + /** + * {@inheritDoc} + */ + @Override + public String getAddressString(Socket socket) + { + InetAddress addr = socket.getInetAddress(); + if (addr == null) + { + return null; + } + + String sAddr = addr.getHostAddress(); + if (sAddr.contains(":")) + { + // ipv6 representation + sAddr = "[" + sAddr + "]"; + } + + return sAddr + ":" + socket.getPort(); + } + + /** + * {@inheritDoc} + */ + @Override + public String getAddressString(ServerSocket socket) + { + InetAddress addr = socket.getInetAddress(); + boolean fAny = addr.isAnyLocalAddress(); + String sAddr; + if (fAny) + { + // replace wildcard address with local hostname as this + try + { + addr = InetAddress.getLocalHost(); + } + catch (UnknownHostException e) {} + + sAddr = addr.getCanonicalHostName(); + } + else + { + // use host ip address + sAddr = addr.getHostAddress(); + } + + if (sAddr.contains(":")) + { + // ipv6 representation + sAddr = "[" + sAddr + "]"; + } + + return sAddr + ":" + socket.getLocalPort(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SSLSettings.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SSLSettings.java new file mode 100644 index 0000000000000..13eebf64d5512 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SSLSettings.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; + + +/** + * SSLSettings provides a means to configure the common aspects of + * SSL properties. + * + * @author bbc + */ +public class SSLSettings + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an empty SSL configuration. + */ + public SSLSettings() + { + } + + /** + * Construct a SSLSettings object copying the values from the + * specified settingsSSL object + * + * @param settingsSSL the object to copy, or null + */ + + public SSLSettings(SSLSettings settingsSSL) + { + if (settingsSSL != null) + { + m_ctx = settingsSSL.getSSLContext(); + m_fClientAuthRequired = settingsSSL.isClientAuthenticationRequired(); + m_hostnameVerifier = settingsSSL.getHostnameVerifier(); + m_asCipherSuitesEnabled = settingsSSL.getEnabledCipherSuites(); + m_asProtocolVersionsEnabled = settingsSSL.getEnabledProtocolVersions(); + } + } + + /** + * Return the SSLContext representing the SSL implementation and + * configuration. + * + * @return the SSLContext + */ + public SSLContext getSSLContext() + { + SSLContext ctx = m_ctx; + try + { + return ctx == null ? SSLContext.getDefault() : ctx; + } + catch (NoSuchAlgorithmException e) + { + throw new IllegalStateException(e); + } + } + + /** + * Specify the SSLContex to utilize. + * + * @param ctx the SSLContext + * + * @return this object + */ + public SSLSettings setSSLContext(SSLContext ctx) + { + m_ctx = ctx; + return this; + } + + /** + * Return true iff produced server sockets will require client + * authentication. + * + * @return true iff client authentication is required + */ + public boolean isClientAuthenticationRequired() + { + return m_fClientAuthRequired; + } + + /** + * Specify if client authentication is required. + * + * @param fRequired true iff client authentication is required + * + * @return this object + */ + public SSLSettings setClientAuthenticationRequired(boolean fRequired) + { + m_fClientAuthRequired = fRequired; + return this; + } + + /** + * Return the SSL HostnameVerifier to be used to verify hostnames + * once an SSL session has been established. + * + * @return the verifier, or null to disable + */ + public HostnameVerifier getHostnameVerifier() + { + return m_hostnameVerifier; + } + + /** + * Specify the HostnameVerifier. + * + * @param verifier the HostnameVerifier + * + * @return this object + */ + public SSLSettings setHostnameVerifier(HostnameVerifier verifier) + { + m_hostnameVerifier = verifier; + return this; + } + + /** + * Return the set of enabled SSL cipher suites. + * + * @return the enabled SSL cipher suites, or null for default + */ + public String[] getEnabledCipherSuites() + { + return m_asCipherSuitesEnabled; + } + + /** + * Specify the enabled cipher suites. + * + * @param asCiphers the enabled ciper suites + * + * @return this object + */ + public SSLSettings setEnabledCipherSuites(String[] asCiphers) + { + m_asCipherSuitesEnabled = asCiphers; + return this; + } + + /** + * Return the set of enabled protocol versions. + * + * @return the enabled protocol versions + */ + public String[] getEnabledProtocolVersions() + { + return m_asProtocolVersionsEnabled; + } + + /** + * Specify the enabled protocol versions. + * + * @param asProtocols the enabled protocol versions + * + * @return this object + */ + public SSLSettings setEnabledProtocolVersions(String[] asProtocols) + { + m_asProtocolVersionsEnabled = asProtocols; + return this; + } + + // ----- data members --------------------------------------------------- + /** + * The SSLContext, default is jvm ssl context. + */ + protected SSLContext m_ctx; + + /** + * True if client authentication is required. + */ + protected boolean m_fClientAuthRequired; + + /** + * The HostnameVerifier, null for default. + */ + protected HostnameVerifier m_hostnameVerifier; + + /** + * The enabled cipher suites or null for default. + */ + protected String[] m_asCipherSuitesEnabled; + + /** + * The enabled protocol versions. + */ + protected String[] m_asProtocolVersionsEnabled; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SSLSocketProvider.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SSLSocketProvider.java new file mode 100644 index 0000000000000..232a7107cce3a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SSLSocketProvider.java @@ -0,0 +1,697 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import java.io.IOException; + +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; + +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import java.security.NoSuchAlgorithmException; + +import java.util.logging.Logger; +import java.util.logging.Level; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; + +import com.oracle.coherence.common.internal.net.ssl.SSLSocket; +import com.oracle.coherence.common.internal.net.ssl.SSLSocketChannel; +import com.oracle.coherence.common.internal.net.ssl.SSLServerSocket; +import com.oracle.coherence.common.internal.net.ssl.SSLServerSocketChannel; +import com.oracle.coherence.common.internal.security.SecurityProvider; + +import com.oracle.coherence.common.util.DaemonThreadFactory; + + + +/** + * SocketProvider that produces instances of socket and channel implementations + * which utilize SSL. + * + * @author mf, jh 2010.04.21 + */ +public class SSLSocketProvider + implements SocketProvider + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an SSLSocketProvider. + */ + public SSLSocketProvider() + { + this (null); + } + + /** + * Construct an SSLSocketProvider. + * + * @param deps the provider dependencies, or null + */ + public SSLSocketProvider(Dependencies deps) + { + m_dependencies = copyDependencies(deps).validate(); + } + + + // ----- SocketProvider interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public SocketAddress resolveAddress(String sAddr) + { + return getDependencies().getDelegateSocketProvider().resolveAddress(sAddr); + } + + /** + * {@inheritDoc} + */ + @Override + public String getAddressString(Socket socket) + { + return getDependencies().getDelegateSocketProvider().getAddressString(socket); + } + + /** + * {@inheritDoc} + */ + @Override + public String getAddressString(ServerSocket socket) + { + return getDependencies().getDelegateSocketProvider().getAddressString(socket); + } + + /** + * {@inheritDoc} + */ + @Override + public Socket openSocket() + throws IOException + { + return new SSLSocket(getDependencies().getDelegateSocketProvider() + .openSocket(), this); + } + + /** + * {@inheritDoc} + */ + @Override + public SocketChannel openSocketChannel() + throws IOException + { + return new SSLSocketChannel(getDependencies().getDelegateSocketProvider() + .openSocketChannel(), this); + } + + /** + * {@inheritDoc} + */ + @Override + public ServerSocket openServerSocket() + throws IOException + { + return new SSLServerSocket(getDependencies().getDelegateSocketProvider() + .openServerSocket(), this); + } + + /** + * {@inheritDoc} + */ + @Override + public ServerSocketChannel openServerSocketChannel() + throws IOException + { + return new SSLServerSocketChannel( + getDependencies().getDelegateSocketProvider() + .openServerSocketChannel(), this); + } + + /** + * {@inheritDoc} + */ + @Override + public SocketProvider getDelegate() + { + return getDependencies().getDelegateSocketProvider(); + } + + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return "SSLSocketProvider(" + getDependencies().toString() + ")"; + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Ensure that supplied session is acceptable. + * + * @param session the current SSLSession; must not be null + * @param socket the socket associated with the session + * + * @throws SSLException if the session is unacceptible + */ + public void ensureSessionValidity(SSLSession session, Socket socket) + throws SSLException + { + if (session == null || socket == null) + { + throw new IllegalArgumentException(); + } + + HostnameVerifier verifier = getDependencies().getHostnameVerifier(); + if (verifier == null || + verifier.verify(socket.getInetAddress().getHostName(), session)) + { + getDependencies().getLogger().log(Level.FINE, "Established " + session.getCipherSuite() + + " connection with " + socket); + } + else + { + throw new SSLException("Unacceptible peer: " + socket); + } + } + + + + /** + * Return the SocketProvider's dependencies. + * + * @return the dependencies + */ + public Dependencies getDependencies() + { + return m_dependencies; + } + + /** + * Produce a shallow copy of the supplied dependencies. + * + * @param deps the dependencies to copy + * + * @return the dependencies + */ + protected DefaultDependencies copyDependencies(Dependencies deps) + { + return new DefaultDependencies(deps); + } + + + // ----- inner interface: Dependencies ---------------------------------- + + /** + * Dependencies specifies all dependency requirements of the SSLSocketProvider. + */ + public interface Dependencies + { + /** + * Return the SocketProvider to use in producing the underling sockets + * which will be wrapped with SSL. + * + * @return the delegate SocketProvider + */ + public SocketProvider getDelegateSocketProvider(); + + /** + * Return the SSLContext representing the SSL implementation and + * configuration. + * + * @return the SSLContext + */ + public SSLContext getSSLContext(); + + /** + * Return a copy of the SSLParameters used by the SSLSocketProvider. + * + * @return a copy of the SSLParameters used by the SSLSocketProvider + */ + public SSLParameters getSSLParameters(); + + /** + * Return true iff produced server sockets will require client + * authentication. + * + * @return true iff client authentication is required + */ + public boolean isClientAuthenticationRequired(); + + /** + * Return the SSL HostnameVerifier to be used to verify hostnames + * once an SSL session has been established. + * + * @return the verifier, or null to disable + */ + public HostnameVerifier getHostnameVerifier(); + + /** + * Return the set of enabled SSL cipher suites. + * + * @return the enabled SSL cipher suites, or null for default + */ + public String[] getEnabledCipherSuites(); + + /** + * Return the set of enabled protocol versions. + * + * @return the enabled protocol versions, or null for default + */ + public String[] getEnabledProtocolVersions(); + + /** + * Return the Executor to use in offloading delegated tasks. + * + * @return the Executor + */ + public Executor getExecutor(); + + /** + * Return the Logger to use. + * + * @return the Logger + */ + public Logger getLogger(); + } + + + // ----- inner class: DefaultDependenceis ------------------------------- + + /** + * DefaultDependenceis is a basic implementation of the Dependencies + * interface provding "setter" methods for each property. + *

+ * Additionally this class serves as a source of default dependency values. + */ + public static class DefaultDependencies + implements Dependencies + { + /** + * Construct a DefaultDependencies object. + */ + public DefaultDependencies() + { + } + + /** + * Construct a DefaultDependencies object copying the values from the + * specified dependencies object + * + * @param deps the dependencies to copy, or null + */ + public DefaultDependencies(Dependencies deps) + { + if (deps != null) + { + m_delegate = deps.getDelegateSocketProvider(); + m_ctx = deps.getSSLContext(); + m_fClientAuthRequired = deps.isClientAuthenticationRequired(); + m_hostnameVerifier = deps.getHostnameVerifier(); + m_asCipherSuitesEnabled = deps.getEnabledCipherSuites(); + m_asProtocolVersionsEnabled = deps.getEnabledProtocolVersions(); + m_executor = deps.getExecutor(); + m_logger = deps.getLogger(); + } + } + + /** + * Apply the specified settingsSSL to the dependencies + * + * @param settingsSSL the SSLSettings object to apply + * + * @return this dependencies object + */ + public DefaultDependencies applySSLSettings(SSLSettings settingsSSL) + { + return this.setSSLContext(settingsSSL.getSSLContext()) + .setClientAuthenticationRequired(settingsSSL.isClientAuthenticationRequired()) + .setHostnameVerifier(settingsSSL.getHostnameVerifier()) + .setEnabledCipherSuites(settingsSSL.getEnabledCipherSuites()) + .setEnabledProtocolVersions(settingsSSL.getEnabledProtocolVersions()); + } + + // ----- Dependencies interface --------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public SocketProvider getDelegateSocketProvider() + { + return m_delegate; + } + + /** + * Specify the SocketProvider to delegate to. + * + * @param delegate the delegate SocketProvider + * + * @return this object + */ + public DefaultDependencies setDelegate(SocketProvider delegate) + { + m_delegate = delegate; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public SSLContext getSSLContext() + { + SSLContext ctx = m_ctx; + try + { + return ctx == null ? SSLContext.getDefault() : ctx; + } + catch (NoSuchAlgorithmException e) + { + throw new IllegalStateException(e); + } + } + + /** + * Specify the SSLContex to utilize. + * + * @param ctx the SSLContext + * + * @return this object + */ + public DefaultDependencies setSSLContext(SSLContext ctx) + { + m_ctx = ctx; + return this; + } + + /** + * {@inheritDoc} + */ + public SSLParameters getSSLParameters() + { + SSLContext ctx = getSSLContext(); + SSLParameters params = ctx.getDefaultSSLParameters(); + String[] asCiphers = getEnabledCipherSuites(); + String[] asProtocols = getEnabledProtocolVersions(); + if (asCiphers != null) + { + params.setCipherSuites(asCiphers); + } + + if (asProtocols != null) + { + params.setProtocols(asProtocols); + } + + params.setNeedClientAuth(isClientAuthenticationRequired()); + + return params; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isClientAuthenticationRequired() + { + return m_fClientAuthRequired; + } + + /** + * Specify if client authentication is required. + * + * @param fRequired true iff client authentication is required + * + * @return this object + */ + public DefaultDependencies setClientAuthenticationRequired(boolean fRequired) + { + m_fClientAuthRequired = fRequired; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public HostnameVerifier getHostnameVerifier() + { + return m_hostnameVerifier; + } + + /** + * Specify the HostnameVerifier. + * + * @param verifier the HostnameVerifier + * + * @return this object + */ + public DefaultDependencies setHostnameVerifier(HostnameVerifier verifier) + { + m_hostnameVerifier = verifier; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Executor getExecutor() + { + Executor executor = m_executor; + return executor == null ? DEFAULT_EXECUTOR : executor; + } + + /** + * Specify the Executor to use. + * + * @param executor the Executor + * + * @return this object + */ + public DefaultDependencies setExecutor(Executor executor) + { + m_executor = executor; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getEnabledCipherSuites() + { + return m_asCipherSuitesEnabled; + } + + /** + * Specify the enabled cipher suites. + * + * @param asCiphers the enabled ciper suites + * + * @return this object + */ + public DefaultDependencies setEnabledCipherSuites(String[] asCiphers) + { + m_asCipherSuitesEnabled = asCiphers; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public String[] getEnabledProtocolVersions() + { + return m_asProtocolVersionsEnabled; + } + + /** + * Specify the enabled protocol versions. + * + * @param asProtocols the enabled protocol versions + * + * @return this object + */ + public DefaultDependencies setEnabledProtocolVersions(String[] asProtocols) + { + m_asProtocolVersionsEnabled = asProtocols; + return this; + } + + /** + * {@inheritDoc} + */ + public Logger getLogger() + { + Logger logger = m_logger; + return logger == null ? LOGGER : logger; + } + + + /** + * Specify the Logger to utilize. + * + * @param logger the logger + * + * @return this object + */ + public DefaultDependencies setLogger(Logger logger) + { + m_logger = logger; + return this; + } + + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(getSSLContext()); + + if (getHostnameVerifier() != null) + { + sb.append(", hostname-verifier=enabled"); + } + + if (isClientAuthenticationRequired()) + { + sb.append(", client-auth=enabled"); + } + + return sb.toString(); + } + + // ----- helpers ------------------------------------------------ + + /** + * Validate the dependencies. + * + * @return this object + * + * @throws IllegalArgumentException if the dependencies are invalid + */ + protected DefaultDependencies validate() + throws IllegalArgumentException + { + ensureArgument(getDelegateSocketProvider(), "DelegateSocketProvider"); + ensureArgument(getExecutor(), "Executor"); + ensureArgument(getLogger(), "Logger"); + return this; + } + + /** + * Ensure that the specified object is non-null + * + * @param o the object to ensure + * @param sName the name of the corresponding parameter + * + * @throws IllegalArgumentException if o is null + */ + protected static void ensureArgument(Object o, String sName) + { + if (o == null) + { + throw new IllegalArgumentException(sName + " cannot be null"); + } + } + + // ----- data members ------------------------------------------- + + /** + * The SocketProvider which produces the underlying clear-text + * sockets. + */ + protected SocketProvider m_delegate = TcpSocketProvider.INSTANCE; + + /** + * The SSLContext used by this SocketProvider. + */ + protected SSLContext m_ctx; + + /** + * True if client authentication is required. + */ + protected boolean m_fClientAuthRequired; + + /** + * The HostnameVerifier used by this SocketProvider. + */ + protected HostnameVerifier m_hostnameVerifier; + + /** + * The enabled cipher suites or null for default. + */ + protected String[] m_asCipherSuitesEnabled; + + /** + * The enabled protocol versions. + */ + protected String[] m_asProtocolVersionsEnabled; + + /** + * The SSL executor, or null for default. + */ + protected Executor m_executor; + + /** + * The Logger to use. + */ + protected Logger m_logger; + + static + { + // make sure the commons security provider is loaded + SecurityProvider.ensureRegistration(); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The SSLSocketProvider's dependencies. + */ + protected Dependencies m_dependencies; + + + // ----- constants ------------------------------------------------------ + + /** + * The default Logger for all derivations of this class. + */ + private static Logger LOGGER = Logger.getLogger( + SSLSocketProvider.class.getName()); + + /** + * The default executor used by new SSLSocketProviders. + */ + private static final Executor DEFAULT_EXECUTOR = Executors. + newCachedThreadPool(new DaemonThreadFactory("SSLExecutor-")); + + + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SafeSelectionHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SafeSelectionHandler.java new file mode 100644 index 0000000000000..c68aef4b4ab65 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SafeSelectionHandler.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import java.io.IOException; +import java.nio.channels.SelectableChannel; + + +/** + * SafeChannelHandler is an abstract SelectorService.Handler implementation + * with additional error handling support. + * + * @param the SelectableChannel type + * + * @author mf 2010.11.17 + */ +public abstract class SafeSelectionHandler + implements SelectionService.Handler + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a SafeChannel for the specified channel. + * + * @param channel the channel to handle + */ + protected SafeSelectionHandler(C channel) + { + m_channel = channel; + } + + + // ----- SafeChannelHandler interface ----------------------------------- + + /** + * Called when the channel has been selected. + *

+ * If this method throws an exception it will be handled by {@link + * #onException} + * + * @param nOps the selected ops + * + * @return the new interest set + * + * @throws IOException on an I/O error + */ + protected abstract int onReadySafe(int nOps) + throws IOException; + + /** + * Called in the event that {@link #onReadySafe} resulted in an exception. + *

+ * The default implementation simply closes the channel. + * + * @param t the exception + * + * @return the new interest set, the default implementation returns 0 + */ + protected int onException(Throwable t) + { + try + { + m_channel.close(); + } + catch (IOException e) {} + return 0; + } + + /** + * Return the associated channel. + * + * @return the channel + */ + public C getChannel() + { + return m_channel; + } + + + // ----- Handler interface ---------------------------------------------- + + /** + * {@inheritDoc} + */ + public final int onReady(int nOps) + { + try + { + return onReadySafe(nOps); + } + catch (Throwable t) + { + return onException(t); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The SelectableChannel. + */ + private final C m_channel; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SdpSocketProvider.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SdpSocketProvider.java new file mode 100644 index 0000000000000..1776c91e511ea --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SdpSocketProvider.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import com.oracle.coherence.common.internal.net.MultiplexedSocketProvider; + +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.lang.reflect.Method; + + +/** + * SdpSocketProvider produces SDP based sockets. + *

+ * This provider is only functional in environments which support SDP. + * + * @author mf 2010.12.27 + */ +public class SdpSocketProvider + extends InetSocketProvider + { + @Override + public ServerSocketChannel openServerSocketChannel() + throws IOException + { + try + { + return (ServerSocketChannel) METHOD_OPEN_SERVER_CHANNEL.invoke(null); + } + catch (Exception e) + { + verifySDP(); + throw ensureIOException(e); + } + } + + @Override + public ServerSocket openServerSocket() + throws IOException + { + try + { + return (ServerSocket) METHOD_OPEN_SERVER.invoke(null); + } + catch (Exception e) + { + verifySDP(); + throw ensureIOException(e); + } + } + + @Override + public SocketChannel openSocketChannel() + throws IOException + { + try + { + return (SocketChannel) METHOD_OPEN_CLIENT_CHANNEL.invoke(null); + } + catch (Exception e) + { + verifySDP(); + throw ensureIOException(e); + } + } + + @Override + public Socket openSocket() + throws IOException + { + try + { + return (Socket) METHOD_OPEN_CLIENT.invoke(null); + } + catch (Exception e) + { + verifySDP(); + throw ensureIOException(e); + } + } + + @Override + public SocketAddress resolveAddress(String sAddr) + { + try + { + verifySDP(); + } + catch (IOException e) + { + throw new IllegalArgumentException(e.getMessage()); + } + return super.resolveAddress(sAddr); + } + + @Override + public SocketProvider getDelegate() + { + return null; + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Verify that an SDP socket implementation is available. + * + * @throws IOException if no implementation is available + */ + public static void verifySDP() + throws IOException + { + if (METHOD_OPEN_SERVER_CHANNEL == null) + { + throw new IOException("SDP classes are unavailable"); + } + else if (!Boolean.getBoolean("java.net.preferIPv4Stack")) + { + throw new IOException("SDP requires the java.net.preferIPv4Stack" + + " system property be set to true"); + } + } + + /** + * Return an IOException for the specified Exception + * + * @param e the exception to ensure + * + * @return the associated IOException + */ + public static IOException ensureIOException(Exception e) + { + if (e instanceof IOException) + { + return (IOException) e; + } + else if (e.getCause() instanceof IOException) + { + return (IOException) e.getCause(); + } + else + { + return new IOException(e); + } + } + + + // ----- constants ------------------------------------------------------ + + /** + * A default SdpSocketProvider instance. + */ + public static final SdpSocketProvider INSTANCE = new SdpSocketProvider(); + + /** + * A default Multiplexed SdpSocketProvider. + */ + public static final MultiplexedSocketProvider MULTIPLEXED = + new MultiplexedSocketProvider(new MultiplexedSocketProvider + .DefaultDependencies() + .setDelegateProvider(INSTANCE)) + { + @Override + public SocketAddress resolveAddress(String sAddr) + { + try + { + verifySDP(); + } + catch (IOException e) + { + throw new IllegalArgumentException(e.getMessage()); + } + return super.resolveAddress(sAddr); + } + }; + + /** + * The method for openeing SDP ServerSocketChannels. + */ + protected static final Method METHOD_OPEN_SERVER_CHANNEL; + + /** + * The method for openeing SDP ServerSocket. + */ + protected static final Method METHOD_OPEN_SERVER; + + /** + * The method for openeing SDP SocketChannels. + */ + protected static final Method METHOD_OPEN_CLIENT_CHANNEL; + + /** + * The method for openeing SDP Socket. + */ + protected static final Method METHOD_OPEN_CLIENT; + + static + { + Method metServerChan = null; + Method metServer = null; + Method metClientChan = null; + Method metClient = null; + try + { + Class clz = Class.forName("com.oracle.net.Sdp"); + metServerChan = clz.getMethod("openServerSocketChannel"); + metServer = clz.getMethod("openServerSocket"); + metClientChan = clz.getMethod("openSocketChannel"); + metClient = clz.getMethod("openSocket"); + } + catch (Throwable t) {} + finally + { + METHOD_OPEN_SERVER_CHANNEL = metServerChan; + METHOD_OPEN_SERVER = metServer; + METHOD_OPEN_CLIENT_CHANNEL = metClientChan; + METHOD_OPEN_CLIENT = metClient; + } + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SelectionService.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SelectionService.java new file mode 100644 index 0000000000000..e3405a1445899 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SelectionService.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import java.io.IOException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; + + +/** + * The SelectionService interface describes a service for selecting on channels. + * + * @author mf 2010.10.29 + */ +public interface SelectionService + { + /** + * Register a channel with the service. + *

+ * If called for the same channel multiple times, the prior handler will + * be unregistered and the new handler will be registered in its place. + *

+ * The handler can be deregistered either by closing the channel, or + * via an explicit reregistration with a null handler. + *

+ * Following a (re)registration the handler will have an initial interest + * set based on the channels full {@link SelectableChannel#validOps valid} + * operation set. + *

+ * Note that registration should be treated as asynchronous except when it is performed from + * within a {@link Handler#onReady} callback on the same channel, in which case it will take + * effect once the callback returns, without setting {@link Handler#OP_EAGER}. + * + * @param chan the channel to monitor + * @param handler the handler to call when the channel is ready for + * servicing + * + * @throws IOException if an I/O error occurs + */ + public void register(SelectableChannel chan, Handler handler) + throws IOException; + + /** + * Invoke the runnable by the SelectionService. It guarantees that the + * runnable associated with the SelectableChannel and any Handler associated + * with the same channel will not run concurrently + *

+ * If called for the same channel multiple times, the Runnables will be + * executed sequentially in the order invoke was called. + *

+ * Note there is no implied ordering between calls to {@link #register} and {@link #invoke}. + * + * @param chan the channel the runnable is associated with + * @param runnable the Runnable to call by the SelectionService + * @param cMillis the delay before invocation, or 0 for none + * + * @throws IOException if an I/O error occurs + */ + public void invoke(SelectableChannel chan, Runnable runnable, long cMillis) + throws IOException; + + /** + * Associate one (the child) with another channel (the parent). Once associated the child channel + * will be handled serially with respect to the parent and all other associated children. Generally + * this means that it will be handled on the same thread in multi-threaded SelectionService implementations. + *

+ * Note that changing a channels association may cause various ordering issues if the channel was previously + * in use by the service, or if the channel was the parent of another association. It is generally discouraged + * to re-associate channels. If re-association is performed it is up to the caller to handle any potential ordering + * issues with outstanding registrations, invocations, and children. Any pre-existing registration will be + * asynchronously deregistered, and any pending invocations will be asynchronously executed. + *

+ * + * @param chanParent the parent channel, or null to remove any prior association + * @param chanChild the child channel to associate with parent + * + * @throws IOException if an I/O error occurs + */ + public void associate(SelectableChannel chanParent, SelectableChannel chanChild) + throws IOException; + + /** + * Shutdown the SelectionService. + */ + public void shutdown(); + + /** + * Handler provides a pluggable callback which is invoked when the + * registered channel needs servicing. + */ + public interface Handler + { + /** + * Called when the channel has been selected. + *

+ * The handler implementation is expected to handle all exceptions. + * Any unchecked exception thrown from this method will result in + * the handler being deregistered from the SelectionService, and no + * further selection operations being performed upon the channel. + * + * @param nOps the ready ops + * + * @return the new interest ops + */ + public int onReady(int nOps); + + /** + * Operation-set bit for socket-accept operations. + * + * @see SelectionKey#OP_ACCEPT + */ + public static final int OP_ACCEPT = SelectionKey.OP_ACCEPT; + + /** + * Operation-set bit for socket-connect operations. + * + * @see SelectionKey#OP_CONNECT + */ + public static final int OP_CONNECT = SelectionKey.OP_CONNECT; + + /** + * Operation-set bit for read operations. + * + * @see SelectionKey#OP_READ + */ + public static final int OP_READ = SelectionKey.OP_READ; + + /** + * Operation-set bit for write operations. + * + * @see SelectionKey#OP_WRITE + */ + public static final int OP_WRITE = SelectionKey.OP_WRITE; + + /** + * Operation-set bit indicating that it is likely that at least one of the other bits in the set are likely + * to be satisfied soon. This serves as a hint to the service that it may be worth re-running the handler + * prior to waiting on the selector. If a Handler is called eagerly this bit will be set in nOps value + * passed to {@link #onReady} to indicate that the reason for the call. + */ + public static final int OP_EAGER = Math.max(OP_ACCEPT, Math.max(OP_CONNECT, Math.max(OP_READ, OP_WRITE))) << 1; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SelectionServices.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SelectionServices.java new file mode 100644 index 0000000000000..1e02062c91c47 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SelectionServices.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import com.oracle.coherence.common.base.Factory; +import com.oracle.coherence.common.internal.Platform; +import com.oracle.coherence.common.internal.net.MultiProviderSelectionService; +import com.oracle.coherence.common.internal.net.ResumableSelectionService; +import com.oracle.coherence.common.internal.net.HashSelectionService; +import com.oracle.coherence.common.internal.net.RoundRobinSelectionService; +import com.oracle.coherence.common.util.Duration; + +import java.util.concurrent.ThreadFactory; + + +/** + * The SelectionServices class provides helper methods related to + * SelectionServices. + * + * @author mf 2010.12.01 + */ +public class SelectionServices + { + /** + * Return A singleton instance of a SelectionService which is suitable for + * managing a large number of SelectableChannels efficiently. + *

+ * The number of threads used to handle the load can be influenced via the + * com.oracle.common.net.SelectionServices.threads system property, and defaults to a value + * relative to the number of available cores and percentage of system memory the VM has been sized to use. + *

+ * The threads used to run the service will be dynamically started and + * stopped. The idle timeout can be specified via the + * com.oracle.common.net.SelectionServices.timeout system property, and defaults to 5s. + * + * @return the default SelectionService + */ + public static SelectionService getDefaultService() + { + return DefaultServiceHolder.INSTANCE; + } + + /** + * LoadBalancer defines the various load-balancing algorithms. + */ + private enum LoadBalancer + { + HASH, + ROUND_ROBIN + } + + /** + * The Holder for the DefaultService reference to allow for lazy + * instantiation. + */ + private static final class DefaultServiceHolder + { + /** + * The default SelectionService instance. + */ + private static final SelectionService INSTANCE; + + /** + * The associated ThreadGroup. + */ + private static final ThreadGroup GROUP = new ThreadGroup("SelectionService"); + static + { + GROUP.setDaemon(false); // keeps group from being auto-destroyed + } + + static + { + final int cThreads = Integer.parseInt(System.getProperty(SelectionServices.class.getName() + ".threads", + String.valueOf(Platform.getPlatform().getFairShareProcessors()))); + final long cMillisTimeout = new Duration(System.getProperty(SelectionServices.class.getName() + ".timeout", "5s")) + .as(Duration.Magnitude.MILLI); + LoadBalancer balancer = LoadBalancer.valueOf( + System.getProperty(SelectionServices.class.getName() + ".loadBalancer", LoadBalancer.HASH.name())); + + final Factory factoryService = + new Factory() + { + @Override + public SelectionService create() + { + return new ResumableSelectionService(new ThreadFactory() + { + public Thread newThread(Runnable r) + { + Thread thread = new Thread(GROUP, r); + thread.setDaemon(true); + return thread; + } + }).setIdleTimeout(cMillisTimeout); + } + }; + + INSTANCE = new MultiProviderSelectionService( + balancer == LoadBalancer.ROUND_ROBIN + ? new Factory() + { + @Override + public SelectionService create() + { + return new RoundRobinSelectionService(cThreads, factoryService); + } + } + : new Factory() + { + @Override + public SelectionService create() + { + return new HashSelectionService(cThreads, factoryService); + } + }) + { + public void shutdown() + { + // Service shutdown is not supported on the singleton + throw new UnsupportedOperationException(); + } + }; + } + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SocketProvider.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SocketProvider.java new file mode 100644 index 0000000000000..faf3cd61536be --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SocketProvider.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; + + +/** + * SocketProvider defines an interface for creating sockets. + * + * @author mf 2010.12.27 + */ +public interface SocketProvider + { + /** + * Resolve the specified address. + * + * @param sAddr the address to resolve + * + * @return the resolved address + * + * @throws IllegalArgumentException if the address is not resolvable + */ + public SocketAddress resolveAddress(String sAddr); + + /** + * Return the string form of of the address to which this socket is connected, + * suitable for resolving via {@link #resolveAddress}, on a remote host. + * + * @param socket the socket + * + * @return the string address, or null if the socket is not connected + */ + public String getAddressString(Socket socket); + + /** + * Return the string form of of the server's address, suitable for + * resolving via {@link #resolveAddress}, on a remote host. + * + * @param socket the socket + * + * @return the string address + */ + public String getAddressString(ServerSocket socket); + + /** + * Create an ServerSocketChannel. + * + * @return the ServerSocketChannel + * + * @throws IOException if an I/O error occurs + */ + public ServerSocketChannel openServerSocketChannel() + throws IOException; + + /** + * Create an ServerSocket. + * + * @return the ServerSocket + * + * @throws IOException if an I/O error occurs + */ + public ServerSocket openServerSocket() + throws IOException; + + /** + * Create a SocketChanel. + * + * @return the Socket + * + * @throws IOException if an I/O error occurs + */ + public SocketChannel openSocketChannel() + throws IOException; + + /** + * Create a Socket. + * + * @return the Socket + * + * @throws IOException if an I/O error occurs + */ + public Socket openSocket() + throws IOException; + + /** + * Return the SocketProvider which this provider delegates to, or null if this is a root provider. + * + * @return the delegate provider or null + */ + public SocketProvider getDelegate(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SocketSettings.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SocketSettings.java new file mode 100644 index 0000000000000..3a4deecc18f8c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/SocketSettings.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import java.util.Map; +import java.util.HashMap; +import java.net.SocketException; +import java.net.SocketOptions; + + +/** +* SocketSettings provides a means to configure the various aspects of +* Sockets. Unlike java.net.SocketOptions, unset options will result in a value +* of null when queried via getOption. +* +* @author mf 2010.05.20 +*/ +public class SocketSettings + implements SocketOptions + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an empty SocketOptions configuration. + */ + public SocketSettings() + { + } + + /** + * Construct a SocketOptions configuration based upon the supplied options. + * + * @param options the options to copy + * + * @throws IllegalArgumentException on error + */ + public SocketSettings(SocketOptions options) + { + setOptions(options); + } + + + // ----- SocketOptions methods ------------------------------------------ + + /** + * {@inheritDoc} + */ + public void setOption(int optID, Object value) + throws SocketException + { + f_mapOptions.put(optID, value); + } + + /** + * {@inheritDoc} + */ + public Object getOption(int optID) + throws SocketException + { + return f_mapOptions.get(optID); + } + + /** + * Set any options indicated by the supplied SocketOptions into this + * SocketOptions. + * + * @param options the options to set + * + * @throws IllegalArgumentException on error + */ + public void setOptions(SocketOptions options) + { + try + { + if (options != null) + { + Map map = f_mapOptions; + for (int nOp : new int[] { + TCP_NODELAY, + SO_REUSEADDR, + SO_BROADCAST, + IP_TOS, + SO_LINGER, + SO_TIMEOUT, + SO_SNDBUF, + SO_RCVBUF, + SO_KEEPALIVE, + SO_OOBINLINE}) + { + Object oVal = options.getOption(nOp); + if (oVal != null) + { + map.put(nOp, oVal); + } + } + } + } + catch (SocketException e) + { + throw new IllegalArgumentException(e); + } + } + + /** + * Set the specified option. + * + * @param optID the option id + * @param value the option value + * + * @return this object + * + * @throws IllegalArgumentException on error + */ + public SocketSettings set(int optID, Object value) + { + try + { + setOption(optID, value); + return this; + } + catch (SocketException e) + { + throw new IllegalArgumentException(e); + } + } + + /** + * Return the specified option. + * + * @param optID the option id + * + * @return the option value + * + * @throws IllegalArgumentException on error + */ + public Object get(int optID) + { + try + { + return getOption(optID); + } + catch (SocketException e) + { + throw new IllegalArgumentException(e); + } + } + + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + Map map = f_mapOptions; + StringBuffer sb = new StringBuffer("SocketOptions{"); + String sDelim = ""; + String sComma = ", "; + Object oOption; + + if ((oOption = map.get(SO_REUSEADDR)) != null) + { + sb.append(sDelim).append("ReuseAddress=").append(oOption); + sDelim = sComma; + } + if ((oOption = map.get(SO_RCVBUF)) != null) + { + sb.append(sDelim).append("ReceiveBufferSize=").append(oOption); + sDelim = sComma; + } + if ((oOption = map.get(SO_SNDBUF)) != null) + { + sb.append(sDelim).append("SendBufferSize=").append(oOption); + sDelim = sComma; + } + if ((oOption = map.get(SO_TIMEOUT)) != null) + { + sb.append(sDelim).append("Timeout=").append(oOption); + sDelim = sComma; + } + if ((oOption = map.get(SO_LINGER)) != null) + { + sb.append(sDelim).append("LingerTimeout=").append(oOption); + sDelim = sComma; + } + if ((oOption = map.get(SO_KEEPALIVE)) != null) + { + sb.append(sDelim).append("KeepAlive=").append(oOption); + sDelim = sComma; + } + if ((oOption = map.get(TCP_NODELAY)) != null) + { + sb.append(sDelim).append("TcpNoDelay=").append(oOption); + sDelim = sComma; + } + if ((oOption = map.get(IP_TOS)) != null) + { + sb.append(sDelim).append("TrafficClass=").append(oOption); + // sDelim = sComma; + } + + return sb.append('}').toString(); + } + + + // ----- data members --------------------------------------------------- + + /** + * A map of the specified options. + */ + protected final Map f_mapOptions = new HashMap(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/Sockets.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/Sockets.java new file mode 100644 index 0000000000000..f4f6af9877d5e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/Sockets.java @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import com.oracle.coherence.common.internal.net.InterruptibleChannels; + +import java.io.IOException; + +import java.net.SocketException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.DatagramSocket; +import java.net.MulticastSocket; +import java.net.SocketOptions; + +import java.nio.channels.SelectableChannel; + +import java.util.logging.Logger; +import java.util.logging.Level; + + +/** + * Sockets provides static helper methods related to sockets. + * + * @author mf 2010.12.14 + */ +public final class Sockets + { + /** + * Apply the specified options to a socket. + * + * @param socket the socket to configure + * @param options the options to apply, or null for none + * + * @throws SocketException if an I/O error occurs + */ + public static void configure(ServerSocket socket, SocketOptions options) + throws SocketException + { + if (options == null || socket.isClosed()) + { + return; + } + + try + { + Object oOption; + if (!socket.isBound() && + (oOption = options.getOption(SocketOptions.SO_REUSEADDR)) != null) + { + socket.setReuseAddress((Boolean) oOption); + } + if ((oOption = options.getOption(SocketOptions.SO_RCVBUF)) != null) + { + int cb = (Integer) oOption; + socket.setReceiveBufferSize(cb); + int cbReal = socket.getReceiveBufferSize(); + if (cbReal < cb) + { + warnBufferSize(socket, "receive", cb, cbReal); + } + } + if ((oOption = options.getOption(SocketOptions.SO_TIMEOUT)) != null) + { + socket.setSoTimeout((Integer) oOption); + } + } + catch (SocketException e) + { + if (socket.isClosed()) + { + return; + } + throw e; + } + } + + /** + * Apply the specified options to a socket. + * + * @param socket the socket to configure + * @param options the options to apply, or null for none + * + * @throws SocketException if an I/O error occurs + */ + public static void configure(Socket socket, SocketOptions options) + throws SocketException + { + if (options == null || socket.isClosed()) + { + return; + } + + try + { + Object oOption; + if (!socket.isBound() && + (oOption = options.getOption(SocketOptions.SO_REUSEADDR)) != null) + { + boolean fReuse = (Boolean) oOption; + if (fReuse != socket.getReuseAddress()) + { + warnReuseAddr(fReuse); + } + socket.setReuseAddress((Boolean) oOption); + } + if ((oOption = options.getOption(SocketOptions.SO_RCVBUF)) != null) + { + int cb = (Integer) oOption; + socket.setReceiveBufferSize(cb); + int cbReal = socket.getReceiveBufferSize(); + if (cbReal < cb) + { + warnBufferSize(socket, "receive", cb, cbReal); + } + } + if ((oOption = options.getOption(SocketOptions.SO_SNDBUF)) != null) + { + int cb = (Integer) oOption; + socket.setSendBufferSize(cb); + int cbReal = socket.getSendBufferSize(); + if (cbReal < cb) + { + warnBufferSize(socket, "send", cb, cbReal); + } + } + if ((oOption = options.getOption(SocketOptions.SO_TIMEOUT)) != null) + { + socket.setSoTimeout((Integer) oOption); + } + if ((oOption = options.getOption(SocketOptions.SO_LINGER)) != null) + { + socket.setSoLinger(true, (Integer) oOption); + } + if ((oOption = options.getOption(SocketOptions.SO_KEEPALIVE)) != null) + { + socket.setKeepAlive((Boolean) oOption); + } + if ((oOption = options.getOption(SocketOptions.TCP_NODELAY)) != null) + { + socket.setTcpNoDelay((Boolean) oOption); + } + if ((oOption = options.getOption(SocketOptions.IP_TOS)) != null) + { + socket.setTrafficClass((Integer) oOption); + } + } + catch (SocketException e) + { + if (socket.isClosed()) + { + return; + } + + try + { + // on some OSs (OSX) you can't set options if the remote end + // has closed the socket. This isn't an easily detectable state + // but we try by attempting to simply set some random option to its + // current value. + socket.setKeepAlive(socket.getKeepAlive()); + } + catch (SocketException e2) + { + return; + } + + throw e; + } + } + + /** + * Apply the specified options to a socket. + * + * @param socket the socket to configure + * @param options the options to apply, or null for none + * + * @throws SocketException if an I/O error occurs + */ + public static void configure(DatagramSocket socket, SocketOptions options) + throws SocketException + { + if (options == null || socket.isClosed()) + { + return; + } + + try + { + Object oOption; + if (!socket.isBound() && + (oOption = options.getOption(SocketOptions.SO_REUSEADDR)) != null) + { + boolean fReuse = (Boolean) oOption; + if (fReuse != socket.getReuseAddress()) + { + warnReuseAddr(fReuse); + } + socket.setReuseAddress((Boolean) oOption); + } + if ((oOption = options.getOption(SocketOptions.SO_RCVBUF)) != null) + { + int cb = (Integer) oOption; + socket.setReceiveBufferSize(cb); + int cbReal = socket.getReceiveBufferSize(); + if (cbReal < cb) + { + warnBufferSize(socket, "receive", cb, cbReal); + } + } + if ((oOption = options.getOption(SocketOptions.SO_SNDBUF)) != null) + { + int cb = (Integer) oOption; + socket.setSendBufferSize(cb); + int cbReal = socket.getSendBufferSize(); + if (cbReal < cb) + { + warnBufferSize(socket, "send", cb, cbReal); + } + } + if ((oOption = options.getOption(SocketOptions.SO_TIMEOUT)) != null) + { + socket.setSoTimeout((Integer) oOption); + } + } + catch (SocketException e) + { + if (socket.isClosed()) + { + return; + } + throw e; + } + } + + /** + * Apply the specified options to a socket. + * + * @param socket the socket to configure + * @param options the options to apply, or null for none + * + * @throws SocketException if an I/O error occurs + */ + public void configure(MulticastSocket socket, SocketOptions options) + throws SocketException + { + configure((DatagramSocket) socket, options); + } + + /** + * Update the Socket's blocking (and possibly interruptible mode). + *

+ * When set via this method a non-blocking socket will also be made non-interruptible if possible, and + * interruptible when set to blocking if possible. + *

+ * Changing the interrupt mode on a socket may not be possible in all JVMs, especially if Java + * security has been enabled. In such cases the Socket will retain its default behavior of always + * being interruptible. + *

+ * The benefit of making a non-blocking Socket non-interruptible is that it prevents the socket from + * being arbitrarily closed when accessed from a interrupted thread. + *

+ * Note: This is a work around for JDK-6908931 + * Note: As of Java 9 this work around is no longer functional. + *

+ * @param chan the channel + * @param fBlocking the blocking/interrutible mode + * + * @return true if both modes were set, false if only the blocking mode was set + * + * @throws java.io.IOException if an IO error occurs + */ + public static boolean configureBlocking(SelectableChannel chan, boolean fBlocking) + throws IOException + { + synchronized (chan.blockingLock()) + { + chan.configureBlocking(fBlocking); + return InterruptibleChannels.setInterruptible(chan, fBlocking); + } + } + + /** + * Return the MTU for the local NIC associated this socket. + * + * @param socket the socket + * @return the MTU + */ + public static int getMTU(Socket socket) + { + int nMtu = 0; + try + { + nMtu = InetAddresses.getLocalMTU(socket.getLocalAddress()); + } + catch (Throwable e) + { + // at least on OSX we've seen that calling getLocalAddress on a socket which is + // concurrently being closed can yield this Error. As getLocalAddress is not + // allowed to throw SocketException, it is surfaced as an Error, which is in + // and of itself wrong. + + // fall through + } + + return nMtu == 0 ? InetAddresses.getLocalMTU() : nMtu; + } + + // ----- helpers -------------------------------------------------------- + + /** + * Issue a warning regarding an undersized socket buffer. + * + * @param socket the socket + * @param sBuffer the buffer description + * @param cb the requested size + * @param cbReal the actual size + */ + protected static void warnBufferSize(Object socket, String sBuffer, + int cb, int cbReal) + { + LOGGER.log(Level.INFO, + "Failed to set the " + sBuffer + " buffer size on " + socket + + " to " + cb + " bytes; actual size is " + cbReal + " bytes." + + " Consult your OS documentation regarding increasing the" + + " maximum socket buffer size. Proceeding with the actual value" + + " may cause sub-optimal performance."); + } + + /** + * Issue a warning regarding overrideing SO_REUSEADDR + * + * @param fReuse the specified setting + */ + protected static void warnReuseAddr(boolean fReuse) + { + if (s_fWarnReuseAddr) + { + s_fWarnReuseAddr = false; // only log this once per JVM + LOGGER.log(Level.WARNING, + "The value of SO_REUSEADDR is being overriden to " + + fReuse + " from the system default; this setting is not " + + "portable and may result in differeing behavior across " + + "environments."); + } + } + + + // ----- static data members -------------------------------------------- + + /** + * Tracks if the JVM has already warned about explicitly setting re-use + * addr. + */ + private static boolean s_fWarnReuseAddr = true; + + + // ----- constants ------------------------------------------------------ + + /** + * The Sockets class logger. + */ + private static Logger LOGGER = Logger.getLogger(Sockets.class.getName()); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/TcpSocketProvider.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/TcpSocketProvider.java new file mode 100644 index 0000000000000..2e8dcaeab7274 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/TcpSocketProvider.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net; + + +import com.oracle.coherence.common.internal.net.DemultiplexedSocketProvider; +import com.oracle.coherence.common.internal.net.MultiplexedSocketProvider; + +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + + +/** + * TcpSocketProvider produces standard TCP sockets. + * + * @author mf 2010.12.27 + */ +public class TcpSocketProvider + extends InetSocketProvider + { + @Override + public ServerSocketChannel openServerSocketChannel() + throws IOException + { + return ServerSocketChannel.open(); + } + + @Override + public ServerSocket openServerSocket() + throws IOException + { + return new ServerSocket(); + } + + @Override + public SocketChannel openSocketChannel() + throws IOException + { + return SocketChannel.open(); + } + + @Override + public Socket openSocket() + throws IOException + { + return new Socket(); + } + + @Override + public SocketProvider getDelegate() + { + return null; + } + + + // ----- constants ------------------------------------------------------ + + /** + * A default TcpSocketProvider instance. + */ + public static final TcpSocketProvider INSTANCE = new TcpSocketProvider(); + + /** + * A default Multiplexed TcpSocketProvider. + */ + public static final MultiplexedSocketProvider MULTIPLEXED = + new MultiplexedSocketProvider(new MultiplexedSocketProvider + .DefaultDependencies() + .setDelegateProvider(INSTANCE)); + + /** + * A default Demultiplexed TcpSocketProvider. Bindings from this provider will + * always be on the base port, but they will not block others from doing multiplexed + * bindings on that same port. Additionally since this provider sits on-top of the + * multiplexed provider it supports NAT bindings. + */ + public static final DemultiplexedSocketProvider DEMULTIPLEXED = + new DemultiplexedSocketProvider(TcpSocketProvider.MULTIPLEXED, -1); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/Bus.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/Bus.java new file mode 100644 index 0000000000000..ba2ab5e6cced1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/Bus.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus; + + +import com.oracle.coherence.common.base.Collector; + +import java.io.Closeable; + + +/** + * A Bus represents a communication mechanism that allows the exchange of + * information between multiple peers, called EndPoints. + *

+ * Communication with another peer requires a connection. The boundaries of a + * connection are identified by a pair of CONNECT and RELEASE events. Unless + * otherwise specified, all operations occurring within a connection between + * the two peers are ordered based upon the order in which they were invoked + * upon the source bus. + *

+ * Bus operations taking receipts are asynchronous. Completion of asynchronous + * operations is identified via a corresponding {@link Event.Type#RECEIPT + * RECEIPT} event {@link #setEventCollector(Collector)} + * arriving locally} prior to any {@link Event.Type#DISCONNECT DISCONNECT} + * event for the same peer. Any RECEIPT event arriving after the DISCONNECT + * event indicates an operation which is not known to have completed. + * Completion of an operation indicates that the operation reached the peer + * and is ready for processing. As operations are ordered this also indicates + * that all prior operations reached the peer as well. Note RECEIPT events are + * not provided for null receipts but can be inferred from a RECEIPT + * event for a subsequent operation against the same peer. + *

+ * Invoking an asynchronous operation may not result in the operation being + * immediately dispatched to the peer. The dispatch is only ensured once + * a call to {@link #flush flush()} is issued. The general usage pattern would + * be to perform a series of asynchronous operations, followed by a call to + * flush. + *

+ * Unless otherwise noted all bus operations are thread-safe and all + * asynchronous bus operations are re-entrant, i.e. may be issued from + * within a collector callback. + * + * @author mf/gg/cp 2010.10.04 + */ +public interface Bus + extends Closeable + { + /** + * Return the EndPoint this bus is bound to. Parties wishing to transport + * information to this bus, can connect via this EndPoint. + *

+ * Note: The returned EndPoint may be a different object then the one + * supplied to the {@link Depot} at creation time. The EndPoint may in + * fact not even be {@link Object#equals equal} to the original if the + * supplied EndPoint for instance had represented an ephemeral endpoint. + * Once the bus is open this method will always return the same value. + * + * @return the local EndPoint + */ + public EndPoint getLocalEndPoint(); + + /** + * Open the bus, allowing it to begin exchanging data. + *

+ * Upon completion of the asynchronous operation a {@link Event.Type#OPEN + * OPEN} event will emitted to its event collector. + */ + public void open(); + + /** + * Close the bus. This prevents any new connections and {@link #release releases} + * all existing connections, thus preventing any further data exchanges. + *

+ * Upon completion of the asynchronous operation a {@link Event.Type#CLOSE + * CLOSE} event will emitted to its event collector. + *

+ * Once closed a bus cannot be re-opened. + */ + public void close(); + + /** + * Connect this bus to an EndPoint. + *

+ * Prior to the completion of this operation a {@link Event.Type#CONNECT + * CONNECT} event will emitted to the event collector unless the Bus is + * already connected to the specified peer. + *

+ * A successful completion of this operation does not imply that the other + * party is actually reachable, it only sets up a starting point in the + * conversation. + * + * @param peer the EndPoint + * + * @throws IllegalArgumentException if the EndPoint type is not supported + */ + public void connect(EndPoint peer); + + /** + * Disconnect an EndPoint from this bus. + *

+ * Upon completion of the asynchronous operation a {@link + * Event.Type#DISCONNECT DISCONNECT} event will emitted to its event + * collector unless the Bus had already disconnected from the specified + * peer. + *

+ * Note: to allow future connections with the same peer {@link #release} + * must also be called. + * + * @param peer the EndPoint + * + * @throws IllegalArgumentException if the peer is unknown to the Bus + */ + public void disconnect(EndPoint peer); + + /** + * Release an EndPoint from this bus. + *

+ * The release operation drops all former state associated with a + * connection to a peer, allowing a new connection to be established. If + * called on for a connected peer, a disconnect will be performed before + * the release. + *

+ * Upon completion of the asynchronous operation a {@link + * Event.Type#RELEASE RELEASE} event will emitted to its event + * collector. + * + * @param peer the EndPoint + * + * @throws IllegalArgumentException if the peer is unknown to the Bus + */ + public void release(EndPoint peer); + + /** + * Ensure that any buffered asynchronous operations are dispatched. + *

+ * Upon completion of the asynchronous operation all previously buffered + * asynchronous operations will have been dispatched. + *

+ */ + public void flush(); + + /** + * Register a collector which will receive events for this Bus. + *

+ * Collector operations are expected to complete in a timely manner, as + * they may be called on dedicated Bus threads and blocking could impact + * the overall Bus performance. A typical implementation may simply add + * each event to a queue for later processing on application threads. + *

+ * The collector may be called concurrently if the bus is multi-threaded, + * however the collector will not be called concurrently for multiple + * events {@link Event#getEndPoint() associated} with the same EndPoint. + * + * @param collector the event collector + * + * @throws IllegalStateException if the Bus is open + */ + public void setEventCollector(Collector collector); + + /** + * Obtain the registered event collector. + * + * @return the event collector. + */ + public Collector getEventCollector(); + + /** + * Return a human readable description of the connection with the peer. + * + * @param peer the peer + * + * @return a string describing the connection + */ + public default String toString(EndPoint peer) + { + return peer == null ? "null" : peer.toString(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/Depot.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/Depot.java new file mode 100644 index 0000000000000..1312527cdd792 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/Depot.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus; + + +/** + * A Depot serves as a factory for creating EndPoints and Buses. + * + * @author mf 2010.10.06 + */ +public interface Depot + { + /** + * Resolve the EndPoint for the specified canonical name. + * + * @param sName the EndPoint's canonical name + * + * @return the EndPoint + * + * @throws IllegalArgumentException if the format is unresolvable + */ + public EndPoint resolveEndPoint(String sName); + + /** + * Create a new MessageBus bound to the specified local EndPoint. + * + * @param pointLocal the local EndPoint or null for an ephemeral EndPoint + * + * @return the MessageBus + */ + public MessageBus createMessageBus(EndPoint pointLocal); + + /** + * Create a new MemoryBus bound to the specified local EndPoint. + * + * @param pointLocal the local EndPoint or null for an ephemeral EndPoint + * + * @return the MemoryBus + */ + public MemoryBus createMemoryBus(EndPoint pointLocal); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/EndPoint.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/EndPoint.java new file mode 100644 index 0000000000000..63e1ad2b55338 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/EndPoint.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus; + + +/** + * EndPoint provides an opaque representation of an address for a Bus. + * + * @author mf 2010.10.04 + */ +public interface EndPoint + { + /** + * Return the string representation of the EndPoint. + * + * @return the string representation of the EndPoint + */ + public String getCanonicalName(); + + /** + * Return true iff the specified object in an EndPoint representing the + * same bus as this EndPoint. + * + * @param o the EndPoint to compare against + * + * @return true if the specified EndPoint is equal to this EndPoint + */ + public boolean equals(Object o); + + /** + * {@inheritDoc} + */ + public int hashCode(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/Event.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/Event.java new file mode 100644 index 0000000000000..e619d1511cc03 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/Event.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus; + + +import com.oracle.coherence.common.base.Disposable; +import com.oracle.coherence.common.io.BufferSequence; + + +/** + * An Event indicates that a special condition has occurred on a Bus. + * + * @author mf/cp 2010.10.04 + */ +public interface Event + extends Disposable + { + /** + * Enumeration of event types. + */ + enum Type + { + /** + * The OPEN Event is emitted once the Bus is ready to exchange + * information. + */ + OPEN, + + /** + * The CLOSE Event is emitted once the Bus will no longer exchange + * information. No more input will be accepted and no further output + * will be added to the collectors. + */ + CLOSE, + + /** + * The CONNECT Event is emitted at the start of a sequence of + * communication between two EndPoints on a Bus. The event may either + * be solicited as a result of a local {@link Bus#connect connect}, + * unsolicited due to to a peer initiating the connection. + */ + CONNECT, + + /** + * The DISCONNECT Event is emitted at the end of a sequence of + * communication between two EndPoints on a Bus. + *

+ * The DISCONNECT event may either be solicited as the result of + * a local {@link Bus#disconnect disconnect}, or unsolicited due to + * either a remote disconnect or a lower level protocol termination. + *

+ * Note that except in the case of a local call to {@link Bus#disconnect} + * the Bus is not required to detect a logical or physical + * disconnection, or to emit this event. Therefore "death detection" + * is ultimately the responsibility of the Bus consumer, and not the + * Bus itself. + *

+ * Any receipt for the specified EndPoint added to the event collector + * after this event, indicates an operation that is not known to have + * completed; in other words, the operation may or may not have + * completed. + *

+ * For an unsolicited DISCONNECT event the Content may contain an + * optional {@link Throwable} indicating the reason for the DISCONNECT. + *

+ * Invoking asynchronous operations against a disconnected EndPoint + * will have no effect except for the receipt being emitted to the + * event collector. + */ + DISCONNECT, + + /** + * The RELEASE Event is emitted by the Bus only in response to a local + * {@link Bus#release release}. Prior to being released, a logical + * connection exists even after the DISCONNECT event has been emitted. + * Once released, the Bus is ready for new connections for the + * corresponding EndPoint. + */ + RELEASE, + + /** + * The BACKLOG_EXCESSIVE Event is emitted when the Bus has reached + * a state where it is having difficulty in keeping up with the load + * placed upon it. While in this state, the Bus will not reject work + * but may eventually disconnect if the backlog continues to increase. + * Upon receiving this event, it is the responsibility of the Bus + * consumer to modulate its use of Bus resources. + *

+ * The event may be emitted for a specific remote EndPoint, indicating + * that throttling is only desired for that EndPoint, or null + * if the backlog is not EndPoint specific. The event may also be + * emitted for the local EndPoint to indicate that the caller needs to + * service the Bus output faster, such as when a Bus consumer falls + * behind in disposing of Events. + */ + BACKLOG_EXCESSIVE, + + /** + * The BACKLOG_NORMAL Event is emitted to signify the end of a + * backlog state. This event is only emitted as a follow up to a + * BACKLOG_EXCESSIVE event, and will use the same EndPoint + * information as was used in the BACKLOG_EXCESSIVE event. + */ + BACKLOG_NORMAL, + + /** + * A RECEIPT Event is emitted by the Bus to indicate the completion of + * an asynchronous operation. + *

+ * The content of a RECEIPT event is the "receipt" object that was + * supplied to the asynchronous operation. Note null receipts + * will not result in a RECEIPT event. + */ + RECEIPT, + + /** + * The SIGNAL Event is emitted on the MemoryBus as a result of a peer's + * call to the {@link MemoryBus#signal signal()} method specifying this + * EndPoint. + */ + SIGNAL, + + /** + * The MESSAGE Event is emitted by the MessageBus in order to deliver the + * contents of a message that the Bus received from a peer. + *

+ * For a MessageBus, the Content of a MESSAGE Event is a {@link + * BufferSequence}. + */ + MESSAGE, + } + + /** + * Determine the event type. + * + * @return the Type of event + */ + public Type getType(); + + /** + * Return the EndPoint associated with the event, if any. + * + * @return the associated EndPoint, or null if the event is not + * related to a specific EndPoint + */ + public EndPoint getEndPoint(); + + /** + * Obtain the content associated with this event. + *

+ * See {@link Type} for details regarding the content type for different + * events. + * + * @return the content associated with this event, or null + */ + public Object getContent(); + + /** + * Dispose of the event and optionally return a decoupled version its content. + *

+ * If fTakeContent is true then the object returned from this + * method is independent of the disposed event allowing the content to remain + * valid for application usage. The returned content will be equivalent to the + * object returned from {@link #getContent}, but may or may not be the same + * object instance. If false is specified then the event and its + * content will be disposed and null will be returned. + * + * @param fTakeContent true if a decoupled version of the content should be + * returned + * + * @return a the content associated with the event, or null + */ + public Object dispose(boolean fTakeContent); + + /** + * Dispose of the event, releasing any resources associated with it. An + * Event Collector must ensure that this method is called exactly once for + * each Event that is emitted. Once this method has been called, any + * subsequent use of the Event or contents previously obtained from it is + * illegal. Specifically, this method indicates to the Bus that the event + * has been received, processed, and that any resources associated with + * the Event can be reused. + */ + public void dispose(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/MemoryBus.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/MemoryBus.java new file mode 100644 index 0000000000000..f762adb522fd2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/MemoryBus.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus; + + +import com.oracle.coherence.common.io.BufferSequence; +import com.oracle.coherence.common.io.Buffers; + +import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicLong; + + +/** + * A MemoryBus provides access to regions of remotely accessible memory. + *

+ * The MemoryBus intentionally provides a very limited number of operations, + * leaving higher level functions such as synchronization to be externally + * implemented via mechanisms such as {@link MessageBus messaging}. + * + * @author mf/cp 2010.10.06 + */ +public interface MemoryBus + extends Bus + { + /** + * Specify the BufferSequence which this MemoryBus will host. + *

+ * If the BufferSequence contains multiple ByteBuffers then all but the last + * buffer must {@link ByteBuffer#position start} and {@link ByteBuffer#limit end} on + * an eight byte word boundary. + *

+ * The sequence if specified must be provided before {@link #open opening} the bus. + *

+ * It is the responsibility of the caller to {@link BufferSequence#dispose dispose} of + * the sequence once the bus has been {@link Event.Type#CLOSE closed}. + *

+ * + * @param bufseq the buffer sequence to host or null + * + * @see Buffers#allocateDirect + */ + public void setBufferSequence(BufferSequence bufseq); + + /** + * Return the BufferSequence representing the locally hosted memory. + * + * @return the locally hosted BufferSequence or null if nothing is hosted + */ + public BufferSequence getBufferSequence(); + + /** + * Return the capacity of a peer's hosted memory in bytes. + *

+ * The EndPoint's bus region is [0 .. capacity). + * + * @param peer the target EndPoint, or null for the local capacity + * + * @return the peer's capacity + * + * @throws IllegalArgumentException if the peer is unknown to the Bus + */ + public long getCapacity(EndPoint peer); + + /** + * Request a read from the peer's memory into the supplied BufferSequence. + *

+ * Upon {@link Event.Type#RECEIPT completion} of the asynchronous + * operation the supplied BufferSequence will contain a copy of the peer's + * memory segment. + * + * @param peer the target EndPoint to read from + * @param offset the offset into the peer's memory to start reading from + * @param bufseq the buffers to write to + * @param receipt the optional operation receipt + * + * @throws IllegalArgumentException if the peer is unknown to the Bus + * @throws IndexOutOfBoundsException if the offset is negative, or the + * offset+{@link BufferSequence#getLength bufseq.getLength()} + * is greater than the {@link #getCapacity peer's capacity} + */ + public void read(EndPoint peer, long offset, BufferSequence bufseq, + Object receipt); + + /** + * Request a write into the peer's memory from the specified BufferSequence. + *

+ * Upon {@link Event.Type#RECEIPT completion} of the asynchronous + * operation the peer's memory segment will have been updated with the + * contents of the supplied BufferSequence. + * + * @param peer the target EndPoint to write to + * @param offset the offset into the peer's memory to start writing to + * @param bufseq the buffers to read from + * @param receipt the optional operation receipt + * + * @throws IllegalArgumentException if the peer is unknown to the Bus + * @throws IndexOutOfBoundsException if the offset is negative, or the + * offset+{@link BufferSequence#getLength bufseq.getLength()} + * is greater than the {@link #getCapacity peer's capacity} + */ + public void write(EndPoint peer, long offset, BufferSequence bufseq, + Object receipt); + + /** + * Request an atomic compare and swap (CAS) operation on an eight byte + * word in the peer's memory. + *

+ * The CAS operation may only be performed at a word-aligned offset. + * As the hosted memory region itself is word-aligned any offset where + * offset % 8 == 0 is known to be word-aligned. + *

+ * The endianness of the underlying remote bytes is dependent upon the + * peer's environment. The Bus pair will handle any necessary conversion + * of the in/out long values. The endianness only becomes + * relevant in the case where the same memory region is accessed with + * via other means such as {@link #read}, {@link #write}, or {@link + * #getBufferSequence()}, as those methods will not perform any endian + * conversion. + *

+ * Upon {@link Event.Type#RECEIPT completion} of the asynchronous + * operation the result holder will contain the value of peer's word at + * the time the compare was performed. Success of the completed CAS can be + * identified by comparing the value in the result holder against the + * supplied expected value. If these values are equal then the swap + * occurred and the peer's memory was updated, otherwise the peer's memory was not updated. + * + * @param peer the target EndPoint + * @param offset the offset into the peer's memory + * @param expect the expected value + * @param update the new value + * @param result the holder for the deferred result + * @param receipt the optional operation receipt + * + * @throws IllegalArgumentException if the peer is unknown to the Bus + * @throws IllegalArgumentException if the offset is not suitable for use + * in a CAS operation on this bus for example is not on a word + * boundary + * @throws IndexOutOfBoundsException if the offset is negative, or the + * offset+8 is greater than the {@link #getCapacity + * peer's capacity} + */ + public void compareAndSwap(EndPoint peer, long offset, long expect, + long update, AtomicLong result, Object receipt); + + /** + * Request an atomic increment on an eight byte word on the peer. + *

+ * The atomic increment operation has the same endianness and word-alignment + * issues as described for the {@link #compareAndSwap CAS} operation. + *

+ * Upon {@link Event.Type#RECEIPT completion} of the asynchronous operation + * the peer's memory segment will have been incremented, and the result + * holder will contain the (pre-incremented) prior value. + * + * @param peer the target EndPoint + * @param offset the offset into the peer's memory + * @param delta the amount to increment by (may be negative) + * @param result the holder for the deferred result + * @param receipt the optional operation receipt + * + * @throws IllegalArgumentException if the peer is unknown to the Bus + * @throws IllegalArgumentException if the offset is not suitable for use + * in a CAS operation on this bus for example is not on a word + * boundary + * @throws IndexOutOfBoundsException if the offset is negative, or the + * offset+8 is greater than the {@link #getCapacity + * peer's capacity} + */ + public void getAndAdd(EndPoint peer, long offset, long delta, + AtomicLong result, Object receipt); + + /** + * Signal a peer. + *

+ * Upon {@link Event.Type#RECEIPT completion} of the asynchronous + * operation it is guaranteed that the peer will eventually have a + * {@link Event.Type#SIGNAL SIGNAL} event emitted to its event collector. + * + * @param peer the target EndPoint + * @param lContent the {@link Event#getContent() content} + * to provide in the SIGNAL event as a {@link Number} + * @param receipt the operation receipt + * + * @throws IllegalArgumentException if the peer is unknown to the Bus + */ + public void signal(EndPoint peer, long lContent, Object receipt); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/MessageBus.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/MessageBus.java new file mode 100644 index 0000000000000..a00659078e968 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/MessageBus.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus; + + +import com.oracle.coherence.common.io.BufferSequence; + + +/** + * A MessageBus is a Bus that provides a message-passing communication model. + * + * @author mf/gg/cp 2010.10.04 + */ +public interface MessageBus + extends Bus + { + /** + * Send a message to an EndPoint. + *

+ * Upon {@link Event.Type#RECEIPT completion} of the asynchronous + * operation it is guaranteed that the peer will eventually have a + * {@link Event.Type#MESSAGE MESSAGE} event with the specified + * BufferSequence contents emitted to its event collector. + * + * @param peer the target EndPoint + * @param bufseq the contents of the message to send + * @param receipt the optional receipt + * + * @throws IllegalArgumentException if the peer is unknown to the Bus + * @throws UnsupportedOperationException if the message size exceeds the + * size supported by the bus + */ + public void send(EndPoint peer, BufferSequence bufseq, Object receipt); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/package.html new file mode 100644 index 0000000000000..367873fe566dc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/package.html @@ -0,0 +1,9 @@ + +The Exabus package provides access to high performance IO related classes. +

+Of specific interest is the MessageBus class which provides an asynchronous binary messaging interface. MessageBus +instances may be obtained from the Depot which serves as a factory. +

+ +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/spi/Driver.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/spi/Driver.java new file mode 100644 index 0000000000000..fa28e7b97791a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/spi/Driver.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus.spi; + +import com.oracle.coherence.common.net.exabus.Bus; +import com.oracle.coherence.common.net.exabus.Depot; +import com.oracle.coherence.common.net.exabus.EndPoint; + + +/** + * A Driver represents a distinct bus implementation. + * + * @author mf 2010.10.02 + */ +public interface Driver + { + /** + * Set the depot associated with this driver. + * + * @param depot the depot associated with the driver + */ + public void setDepot(Depot depot); + + /** + * Return the depot associated with this driver. + * + * @return the depot associated with this driver + */ + public Depot getDepot(); + + /** + * Resolve the EndPoint for the specified canonical name. + * + * @param sName the EndPoint's canonical name + * + * @return the EndPoint or null if the format is not supported + * + * @throws IllegalArgumentException if the format is supported, but the + * address is not resolvable + */ + public EndPoint resolveEndPoint(String sName); + + /** + * Indicate if the specified EndPoint is supported by this driver. + * + * @param point the EndPoint + * + * @return true iff the EndPoint is supported + */ + public boolean isSupported(EndPoint point); + + /** + * Create a new Bus bound to the specified local EndPoint. + * + * @param pointLocal the local EndPoint or null for an ephemeral EndPoint + * + * @return the Bus + * + * @throws IllegalArgumentException if the EndPoint is not compatible + */ + public Bus createBus(EndPoint pointLocal); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/AbstractPollingEventCollector.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/AbstractPollingEventCollector.java new file mode 100644 index 0000000000000..430c90904144c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/AbstractPollingEventCollector.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus.util; + +/** + * Abstract implementation of a PollingEventCollector + * + * @author mf 2013.02.19 + */ +public abstract class AbstractPollingEventCollector + implements PollingEventCollector + { + // ----- PollingEventCollector methods ---------------------------------- + + /** + * Bind the collector to the bus instance it will poll. + * + * This method is not for application use, but is to be called by busses which support binding themselves + * to this type of collector. + * + * @param bus the bus to bind to + * + * @throws IllegalStateException if already bound + */ + public void bind(BusProcessor bus) + { + if (m_busProcessor == null) + { + m_busProcessor = bus; + } + else + { + throw new IllegalStateException("collector is already bound to a bus"); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The bus (if any) to which this collector is bound. + */ + protected BusProcessor m_busProcessor; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/MessageBusTest.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/MessageBusTest.java new file mode 100644 index 0000000000000..e65122899aec3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/MessageBusTest.java @@ -0,0 +1,3205 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus.util; + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.base.Hasher; + +import com.oracle.coherence.common.net.exabus.Bus; +import com.oracle.coherence.common.net.exabus.Depot; +import com.oracle.coherence.common.net.exabus.EndPoint; +import com.oracle.coherence.common.net.exabus.Event; +import com.oracle.coherence.common.net.exabus.MemoryBus; +import com.oracle.coherence.common.net.exabus.MessageBus; + + +import com.oracle.coherence.common.internal.util.Histogram; +import com.oracle.coherence.common.internal.util.ScaledHistogram; + +import com.oracle.coherence.common.net.SSLSettings; + +import com.oracle.coherence.common.base.Collector; +import com.oracle.coherence.common.base.Disposable; +import com.oracle.coherence.common.base.Factory; +import com.oracle.coherence.common.base.Notifier; +import com.oracle.coherence.common.base.Pollable; +import com.oracle.coherence.common.base.SingleWaiterCooperativeNotifier; + +import com.oracle.coherence.common.collections.SingleConsumerBlockingQueue; + +import com.oracle.coherence.common.internal.Platform; +import com.oracle.coherence.common.internal.net.socketbus.SocketBusDriver; + +import com.oracle.coherence.common.io.BufferManager; +import com.oracle.coherence.common.io.BufferManagers; +import com.oracle.coherence.common.io.BufferSequence; +import com.oracle.coherence.common.io.BufferSequenceInputStream; +import com.oracle.coherence.common.io.BufferSequenceOutputStream; +import com.oracle.coherence.common.io.Buffers; +import com.oracle.coherence.common.io.MultiBufferSequence; +import com.oracle.coherence.common.io.SingleBufferSequence; + +import com.oracle.coherence.common.net.SocketSettings; +import com.oracle.coherence.common.net.exabus.spi.Driver; + +import com.oracle.coherence.common.util.Bandwidth; +import com.oracle.coherence.common.util.Bandwidth.Rate; +import com.oracle.coherence.common.util.Duration; +import com.oracle.coherence.common.util.MemorySize; + +import java.io.DataInput; +import java.io.IOException; +import java.io.PrintStream; +import java.net.SocketOptions; +import java.net.URL; +import java.nio.ByteBuffer; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.util.*; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +/** + * MessageBusTest is an application for testing the performance characteristics + * of MessageBus implementations and the network on which they operate. + * + * @author mf 2010.12.14 + */ +public class MessageBusTest + { + // ----- inner class: Receipt ------------------------------------------- + + /** + * Receipt object used in the test. + */ + public static class Receipt + implements Disposable + { + public Receipt(long ldtNanos, Disposable garbage) + { + m_ldtNanos = ldtNanos; + m_garbage = garbage; + } + + public long getTimestampNanos() + { + return m_ldtNanos; + } + + @Override + public void dispose() + { + Disposable garbage = m_garbage; + if (garbage != null) + { + m_garbage = null; + garbage.dispose(); + } + } + + // ----- data members ------------------------------------------ + + long m_ldtNanos; + Disposable m_garbage; + } + + // ----- inner class: StampedEvent -------------------------------------- + + /** + * StampedEvent adds a nano-resolution timestamp to events at the time + * of construction. + */ + public static class StampedEvent + implements Event + { + public StampedEvent(Event evt) + { + m_evt = evt; + m_ldtNanos = System.nanoTime(); + } + + @Override + public Type getType() + { + return m_evt.getType(); + } + + @Override + public EndPoint getEndPoint() + { + return m_evt.getEndPoint(); + } + + @Override + public Object getContent() + { + return m_evt.getContent(); + } + + @Override + public Object dispose(boolean fTakeContent) + { + return m_evt.dispose(fTakeContent); + } + + @Override + public void dispose() + { + m_evt.dispose(); + } + + public long getTimestampNanos() + { + return m_ldtNanos; + } + + // ----- data members ------------------------------------------ + + protected final Event m_evt; + protected long m_ldtNanos; + } + + // ----- inner class: EventProcessor ------------------------------------ + + /** + * EventProcessor is the basis for a thread which will handle bus event + * streams. + */ + public static class EventProcessor + extends Thread + implements Runnable + { + // ----- constructors ------------------------------------------- + + /** + * Construct an EventProcessor. + * + * @param bus the Bus for this processor + * @param setPeer the peers to transmit to + * @param setReady the set that is ready to be transmitted to + * @param aTransmitter the associated transmitters for this bus + * @param cbsIn the target inbound data rate + * @param nFlushOn the number of response messages to send before flushing + * @param fBacklogGlobal an AtomicInteger to serve as a flag; non-zero value + * if the bus has declared a global backlog + */ + public EventProcessor(Bus bus, Set setPeer, Set setReady, + Transmitter[] aTransmitter, long cbsIn, int nFlushOn, AtomicInteger fBacklogGlobal) + { + super("EventProcessor(" + bus.getLocalEndPoint() + ")"); + + m_bus = bus; + m_busMsg = bus instanceof MessageBus ? (MessageBus) bus : null; + f_fBacklogGlobal = fBacklogGlobal; + m_setPeer = setPeer; + m_setReady = setReady; + m_cbsIn = cbsIn; + m_nFlushOn = nFlushOn; + m_aTransmitter = aTransmitter; + m_abChunk = new byte[s_cbChunk]; + } + + // ----- EventProcessor interface ------------------------------- + + /** + * Return the Bus used by this processor. + * + * @return the bus + */ + public Bus getBus() + { + return m_bus; + } + + /** + * Return the MessageBus used by this processor if any. + * + * @return the message bus + */ + public MessageBus getMessageBus() + { + return m_busMsg; + } + + /** + * Return the set of peers this processor is configured to transmit + * to + * + * @return the peer set. + */ + public Set getPeers() + { + return m_setPeer; + } + + /** + * Return the number of bytes received. + * + * @return the number of bytes received + */ + public long getBytesIn() + { + return m_cbIn; + } + + /** + * Return the number of bytes sent. + * + * @return the number of bytes sent + */ + public long getBytesOut() + { + return m_cbOut; + } + + /** + * Return the number of messages received. + * + * @return the number of messages received + */ + public long getMessagesIn() + { + return m_cMsgIn; + } + + /** + * Return the number of Messages sent. + * + * @return the number of Messages sent + */ + public long getMessagesOut() + { + return m_cMsgOut; + } + + /** + * Return the number of receipt samples + * + * @return the number of receipt samples + */ + public long getReceiptSamples() + { + return m_cReceiptTimings; + } + + /** + * Return the cumulative receipts time. + * + * @return the cumulative receipts time. + */ + public long getReceiptNanos() + { + return m_cReceiptsNanos; + } + + /** + * Return the number of returned receipts. + * + * @return the number of returned receipts + */ + public long getReceiptsIn() + { + return m_cReceiptsIn; + } + + /** + * Return the number of received responses. + * + * @return the number of received responses + */ + public long getResponsesIn() + { + return m_cResponseIn; + } + + /** + * Return the cumulative response time. + * + * @return the cumulative response time. + */ + public long getResponseNanos() + { + return m_cResponseNanos; + } + + /** + * Return they latency histogram. + * + * @return the latency Histogram + */ + public Histogram getResponseLatencyHistogram() + { + return f_histLatency; + } + + /** + * Return the number of local backlog events received. + * + * @return the number of local backlog events received + */ + public long getLocalBacklogEvents() + { + return m_cBacklogEventsLocal; + } + + /** + * Return the total duration of local backlogs. + * + * @return the local backlog duration + */ + public long getLocalBacklogMillis() + { + return m_cBacklogMillisLocal; + } + + /** + * Return the number of remote backlog events received. + * + * @return the number of remote backlog events received + */ + public long getRemoteBacklogEvents() + { + return m_cBacklogEventsRemote; + } + + /** + * Return the number of connections. + * + * @return the connection count + */ + public long getConnectionCount() + { + return m_cConnections; + } + + /** + * Handle an inbound message. + * + * @param event the event + * + * @return true if a flush is required + * + * @throws IOException if an IO error occurs + */ + protected boolean onMessage(Event event) + throws IOException + { + BufferSequence bufseq = (BufferSequence) event.getContent(); + + DataInput in = new BufferSequenceInputStream(bufseq); + + // format: + // 4B transmitter ID + // 1B msg type + // 8B timestamp (or zero) + // 8B payload size + // payload + long cbMessage = bufseq.getLength(); + + m_cbIn += cbMessage; + + long cMsgIn = m_cMsgIn++; + int nId = in.readInt(); + boolean fResp = in.readBoolean(); + long ldtNanos = in.readLong(); + long cbPayload = in.readLong(); + + if (MSG_HEADER_SIZE + cbPayload != cbMessage) + { + s_cErrors.incrementAndGet(); + System.err.println("unexpecteded message size " + cbMessage + " rather then " + + (MSG_HEADER_SIZE + cbPayload) + ", msg=" + Buffers.toString(bufseq) + " from " + event); + throw new IllegalStateException("unexpected message size in " + + event); + } + + if (s_fVerbose && (cMsgIn % 10000) == 0) + { + // update thread-name to show movement + Thread thread = Thread.currentThread(); + String sName = thread.getName(); + int of = sName.lastIndexOf('#'); + + thread.setName(sName.substring( + 0, of == -1 ? sName.length() : of) + + "#" + cMsgIn); + } + + // TODO: validate message ordering, this could be a bit problematic + // since the test allows multiple threads to send to the same + // peer over the same local bus, so we'd need to improve the + // test's message header + + // TODO: optionally verify contents to account for + int cbChunk = s_cbChunk; + switch (cbChunk) + { + default: + for (; cbPayload >= cbChunk; cbPayload -= cbChunk) + { + in.readFully(m_abChunk); + } + // fall through + + case 8: + for (; cbPayload >= 8; cbPayload -= 8) + { + in.readLong(); + } + // fall through + + case 4: + for (; cbPayload >= 4; cbPayload -= 4) + { + in.readInt(); + } + // fall through + + case 2: + for (; cbPayload >= 2; cbPayload -= 2) + { + in.readShort(); + } + // fall through + + case 1: + for (; cbPayload >= 1; cbPayload -= 1) + { + in.readByte(); + } + // fall through + + case 0: + // read nothing + break; + } + + EndPoint epResponse = null; + boolean fFlush = false; + + if (fResp) + { + // this is an response + if (s_fRelay) + { + // server receiving response to relayed request + if (s_fBlock) + { + RelayResponse resp = s_mapRelayResponse.remove(nId); + epResponse = resp.peer; + nId = resp.nId; + } + // else; response was sent immediately during relay + } + else // client awaiting response + { + if (ldtNanos > 0) + { + ++m_cResponseIn; + long cNanos = System.nanoTime() - ldtNanos; + m_cResponseNanos += cNanos; + f_histLatency.addSample((int) (cNanos / 1000)); + + if (cNanos > m_cResponseNanosMax) + { + m_cResponseNanosMax = cNanos; + } + if (cNanos < m_cResponseNanosMin) + { + m_cResponseNanosMin = cNanos; + } + } + + Transmitter tx = m_aTransmitter[nId & 0x0FFFF]; // upper 16 bits are just a request counter for the thread + + if (s_fBlock) + { + tx.signalResult(nId >>> 16); + } + } + } + else if (s_fRelay && !m_setPeer.contains(event.getEndPoint())) + { + // relay message to a configured peer + + Iterator iterRelay = m_iterPeerRelay; + EndPoint epRelay; + if (iterRelay == null || !iterRelay.hasNext()) + { + iterRelay = m_iterPeerRelay = m_setPeer.iterator(); + } + epRelay = iterRelay.next(); + + if (ldtNanos != 0) + { + if (s_fBlock) + { + // defer response like coherence backups + RelayResponse resp = new RelayResponse(event.getEndPoint(), nId); + nId = s_atomicRelayId.incrementAndGet(); + s_mapRelayResponse.put(nId, resp); + } + else + { + // relay plus immediate response (like async backups) + epResponse = event.getEndPoint(); + } + } + + BufferSequence seqRelay = getMessage(nId, /*fResp*/ false, ldtNanos); + + ++m_cMsgOut; + m_cbOut += seqRelay.getLength(); + + // TODO: consider option to send reply upon delivery confirmation? + getMessageBus().send(epRelay, seqRelay, s_fReceipts ? new Receipt(0, seqRelay) : null); + fFlush = true; + } + else if (ldtNanos != 0) + { + // this is a non-relay request + epResponse = event.getEndPoint(); + } + + if (epResponse != null) + { + // send a response message + if (s_fPrompt) + { + System.out.println("Press ENTER to send messge to " + event.getEndPoint()); + System.in.read(); + } + + BufferSequence seqresponse = getMessage(nId, /*fResp*/ true, ldtNanos); + + ++m_cMsgOut; + m_cbOut += seqresponse.getLength(); + + getMessageBus().send(epResponse, seqresponse, s_fReceipts ? new Receipt(0, seqresponse) : null); + fFlush = true; + } + + return fFlush; + } + + /** + * Process an event + * + * @param event the event to process + * + * @return true if a flush is required + */ + public boolean onEvent(Event event) + { + try + { + Bus bus = m_bus; + EndPoint bindEp = bus.getLocalEndPoint(); + Set setPeer = m_setPeer; + Set setReady = m_setReady; + boolean fFlush = false; + + EndPoint ep = event.getEndPoint(); + + switch (event.getType()) + { + case OPEN: + case CLOSE: + System.err.println(event); + break; + + case CONNECT: + if (!s_fSingleUseConnection) + { + System.err.println(event + " on " + bindEp); + } + ++m_cConnections; + // fall-through to BACKLOG_NORMAL + + case BACKLOG_NORMAL: + if (s_fFlowControl || event.getType() == Event.Type.CONNECT) + { + if (ep == null) + { + synchronized (f_fBacklogGlobal) + { + if (!f_fBacklogGlobal.compareAndSet(1, 0)) + { + System.err.println("received out of order event " + event + " on " + bindEp); + s_cErrors.incrementAndGet(); + } + f_fBacklogGlobal.notifyAll(); + } + } + else if (ep == bindEp) + { + long ldtStart = m_ldtBacklogLocalStart; + if (ldtStart == 0) + { + System.err.println("received out of order event " + event + " on " + bindEp); + s_cErrors.incrementAndGet(); + } + else + { + m_cBacklogMillisLocal += System.currentTimeMillis() - ldtStart; + m_ldtBacklogLocalStart = 0; + } + } + else if (setPeer.contains(ep)) + { + boolean fAdded; + if (setReady.isEmpty()) + { + synchronized (setReady) + { + fAdded = setReady.add(ep); + setReady.notifyAll(); + } + } + else + { + fAdded = setReady.add(ep); + } + + if (!fAdded) + { + System.err.println("received out of order event " + event + " on " + bindEp); + s_cErrors.incrementAndGet(); + } + } + } + break; + + case BACKLOG_EXCESSIVE: + if (s_fFlowControl) + { + if (ep == null) + { + ++m_cBacklogEventsRemote; + if (!f_fBacklogGlobal.compareAndSet(0, 1)) + { + // event appear to be out of sequence + System.err.println("received out of order event " + event + " on " + bindEp); + s_cErrors.incrementAndGet(); + } + } + else if (bindEp == ep) + { + ++m_cBacklogEventsLocal; + if (m_ldtBacklogLocalStart != 0) + { + System.err.println("received out of order event " + event + " on " + bindEp); + s_cErrors.incrementAndGet(); + } + m_ldtBacklogLocalStart = System.currentTimeMillis(); + } + else + { + ++m_cBacklogEventsRemote; + if (!setReady.remove(ep) && setPeer.contains(ep)) + { + // event appear to be out of sequence + System.err.println("received out of order event " + event + " on " + bindEp); + s_cErrors.incrementAndGet(); + } + // else; not a peer we're sending to; ordering not tracked by test + } + } + + break; + + case DISCONNECT: + if (!s_fSingleUseConnection) + { + System.err.println(event + " on " + bindEp); + Throwable t = (Throwable) event.getContent(); + if (t != null && (!(t instanceof IOException) || s_fVerbose)) + { + t.printStackTrace(System.err); + } + } + bus.release(ep); + break; + + case RELEASE: + if (!s_fSingleUseConnection) + { + System.err.println(event + " on " + bindEp); + } + --m_cConnections; + synchronized (setReady) + { + setReady.remove(ep); + setReady.notifyAll(); + } + break; + + case MESSAGE: + fFlush = onMessage(event); + break; + + case RECEIPT: + Receipt receipt = (Receipt) event.getContent(); + long ldtNanosSent = receipt.getTimestampNanos(); + ++m_cReceiptsIn; + if (ldtNanosSent != 0) + { + long cNanos = (s_fPollingCollector ? System.nanoTime() : ((StampedEvent) event).getTimestampNanos()) - ldtNanosSent; + m_cReceiptsNanos += cNanos; + ++m_cReceiptTimings; + + if (m_busMsg == null) + { + f_histLatency.addSample((int) (cNanos / 1000L)); + } + } + + receipt.dispose(); + break; + + default: + System.err.println(event + " on " + bindEp); + break; + } + + event.dispose(); + return fFlush; + } + catch (Throwable e) + { + s_cErrors.incrementAndGet(); + System.err.println("fatal error after receiving " + m_cMsgIn + " messages"); + e.printStackTrace(System.err); + throw new IllegalStateException(e); + } + } + + /** + * Add an event for the processor to handle. + * + * @param event the event to process + * + * @return true if a flush is required + */ + public boolean add(Event event) + { + BlockingQueue queue = m_queue; + if (queue == null) + { + // This synchronization should be mostly uncontended. Contention is only possible + // if we are running in reentrant mode, have multiple peers sending to us, and + // they bus impl uses different threads to deliver their events, and the + // DemultiplexingCollector happens to hash them to the same EventProcessor. The + // level of contention can be roughly controlled via -rxThreads, setting to a + // large negative value. Note that the default is such a value. + + // Note: we break the event stream up into events related to transmit and receive + // to allow this independent streams to be processed concurrently, this is important + // for buses which may emit these events from different threads, as we don't want + // the test to add unecessary contention + switch (event.getType()) + { + case BACKLOG_EXCESSIVE: + case BACKLOG_NORMAL: + if (event.getEndPoint() == m_bus.getLocalEndPoint()) + { + synchronized (f_syncRxEvents) + { + return onEvent(event); + } + } + // else; fall through + + case RECEIPT: + synchronized (f_syncTxEvents) + { + return onEvent(event); + } + + case MESSAGE: + synchronized (f_syncRxEvents) + { + return onEvent(event); + } + + default: + synchronized (f_syncTxEvents) + { + synchronized (f_syncRxEvents) + { + return onEvent(event); + } + } + } + } + else + { + queue.add(event); + return false; + } + } + + + // ----- Runnable interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void run() + { + Bus bus = m_bus; + final BlockingQueue queue = m_queue; + int nFlushOn = m_nFlushOn; + int cFlushEvent = 0; + long cbsInTarget = m_cbsIn; + long cbEval = cbsInTarget / 8; + long ldtLast = 0; + long cbInLast = 0; + long cThrottleMillis = 1; + int nThrottle = 1000; // random + Pollable poll = s_fPollingCollector + ? (QueueingEventCollector) bus.getEventCollector() + : new Pollable() + { + @Override + public Event poll() + { + return queue.poll(); + } + + @Override + public Event poll(long timeout, TimeUnit unit) + throws InterruptedException + { + return timeout == Long.MAX_VALUE + ? queue.take() + : queue.poll(timeout, unit); + } + }; + + try + { + for (int i = 0; ; ++i) + { + Event event = poll.poll(); + while (event == null) + { + if (cFlushEvent > 0) + { + cFlushEvent = 0; + bus.flush(); + } + SingleWaiterCooperativeNotifier.flush(); + + event = poll.poll(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } + + if (onEvent(event) && ++cFlushEvent > nFlushOn) + { + cFlushEvent = 0; + bus.flush(); + } + + if (cbsInTarget > 0) + { + // throttle the input rate; this allows simulating + // a transmitter which outpaces the receiver, we want + // to see that the transmitter will detect our backlog + // and throttle itself accordingly + if ((i % nThrottle) == 0) + { + if (cFlushEvent > 0) + { + cFlushEvent = 0; + bus.flush(); + } + Blocking.sleep(cThrottleMillis); + } + long cbIn = m_cbIn; + long cbDelta = cbIn - cbInLast; + + if (cbInLast == 0 && ldtLast == 0) + { + // start the clock + ldtLast = System.currentTimeMillis(); + } + else if (cbDelta > cbEval) + { + long ldtNow = System.currentTimeMillis(); + long cMillis = Math.max(1, ldtNow - ldtLast); + double cbs = (cbDelta * 1000) / cMillis; + double dfl = cbsInTarget / cbs; + + nThrottle = Math.max(1, (int) Math.round(nThrottle * dfl)); + int nThrottleNew = (int) Math.round(nThrottle * dfl); + if (nThrottleNew == 0) + { + nThrottleNew = 1; + ++cThrottleMillis; // go slower still + } + else if (nThrottleNew == nThrottle) + { + if (dfl > 1.01) + { + ++cThrottleMillis; + } + else if (dfl < 0.09) + { + --cThrottleMillis; + } + } + + cbInLast = cbIn; + ldtLast = ldtNow; + i = 0; + } + } + + } + } + catch (Exception e) + { + throw new IllegalStateException(e); + } + } + + // ----- Thread interface --------------------------------------- + + @Override + public void start() + { + m_queue = new SingleConsumerBlockingQueue(); + + super.start(); + } + + + /** + * Marker class for profiling. + */ + public static class TxEventSynchronizer + { + }; + + /** + * Marker class for profiling. + */ + public static class RxEventSynchronizer + { + }; + + public static class RelayResponse + { + public RelayResponse(EndPoint peer, int nId) + { + this.peer = peer; + this.nId = nId; + } + + EndPoint peer; + int nId; + } + + // ----- data members ------------------------------------------- + + /** + * The Bus for this processor. + */ + protected final Bus m_bus; + + protected final MessageBus m_busMsg; + + /** + * Monitor protecting processing of transmit related events. + */ + protected final Object f_syncTxEvents = new TxEventSynchronizer(); + + /** + * Monitor protecting processing of receive related events. + */ + protected final Object f_syncRxEvents = new RxEventSynchronizer(); + + /** + * AtomicInteger that serves as a flag; non-zero if the bus has + * declared a global backlog. + */ + protected final AtomicInteger f_fBacklogGlobal; + + /** + * The event queue, or null for reentrant processing. + */ + protected BlockingQueue m_queue; + + /** + * Set of peer's to transmit to. + */ + protected final Set m_setPeer; + + /** + * Iterator over the peer set for use as a relay + */ + protected Iterator m_iterPeerRelay; + + /** + * Used by a relay to identify which client a response needs to be sent to. + */ + protected final static Map s_mapRelayResponse = new ConcurrentHashMap(); + + /** + * Relay message ID generator. + */ + protected final static AtomicInteger s_atomicRelayId = new AtomicInteger(); + + /** + * Subset of m_setPeer that ready to be transmitted to, i.e. connected + * and not backlogged. + */ + protected final Set m_setReady; + + /** + * The target inbound data rate. + */ + protected final long m_cbsIn; + + /** + * The number of messages to send before flushing. + */ + protected final int m_nFlushOn; + + /** + * The number of transmit threads to run. + */ + protected final Transmitter[] m_aTransmitter; + + /** + * The number of open connections. + */ + protected long m_cConnections; + + /** + * The number of bytes read. + */ + protected long m_cbIn; + + /** + * The number of received messages. + */ + protected long m_cMsgIn; + + /** + * The total number of received responses. + */ + protected long m_cResponseIn; + + /** + * The maximum response time. + */ + protected long m_cResponseNanosMax = -1; + + /** + * The minimum response time. + */ + protected long m_cResponseNanosMin = Long.MAX_VALUE; + + /** + * Histogram of all latencies. + */ + protected final Histogram f_histLatency = makeLatencyHistogram(); + /** + * The total RTT time for all receied responses. + */ + protected long m_cResponseNanos; + + /** + * The total number of received timed receipts. + */ + protected long m_cReceiptTimings; + + /** + * The total RTT time for all received receipts. + */ + protected long m_cReceiptsNanos; + + /** + * The total number of returned receipts. + */ + protected long m_cReceiptsIn; + + /** + * The number of bytes written. + */ + protected long m_cbOut; + + /** + * The number of sent messages. + */ + protected long m_cMsgOut; + + /** + * The total number of local backlog events received. + */ + protected long m_cBacklogEventsLocal; + + /** + * The time at which the current local backlog condition started, or zero if there is none. + */ + protected long m_ldtBacklogLocalStart; + + /** + * The total number of milliseconds for which the EventProcessor was + * locally backlogged. + */ + protected long m_cBacklogMillisLocal; + + /** + * The total number of remote backlog events received. + */ + protected long m_cBacklogEventsRemote; + + /** + * to be used for chunked reads, we don't reuse s_abChunk in order to avoid introducing + * false memory contention + */ + protected final byte[] m_abChunk; + } + + // ----- inner class: DemultiplexingCollector --------------------------- + + public static class DemultiplexingCollector implements Collector + { + // ----- constructors ------------------------------------------- + + /** + * Construct a DemultiplexingCollector which dispatches to the + * specified queues. + * + * @param bus the bus associated with all the processors + * @param aProcessor the queues to dispatch other events to + */ + public DemultiplexingCollector(Bus bus, EventProcessor[] aProcessor) + { + m_bus = bus; + m_aProcessor = aProcessor; + m_acbReceived = new AtomicLong[aProcessor.length]; + + for (int i = 0, c = m_acbReceived.length; i < c; ++i) + { + m_acbReceived[i] = new AtomicLong(); + } + } + + // ----- Collector interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public void add(Event event) + { + EndPoint pointSrc = event.getEndPoint(); + int nHash = pointSrc == null + ? 0 + : pointSrc.hashCode(); + + switch (event.getType()) + { + case RECEIPT: + if (((Receipt) event.getContent()).getTimestampNanos() != 0) + { + // the event contains a timestamped receipt, insert the + // corresponding "end" receipt here as it is the first point + // at which the application code sees the response. + event = new StampedEvent(event); + } + break; + + case MESSAGE: + m_acbReceived[Hasher.mod(nHash, m_acbReceived.length)] + .addAndGet(((BufferSequence) event.getContent()).getLength()); + break; + + default: + break; + } + + if (m_aProcessor[Hasher.mod(nHash, m_aProcessor.length)].add(event)) + { + m_fFlush = true; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void flush() + { + if (m_fFlush) + { + m_fFlush = false; + m_bus.flush(); + } + + SingleWaiterCooperativeNotifier.flush(); + } + + // ----- helpers ------------------------------------------------ + + /** + * Return the total number of received bytes. + * + * @return the total number of received bytes + */ + public long getReceivedBytes() + { + long cb = 0; + for (int i = 0, c = m_acbReceived.length; i < c; ++i) + { + cb += m_acbReceived[i].get(); + } + return cb; + } + + + // ----- data members ------------------------------------------- + + /** + * The Bus associated with the EventProcessors. + */ + protected final Bus m_bus; + + /** + * The array of processors to dispatch to. + */ + protected final EventProcessor[] m_aProcessor; + + /** + * Array containing received message size totals. + */ + protected final AtomicLong[] m_acbReceived; + + + /** + * True if the collector requires a bus.flush to be performed. + */ + protected boolean m_fFlush; + } + + + // ----- inner class: Transmitter --------------------------------------- + + /** + * A Transmitter is reponsible for sending messages on a bus to a series + * of peers. + */ + public static class Transmitter extends Thread + { + // ----- constructors ------------------------------------------- + + /** + * Construct a Transmitter. + * + * @param bus the associated bus + * @param nId the transmitter id + * @param setReady the peers to send to + * @param nFlushOn the number of messages to send before flushing + * @param fBacklogGlobal an AtomicInteger to serve as a flag; non-zero value + * if the bus has declared a global backlog + * @param cbBacklog the maximum tx backlog, or -1 for no limit + * + * @throws IOException if an IO error occurs + */ + public Transmitter( + Bus bus, int nId, Set setReady, int nFlushOn, AtomicInteger fBacklogGlobal, long cbBacklog) + throws IOException + { + super("Transmitter(" + bus.getLocalEndPoint() + ")"); + + f_bus = bus; + f_bufSeqCached = s_fCached + ? MessageBusTest.getMessage(nId, /*fResp*/ false, 0) + : null; + f_aBufCached = s_fCached + ? f_bufSeqCached.getBuffers() + : null; + f_fBacklogGlobal = fBacklogGlobal; + f_nId = nId; + f_setReady = setReady; + f_nFlushOn = nFlushOn; + f_cbTxMaxBacklog = cbBacklog; + } + + // ----- Transmitter interface ---------------------------------- + + private volatile Object m_oResult; + private final Notifier f_notifier = new SingleWaiterCooperativeNotifier(); + + public void signalResult(Object oResult) + { + long cPending = f_cPendingResponses.decrementAndGet(); + if (cPending == 0) + { + m_oResult = oResult; + f_notifier.signal(); + } + else if (cPending < 0) + { + throw new IllegalStateException(); + } + } + + public Object awaitResult() + throws InterruptedException + { + Object oResult = m_oResult; + if (oResult == null) + { + if (AWAIT_SPIN_NANOS > 0) + { + for (long ldtEnd = System.nanoTime() + AWAIT_SPIN_NANOS; + oResult == null && System.nanoTime() < ldtEnd; + oResult = m_oResult) {} + } + + for (; oResult == null; oResult = m_oResult) + { + f_notifier.await(); + } + } + + m_oResult = null; + return oResult; + } + + /** + * Reset the transmit rate. + * + * Note this may only be reset before starting the thread. + * + * @param cbs the data rate + */ + public void setTransmitRate(long cbs) + { + f_cbs = cbs; + } + + /** + * Return the number of bytes sent. + * + * @return the number of bytes sent + */ + public long getBytesOut() + { + return m_cbOut; + } + + /** + * Return the number of Messages sent. + * + * @return the number of Messages sent + */ + public long getMessagesOut() + { + return m_cMsgOut; + } + + /** + * Return the total number of milliseconds for which the transmitter + * was blocked. + * + * @return the total blocked time + */ + public long getRemoteBacklogMillis() + { + long ldtBacklogStart = m_ldtBacklogStart; + long cMillisBacklog = m_cMillisBacklog; + + return ldtBacklogStart == 0 || m_cbOut == 0 + ? cMillisBacklog + : cMillisBacklog + (System.currentTimeMillis() - ldtBacklogStart); + } + + public class BacklogTrackingReceipt + extends Receipt + { + public BacklogTrackingReceipt(long ldtNanos, Disposable garbage) + { + super(ldtNanos, garbage); + } + + @Override + public void dispose() + { + super.dispose(); + if (f_cbTxMaxBacklog != -1) + { + long cbBacklogPre = f_cbTxBacklog.getAndAdd(-s_cbMsgAvg); + long cbThreshold = f_cbTxMaxBacklog / 3; + if (cbBacklogPre > cbThreshold && cbBacklogPre - s_cbMsgAvg < f_cbTxMaxBacklog) + { + f_notifierBacklog.signal(); + } + } + } + } + + // ----- Runnable interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void run() + { + try + { + Bus bus = f_bus; + + MessageBus busMsg = null; + MemoryBus busMem = null; + + if (bus instanceof MessageBus) + { + busMsg = (MessageBus) bus; + } + else if (bus instanceof MemoryBus) + { + busMem = (MemoryBus) bus; + } + + Set setReady = f_setReady; + long cbsTarget = f_cbs; + long cbEval = cbsTarget / 8; + boolean fBlock = s_fBlock; + long nFlushOn = Math.max(fBlock + ? 1 + : 0, f_nFlushOn); + AtomicInteger cPending = f_cPendingResponses; + long lSeq = 0; + + long cThrottleMillis = 1; + long nThrottle = Math.max(1, ((int) cbEval / (s_cbMsgMax - (s_cbMsgMax - s_cbMsgMin) / 2)) / 8); + long ldtLast = System.currentTimeMillis(); + long cbThis = 0; + long cSendThrottle = 0; + long cMsgOut = 0; + + Iterator iterPeer = setReady.iterator(); + + while (true) + { + if (f_fBacklogGlobal.get() == 1) + { + long ldtStartWait = m_ldtBacklogStart = System.currentTimeMillis(); + synchronized (f_fBacklogGlobal) + { + while (f_fBacklogGlobal.get() == 1) + { + Blocking.wait(f_fBacklogGlobal); + } + } + if (cbThis > 0) + { + m_cMillisBacklog += System.currentTimeMillis() - ldtStartWait; + } + } + + if (!iterPeer.hasNext()) // iterator exhausted + { + iterPeer = setReady.iterator(); + + if (!iterPeer.hasNext()) + { + // all members in backlog + bus.flush(); + synchronized (setReady) + { + for (iterPeer = setReady.iterator(); !iterPeer.hasNext(); iterPeer = setReady + .iterator()) + { + long ldtStartWait = m_ldtBacklogStart = System.currentTimeMillis(); + Blocking.wait(setReady); + m_ldtBacklogStart = 0; + if (cbThis > 0) + { + m_cMillisBacklog += System.currentTimeMillis() - ldtStartWait; + } + } + } + } + } + + // create and send message + BufferSequence bufseq = null; + try + { + if (fBlock) + { + cPending.incrementAndGet(); + } + + EndPoint peer = iterPeer.next(); + + if (s_fPrompt) + { + System.out.println("Press ENTER to send messge to " + peer); + System.in.read(); + } + + // determine if we will take a latency measurement + long ldtNanos = s_nLatencyFreq != 0 && (++lSeq % s_nLatencyFreq == 0) + ? System.nanoTime() // latency measurement + : fBlock + ? -1 // blocking request + : 0; // streaming with no latency measurement + + bufseq = getMessage(ldtNanos); + long cb = bufseq.getLength(); + + Receipt receipt = s_fReceipts + ? new BacklogTrackingReceipt(Math.max(0, ldtNanos), bufseq == f_bufSeqCached ? null : bufseq) + : null; + + if (busMsg != null) + { + busMsg.send(peer, bufseq, receipt); + } + else // RDMA test + { + if (s_fBlock) + { + // on MemoryBus use receipts as responses when in blocking mode + final Disposable garbage = receipt.m_garbage; + receipt.m_garbage = new Disposable() + { + @Override + public void dispose() + { + signalResult(Integer.valueOf(0)); + + if (garbage != null) + { + garbage.dispose(); + } + } + }; + } + + long cbPeer = busMem.getCapacity(peer); + long lOffset = Hasher.mod(s_rand.nextLong(), (cbPeer - bufseq.getLength())); + switch ((int) busMem.getCapacity(null)) + { + case 0: + { + if (cbPeer == 0) // test signaling + { + cb = 8; + busMem.signal(peer, 1234, receipt); + } + else // test RDMA writes + { + busMem.write(peer, lOffset, bufseq, receipt); + } + break; + } + + case 8: // test ATOMICs + { + cb = 16; + busMem.getAndAdd(peer, 0, 1, /*result*/ null, receipt); + break; + } + + default: // test RDMA reads: TODO: read into hosted memory? + { + busMem.read(peer, lOffset, bufseq, receipt); + break; + } + } + } + + cMsgOut = m_cMsgOut++; + cbThis += cb; + m_cbOut += cb; + + if (f_cbTxMaxBacklog != -1 && f_cbTxBacklog.addAndGet(s_cbMsgAvg) > f_cbTxMaxBacklog) + { + bus.flush(); + f_notifierBacklog.await(); + } + + // periodic flush and block + if (nFlushOn != 0 && (cMsgOut % nFlushOn) == 0) + { + bus.flush(); + + if (fBlock) + { + // wait for response(s) + int nSeq = (Integer) awaitResult(); + int nExp = (int) ((m_cMsgOut - 1) & 0x0FFFF); + if (nSeq != 0 && nExp != nSeq) + { + throw new IllegalStateException("unexpected response id " + nSeq + " expected " + nExp); + } + } + } + + if (s_fSingleUseConnection) + { + bus.disconnect(peer); + synchronized (setReady) + { + // wait for release to be processed + while (setReady.contains(peer)) + { + setReady.wait(); + } + } + bus.connect(peer); + } + } + catch (IllegalArgumentException e) + { + // should be from a concurrent release; not an error + if (fBlock) + { + cPending.decrementAndGet(); + } + + if (bufseq != null && bufseq != f_bufSeqCached) + { + bufseq.dispose(); + } + } + + // transmit throttle + if (cbsTarget > 0 && (++cSendThrottle % nThrottle) == 0) + { + bus.flush(); + Blocking.sleep(cThrottleMillis); + + if (cbThis >= cbEval) + { + // evaluate data rate against the target and adjust + long ldtNow = System.currentTimeMillis(); + long cMillis = ldtNow - ldtLast; + if (cMillis == 0) + { + ++nThrottle; + } + else + { + double cbs = (cbThis * 1000) / cMillis; + double dfl = cbsTarget / cbs; + int nThrottleNew = (int) Math.round(nThrottle * dfl); + if (nThrottleNew == 0) + { + nThrottleNew = 1; + ++cThrottleMillis; // go slower still + } + else if (nThrottleNew == nThrottle) + { + if (dfl > 1.01) + { + ++cThrottleMillis; + } + else if (dfl < 0.09) + { + --cThrottleMillis; + } + } + nThrottle = nThrottleNew; + } + + cSendThrottle = 0; + cbThis = 0; + ldtLast = ldtNow; + } + } + + // simple stats updating in thread name + if (s_fVerbose && (cMsgOut % 10000) == 0) + { + // update thread-name to show movement + Thread thread = Thread.currentThread(); + String sName = thread.getName(); + int of = sName.lastIndexOf('#'); + + thread.setName(sName.substring(0, of == -1 + ? sName.length() + : of) + + "#" + cMsgOut); + } + } + } + catch (Throwable e) + { + s_cErrors.incrementAndGet(); + System.err.println("fatal error after sending " + m_cMsgOut + " messages"); + throw new IllegalStateException(e); + } + } + + // ----- helpers ------------------------------------------------ + + /** + * Construct a message. + * + * @param ldtSent the send time if a latency measurement is required, or 0 for none + * + * @return the message + * + * @throws IOException if an IO error occurs + */ + public BufferSequence getMessage(long ldtSent) + throws IOException + { + if (s_fCached) + { + if (s_fBlock) + { + // since we're blocking and asking for a new message the old one is free for re-use; just + // overwrite the timestamp + // Note that reusing the same bufseq would produce ByteBuffer.duplicates which are expensive to + // GC, so instead we produce new sequences of the now unused buffers + for (ByteBuffer buf : f_aBufCached) + { + buf.position(0); + } + f_aBufCached[0].putLong(5, ldtSent); + return f_aBufCached.length == 1 + ? new SingleBufferSequence(null, f_aBufCached[0]) + : new MultiBufferSequence(null, f_aBufCached); + } + else if (ldtSent == 0) + { + // non-blocking cached request in always non-tied + return f_bufSeqCached; + } + // else; timed non-blocking request, not cached; fall through + } + + // upper 16 bits of id are a message counter, lower are a thread id + return MessageBusTest.getMessage(((int) (m_cMsgOut << 16)) | f_nId, /*fResp*/ false, ldtSent); + } + + // ----- data members ------------------------------------------- + + /** + * The associated Bus. + */ + protected final Bus f_bus; + + /** + * The cached message to use for untimed sends + */ + protected final BufferSequence f_bufSeqCached; + + /** + * The buffers in the cached sequence + */ + protected final ByteBuffer[] f_aBufCached; + + /** + * Non-zero if the bus has declared a global backlog. + */ + protected final AtomicInteger f_fBacklogGlobal; + + /** + * The transmitter ID. + */ + protected final int f_nId; + + /** + * The EndPoints to send to. + */ + protected final Set f_setReady; + + /** + * The target data rate. + */ + protected long f_cbs; + + /** + * The number of messages to send before flushing. + */ + protected final int f_nFlushOn; + + /** + * The maximum allowed transmit backlog, or -1 for no limit. + */ + protected final long f_cbTxMaxBacklog; + + /** + * The current backlog. + */ + protected final AtomicLong f_cbTxBacklog = new AtomicLong(); + + /** + * Backlog notifier. + */ + protected final Notifier f_notifierBacklog = new SingleWaiterCooperativeNotifier(); + + /** + * The number of pending responses. + */ + protected final AtomicInteger f_cPendingResponses = new AtomicInteger(); + + /** + * The number of bytes written. + */ + protected long m_cbOut; + + /** + * The number of sent messages. + */ + protected long m_cMsgOut; + + /** + * The number of milliseconds the transmitter was blocked waiting + * for peers. + */ + protected long m_cMillisBacklog; + + /** + * The start time of the current backlog, or 0 if none is active + */ + protected volatile long m_ldtBacklogStart; + + protected final long s_cbMsgAvg = s_cbMsgMin + (s_cbMsgMax - s_cbMsgMin) / 2; + } + + // ----- inner class: SkipStream ---------------------------------------- + + /** + * SkipStream in an OutputStream with the ability to skip a number of + * bytes. This provides a cheap way to write large multi-buffer messages + * without serialization overhead. + */ + public static class SkipStream + extends BufferSequenceOutputStream + { + // ----- constructors ------------------------------------------- + + /** + * Construct a SkipStream + * + * @param manager the manager to allocate the buffers from + */ + public SkipStream(BufferManager manager) + { + super(manager); + } + + /** + * Construct a SkipStream + * + * @param manager the manager to allocate the buffers from + * @param cb the estimated size of the stream + */ + public SkipStream(BufferManager manager, long cb) + { + super(manager, cb); + } + + // ----- SkipStream interface ----------------------------------- + + /** + * Skip over the specified number of output bytes. + * + * @param lcb the number of bytes to skip over. + * + * @throws IOException on an I/O error + */ + public void skip(long lcb) + throws IOException + { + while (lcb > 0) + { + ByteBuffer buf = ensureSpace(lcb); + int cb = (int) Math.min(lcb, buf.remaining()); + buf.position(buf.position() + cb); + lcb -= cb; + } + } + } + + + /** + * EchoBus is a simple MessageBus implementation which echos all messages back to itself. + */ + public static class EchoBus + implements MessageBus + { + public EchoBus(EndPoint pointSelf) + { + m_pointSelf = pointSelf; + } + + // ---- MessageBus interface ---------------------------------------- + + @Override + public void send(final EndPoint peer, BufferSequence bufseq, final Object receipt) + { + final Collector collector = getEventCollector(); + collector.add(new SimpleEvent(Event.Type.MESSAGE, peer, bufseq) + { + @Override + public Object dispose(boolean fTakeContent) + { + if (receipt != null) + { + // echo bus isn't done with source message until it has been disposed + collector.add(new SimpleEvent(Event.Type.RECEIPT, peer, receipt)); + } + return super.dispose(fTakeContent); + } + }); + } + + @Override + public EndPoint getLocalEndPoint() + { + return m_pointSelf; + } + + @Override + public void open() + { + getEventCollector().add(new SimpleEvent(Event.Type.OPEN, getLocalEndPoint())); + flush(); + } + + @Override + public void close() + { + getEventCollector().add(new SimpleEvent(Event.Type.CLOSE, getLocalEndPoint())); + flush(); + } + + @Override + public void connect(EndPoint peer) + { + getEventCollector().add(new SimpleEvent(Event.Type.CONNECT, peer)); + flush(); + } + + @Override + public void disconnect(EndPoint peer) + { + getEventCollector().add(new SimpleEvent(Event.Type.DISCONNECT, peer)); + flush(); + } + + @Override + public void release(EndPoint peer) + { + getEventCollector().add(new SimpleEvent(Event.Type.RELEASE, peer)); + flush(); + } + + @Override + public void flush() + { + getEventCollector().flush(); + } + + @Override + public void setEventCollector(Collector collector) + { + m_collector = collector; + } + + @Override + public Collector getEventCollector() + { + return m_collector; + } + + public static class EchoDriver + implements Driver + { + @Override + public void setDepot(Depot depot) + { + m_depot = depot; + } + + @Override + public Depot getDepot() + { + return m_depot; + } + + @Override + public EndPoint resolveEndPoint(String sName) + { + if (sName == null || !sName.equals("echo")) + { + return null; + } + + return new EndPoint() + { + @Override + public String getCanonicalName() + { + return "echo"; + } + + @Override + public String toString() + { + return getCanonicalName(); + } + }; + } + + @Override + public boolean isSupported(EndPoint point) + { + return point != null && point.getCanonicalName().equals("echo"); + } + + @Override + public Bus createBus(EndPoint pointLocal) + { + if (isSupported(pointLocal)) + { + return new EchoBus(pointLocal); + } + throw new IllegalArgumentException("unsupported"); + } + + // ----- data members --------------------------------------- + + protected Depot m_depot; + } + // ----- data members ----------------------------------------------- + + protected EndPoint m_pointSelf; + protected Collector m_collector; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Return a newly configured Histogram for measuring latencies. Samples added to this histogram must be + * measured in microseconds. + * + * @return the histogram + */ + public static Histogram makeLatencyHistogram() + { + return new ScaledHistogram(10*1000000) // 10s ceiling + .setFormatter((v) -> new Duration(v, Duration.Magnitude.MICRO).toString()); + } + + /** + * Construct a message. + * + * @param nId the requester id + * @param fResp true for response + * @param ldtSent the send time if a latency measurement is required, or 0 for none + * + * @return the message + * + * @throws IOException if an IO error occurs + */ + public static BufferSequence getMessage(int nId, boolean fResp, long ldtSent) + throws IOException + { + long cb = s_cbMsgMin; + long cbDelta = s_cbMsgMax - s_cbMsgMin; + if (cbDelta > 0) + { + cb += s_rand.nextLong() % cbDelta; + } + cb = Math.max(cb, MSG_HEADER_SIZE); + + SkipStream out = null; + do + { + try + { + out = new SkipStream(s_manager, cb); // TODO: optionally don't supply a hint + } + catch (OutOfMemoryError e) + { + System.err.println(Thread.currentThread().getName() + " handling error: " + e); + System.err.println(s_manager); + s_cErrors.incrementAndGet(); + Thread.yield(); + } + } + while (out == null); + + // create message + // format: + // 4B transmitter ID + // 1B msg type + // 8B timestamp (or zero) + // 8B payload size + // payload + long cbPayload = cb - MSG_HEADER_SIZE; + + out.writeInt(nId); + out.writeBoolean(fResp); + out.writeLong(ldtSent); + out.writeLong(cbPayload); + + int cbChunk = s_cbChunk; + switch (cbChunk) + { + default: + for (; cbPayload >= cbChunk; cbPayload -= cbChunk) + { + out.write(s_abChunk); + } + // fall through + + case 8: + for (; cbPayload >= 8; cbPayload -= 8) + { + out.writeLong(0); + } + // fall through + + case 4: + for (; cbPayload >= 4; cbPayload -= 4) + { + out.writeInt(0); + } + // fall through + + case 2: + for (; cbPayload >= 2; cbPayload -= 2) + { + out.writeShort(0); + } + // fall through + + case 1: + for (; cbPayload >= 1; cbPayload -= 1) + { + out.write(0); + } + break; + + case 0: + // skip remainder + out.skip(cbPayload); + break; + } + + return out.toBufferSequence(); + } + + + /** + * Configure a SocketBusDriver from the provided propeties. + * + * @param sPrefix the property prefix + * @param props the properties + * @param deps the Dependencies object to populate + * + * @return the configured Dependencies + * + * @throws Exception on an error + */ + public static SocketBusDriver.DefaultDependencies applyDriverProperties( + String sPrefix, Properties props, SocketBusDriver.DefaultDependencies deps) + throws Exception + { + deps.setBufferManager(s_manager); + + // SocketOptions + SocketSettings sockOpts = new SocketSettings(SocketBusDriver.DefaultDependencies.DEFAULT_OPTIONS); + String sName = sPrefix + "socket.rxbuffer"; + if (props.containsKey(sName)) + { + sockOpts.set(SocketOptions.SO_RCVBUF, (int) new MemorySize(props.getProperty(sName)).getByteCount()); + } + sName = sPrefix + "socket.txbuffer"; + if (props.containsKey(sName)) + { + sockOpts.set(SocketOptions.SO_SNDBUF, (int) new MemorySize(props.getProperty(sName)).getByteCount()); + } + sName = sPrefix + "socket.nodelay"; + if (props.containsKey(sName)) + { + sockOpts.set(SocketOptions.TCP_NODELAY, Boolean.parseBoolean(props.getProperty(sName))); + } + sName = sPrefix + "socket.linger"; + if (props.containsKey(sName)) + { + sockOpts.set(SocketOptions.SO_LINGER, Boolean.parseBoolean(props.getProperty(sName))); + } + deps.setSocketOptions(sockOpts); + + return deps; + } + + + /** + * Parse the supplied properties object into a SimpleDepot.Dependencies + * object. + *

+ * This allows for advanced bus properties to be configured. + * + * @param sPrefix the property prefix + * @param props the properties + * + * @return the Dependencies object + * + * @throws Exception on error + */ + public static SimpleDepot.Dependencies parseDependencies(String sPrefix, final Properties props) + throws Exception + { + SimpleDepot.DefaultDependencies depsDepot = new SimpleDepot.DefaultDependencies(); + + String sSslKeystore = sPrefix + "ssl.keystore"; + if (props.containsKey(sSslKeystore)) + { + // SSL options + String sKeystore = props.getProperty(sSslKeystore); + String sPassword = props.getProperty(sPrefix + "ssl.password", "password"); + SSLContext ctx = SSLContext.getInstance("TLS"); + KeyManagerFactory keymanager = KeyManagerFactory.getInstance("SunX509"); + TrustManagerFactory trustmanager = TrustManagerFactory.getInstance("SunX509"); + KeyStore keystore = KeyStore.getInstance("JKS"); + char[] achPassword = sPassword.toCharArray(); + + keystore.load(new URL("file:" + sKeystore).openStream(), achPassword); + keymanager.init(keystore, achPassword); + trustmanager.init(keystore); + + ctx.init(keymanager.getKeyManagers(), trustmanager.getTrustManagers(), new SecureRandom()); + + depsDepot.setSSLSettings(new SSLSettings().setSSLContext(ctx) + .setClientAuthenticationRequired(Boolean.parseBoolean( + props.getProperty(sPrefix + "ssl.clientauth", "false")))); + } + + Map mapDriver = new HashMap<>(depsDepot.getDrivers()); + + for (Map.Entry entry : mapDriver.entrySet()) + { + Driver driver = entry.getValue(); + if (driver instanceof SocketBusDriver) + { + SocketBusDriver sbDriver = (SocketBusDriver) driver; + entry.setValue(new SocketBusDriver(applyDriverProperties( + sPrefix, props, + new SocketBusDriver.DefaultDependencies(sbDriver.getDependencies())))); + } + } + + mapDriver.put("EchoBus", new EchoBus.EchoDriver()); + + depsDepot.setDrivers(mapDriver); + + return depsDepot; + } + + /** + * Parse command line argments into key value pairs. + * + * @param asArg the command line arguments + * + * @return the key value map + */ + public static Map parseArgs(String[] asArg) + { + Map mapArgs = new HashMap(); + + for (int i = 0, c = asArg.length; i < c; ++i) + { + String arg = asArg[i]; + if (arg.startsWith("-")) + { + String sKey = arg; + String sVal = null; + for (; i + 1 < c; ++i) + { + arg = asArg[i + 1]; + if (arg.startsWith("-")) + { + try + { + Integer.valueOf(arg); + } + catch (NumberFormatException e) + { + break; // new key + } + } + + sVal = sVal == null ? arg : sVal + " " + arg; + } + mapArgs.put(sKey, sVal == null ? "true" : sVal); + } + else + { + throw new IllegalArgumentException("unepxected paramter " + arg); + } + } + + return mapArgs; + } + + /** + * Parse a string containing a space seperated list of EndPoint names. + *

+ * This method supports range based endpoints for EndPoints which end in + * :port. The range can be specified using ..port, + * allowing the specification of a range such as: + * http://localhost:80..89 to result in ten addresses. + * + * @param depot the Depot to use in resolving the names + * @param sEps the EndPoint name string + * + * @return a List resolved EndPoints + */ + public static List parseEndPoints(Depot depot, String sEps) + { + List listEp = new ArrayList(); + for (StringTokenizer tok = new StringTokenizer(sEps); tok.hasMoreElements(); ) + { + String sTok = tok.nextToken(); + int of = sTok.indexOf(".."); + + if (of == -1) + { + listEp.add(depot.resolveEndPoint(sTok)); + } + else // range based endpoint, assumes name:port formatting + { + String sName = sTok.substring(0, of); + int ofPort = Math.max(sName.lastIndexOf('.'), sName.lastIndexOf(':')); + String sPrefix = sName.substring(0, ofPort + 1); + int nPort = Integer.parseInt(sName.substring(ofPort + 1)); + int nPortEnd = Integer.parseInt(sTok.substring(of + 2)); + + if (nPort < nPortEnd) + { + for (; nPort <= nPortEnd; ++nPort) + { + listEp.add(depot.resolveEndPoint(sPrefix + nPort)); + } + } + else + { + for (; nPort >= nPortEnd; --nPort) + { + listEp.add(depot.resolveEndPoint(sPrefix + nPort)); + } + } + } + } + return listEp; + } + + public static void printHelp(PrintStream out) + { + out.println("MessageBusTest parameters:"); + out.println("\t-bind list of one or more local EndPoints to create"); + out.println("\t-peer list of one or more remote EndPoints to send to"); + out.println("\t-rxThreads number of receive threads per bound EndPoint (negative for reentrant)"); + out.println("\t-txThreads number of transmit threads per bound EndPoint"); + out.println("\t-msgSize range of message sizes to send, expressed as min[..max]"); + out.println("\t-chunkSize defines the number of bytes to process as a single unit, i.e. 1 for byte, 8 for long, 0 to disable"); + out.println("\t-cached re-use message objects where possible, reducing buffer manager overhead"); + out.println("\t-txRate target outbound data rate"); + out.println("\t-txMaxBacklog the maximum backlog the test should produce per tx thread"); + out.println("\t-rxRate target inbound data rate"); + out.println("\t-flushFreq number of messages to send before flushing, or 0 for auto"); + out.println("\t-latencyFreq number of messages to send before sampling latency"); + out.println("\t-noReceipts specified if receipts should not be used, relies on GC to reclaim messages"); + out.println("\t-manager buffer manager to utilize (net, direct, heap)"); + out.println("\t-polite if specified this instance will not start sending until connected to"); + out.println("\t-depotFactory the fully qualified class name of the Factory to use to obtain the Depot"); + out.println("\t-reportInterval the report interval"); + out.println("\t-polite if specified this instance will not start sending until connected to"); + out.println("\t-block if specified a transmit thread will block while awaiting a response, optional value of spin duration"); + out.println("\t-relay if specified then the process will relay any received messages to one of its peers"); + out.println("\t-ignoreFlowControl if flow control events are to be ignored, use -txMaxBacklog to prevent OutOfMemory"); + out.println("\t-poll is specified PollingEventCollector will be utilized"); + out.println("\t-prompt if specified the user will be prompted before each send"); + out.println("\t-tabular if specified the output will be in tabular format"); + out.println("\t-warmup time duration or message count which will be discarded for warmup"); + out.println("\t-single if specified an outgoing connection will emit just one message, then reconnect"); + out.println("\t-verbose to enable verbose debugging output"); + } + + /** + * Run the MessageBusTest application. + * + * @param asArg the program arguments + * + * @throws IOException if an IO error occurs + */ + public static void main(String[] asArg) + throws Exception + { + Map mapArgs = parseArgs(asArg); + + String sEpLocal = mapArgs.remove("-bind"); + String sEpPeer = mapArgs.remove("-peer"); + String sRxThreads = mapArgs.remove("-rxThreads"); + String sTxThreads = mapArgs.remove("-txThreads"); + String sMsgSize = mapArgs.remove("-msgSize"); + String sChunkSize = mapArgs.remove("-chunkSize"); + String sCached = mapArgs.remove("-cached"); + String sFlushFreq = mapArgs.remove("-flushFreq"); + String sTxRate = mapArgs.remove("-txRate"); + String sTxMaxBacklog = mapArgs.remove("-txMaxBacklog"); + String sRxRate = mapArgs.remove("-rxRate"); + String sLatFreq = mapArgs.remove("-latencyFreq"); + String sNoReceipt = mapArgs.remove("-noReceipts"); + String sManager = mapArgs.remove("-manager"); + String sVerbose = mapArgs.remove("-verbose"); + String sPolite = mapArgs.remove("-polite"); + String sBlock = mapArgs.remove("-block"); + String sRelay = mapArgs.remove("-relay"); + String sIgnoreFC = mapArgs.remove("-ignoreFlowControl"); + String sTabular = mapArgs.remove("-tabular"); + String sFactoryDepot = mapArgs.remove("-depotFactory"); + String sReportInterval = mapArgs.remove("-reportInterval"); + String sPollingColl = mapArgs.remove("-poll"); + String sPrompt = mapArgs.remove("-prompt"); + String sWarmup = mapArgs.remove("-warmup"); + String sSingle = mapArgs.remove("-single"); + + if (sTxRate != null && Character.isDigit(sTxRate.charAt(sTxRate.length() - 1))) + { + sTxRate += "MBps"; + } + if (sRxRate != null && Character.isDigit(sRxRate.charAt(sRxRate.length() - 1))) + { + sRxRate += "MBps"; + } + + if (sReportInterval == null) + { + sReportInterval = "5s"; + } + else if (Character.isDigit(sReportInterval.charAt(sReportInterval.length() - 1))) + { + sReportInterval += "s"; + } + + s_fVerbose = sVerbose != null; + s_fFlowControl = sIgnoreFC == null; + s_fCached = sCached != null; + s_fPollingCollector = sPollingColl != null; + s_fPrompt = sPrompt != null; + s_fSingleUseConnection = sSingle != null; + + long cReportMillis = new Duration(sReportInterval).as(Duration.Magnitude.MILLI); + boolean fPolite = sPolite != null && sPolite.equals("true"); + boolean fBlock = sBlock != null && !sBlock.equals("false"); + boolean fRelay = sRelay != null && !sRelay.equals("false"); + boolean fTabular = sTabular != null && sTabular.equals("true"); + int cRxThreads = Math.abs(sRxThreads == null ? 1 : Integer.parseInt(sRxThreads)); + int cTxThreads = Math.abs(sTxThreads == null ? fRelay ? 0 : 1 : Integer.parseInt(sTxThreads)); + boolean fReentrant = sRxThreads != null && sRxThreads.startsWith("-"); + long cbsOut = sTxRate == null ? -1 : new Bandwidth(sTxRate).as(Rate.BYTES); + long cbsIn = sRxRate == null ? -1 : new Bandwidth(sRxRate).as(Rate.BYTES); + long cbMaxBacklog = sTxMaxBacklog == null ? -1 : new MemorySize(sTxMaxBacklog).getByteCount(); + long cMsgWarmup = 0; + long cMillisWarmup = 0; + + + if (sWarmup != null) + { + try + { + cMsgWarmup = Integer.parseInt(sWarmup); + } + catch (Exception e) + { + cMillisWarmup = new Duration(sWarmup).as(Duration.Magnitude.MILLI); + } + } + + if (fBlock && !(sBlock.equals("true"))) + { + AWAIT_SPIN_NANOS = new Duration(sBlock).getNanos(); + } + s_fBlock = fBlock; + + if (fRelay) + { + if (cTxThreads != 0) + { + System.err.println("-relay and -txThreads cannot both be specified"); + System.exit(1); + } + } + s_fRelay = fRelay; + + // select default thread counts + if (sRxThreads == null || cRxThreads == 0) + { + // no rx threads specified, default to reentrant mode on all available cores + // note that because we are reentrant the actual number of threads is up to the bus + // impl; this is only selecting how many can execute concurrently + cRxThreads = Platform.getPlatform().getFairShareProcessors() * 17; + fReentrant = true; + } + + if (s_fPollingCollector && cRxThreads != 1) + { + System.err.println("\nWARNING: polling collector generally requires -rxThreads 1\n"); + } + + if (fReentrant && cbsIn != -1) + { + if (sRxThreads == null) + { + // user asked for rx throttling and didn't specify rxThreads, thus didn't ask for reentrancy; disable + fReentrant = false; + cRxThreads = 1; + } + else + { + System.err.println("inbound throttling (-rxRate) not available with reentrant processing (-rxThreads <= 0)"); + System.exit(1); + } + } + + if (sLatFreq == null) + { + s_nLatencyFreq = 100; + } + else + { + s_nLatencyFreq = Integer.parseInt(sLatFreq); + } + + s_fReceipts = sNoReceipt == null || !sNoReceipt.equals("true"); + if (!s_fReceipts && cbMaxBacklog != -1) + { + throw new IllegalArgumentException("-txMaxBacklog requires receipts"); + } + + if (sManager == null || sManager.equals("net")) + { + s_manager = BufferManagers.getNetworkDirectManager(); + } + else if (sManager.equals("direct")) + { + s_manager = BufferManagers.getDirectManager(); + } + else if (sManager.equals("heap")) + { + s_manager = BufferManagers.getHeapManager(); + } + else + { + throw new IllegalArgumentException("unknown heap manager: " + sManager); + } + + long cbMin, cbMax, cbAvg; + if (sMsgSize == null) + { + cbMin = cbMax = cbAvg = 4096; + } + else + { + int ofMsgDelim = sMsgSize.indexOf(".."); + if (ofMsgDelim == -1) + { + // fixed message size + cbMin = cbMax = cbAvg = new MemorySize(sMsgSize).getByteCount(); + } + else if (s_fCached) + { + System.err.println("-cached does not support variable sized messaging"); + throw new IllegalArgumentException(); + } + else + { + // range based + cbMin = new MemorySize(sMsgSize.substring(0, ofMsgDelim)).getByteCount(); + cbMax = new MemorySize(sMsgSize.substring(ofMsgDelim + 2)).getByteCount(); + + if (cbMax < cbMin) + { + // wrong order, swap them + long n = cbMax; + cbMax = cbMin; + cbMin = n; + } + + cbAvg = cbMax - ((cbMax - cbMin) / 2); + } + } + + if (cbMin < MSG_HEADER_SIZE) + { + System.out.println("increasing minimum message size to " + MSG_HEADER_SIZE + " bytes to satisfy test requirements\n"); + cbMin = MSG_HEADER_SIZE; + } + if (cbMax < MSG_HEADER_SIZE) + { + cbMax = MSG_HEADER_SIZE; + } + if (cbAvg < MSG_HEADER_SIZE) + { + cbAvg = MSG_HEADER_SIZE; + } + + s_cbMsgMin = cbMin; + s_cbMsgMax = cbMax; + + int cbChunk = 0; + if (sChunkSize != null) + { + cbChunk = (int) new MemorySize(sChunkSize).getByteCount(); + } + s_cbChunk = (int) Math.min(cbMin, cbChunk); + s_abChunk = new byte[cbChunk]; + + int nFlushOn = sFlushFreq == null ? 0 : Integer.parseInt(sFlushFreq); + + if (!mapArgs.isEmpty()) + { + System.err.println("unknown parameter " + mapArgs.keySet().iterator().next()); + System.err.println(); + printHelp(System.err); + System.exit(1); + } + + // create local busses + Depot depot; + if (sFactoryDepot == null) + { + depot = new SimpleDepot(parseDependencies("depot.", System.getProperties())); + } + else + { + depot = ((Factory) Class.forName(sFactoryDepot).newInstance()).create(); + } + + // resolve peer EndPoints + EndPoint[] aPeer = new EndPoint[0]; + if (sEpPeer != null) + { + aPeer = parseEndPoints(depot, sEpPeer).toArray(aPeer); + } + + List listBus = new ArrayList(); + + if (sEpLocal == null) + { + if (aPeer.length == 0) + { + listBus.add(depot.createMessageBus(null)); + } + else + { + // attempt to compute a local endpoint from remote endpoint + String sPeer = aPeer[0].getCanonicalName(); + if (sPeer.contains(":")) + { + listBus.add(depot.createMessageBus( + depot.resolveEndPoint(sPeer.substring(0, sPeer.indexOf(':')) + "://0.0.0.0:0"))); + } + } + } + else + { + for (EndPoint epBind : parseEndPoints(depot, sEpLocal)) + { + try + { + listBus.add(depot.createMessageBus(epBind)); + } + catch (Exception e) + { + try + { + // "hidden" MemoryBus test mode + listBus.add(depot.createMemoryBus(epBind)); + } + catch (Exception e2) + { + e2.printStackTrace(); + throw e; + } + } + } + } + + // bring each bus up + int cBus = listBus.size(); + Set setDemuxer = new HashSet(); + EventProcessor[] aProcessor = new EventProcessor[cBus * cRxThreads]; + Transmitter[] aTransmitter = new Transmitter[cBus * cTxThreads]; + long cbsInProc = cbsIn == -1 ? cbsIn : Math.max(1, cbsIn / aProcessor.length); + int iProc = 0; + int iTrans = 0; + + for (Bus bus : listBus) + { + AtomicInteger fBacklogLocal = new AtomicInteger(); + Set setReady = Collections.newSetFromMap(new ConcurrentHashMap()); + EndPoint boundEp = bus.getLocalEndPoint(); + EventProcessor[] aProc = new EventProcessor[cRxThreads]; + Transmitter[] aTrans = new Transmitter[cTxThreads]; + + // select peers to operate against + Set setPeer = new HashSet(); + for (EndPoint peer : aPeer) + { + if (!peer.equals(boundEp)) + { + setPeer.add(peer); + } + } + setPeer = Collections.unmodifiableSet(setPeer); + if (setPeer.isEmpty()) + { + Transmitter[] aTransNew = new Transmitter[aTransmitter.length - cTxThreads]; + System.arraycopy(aTransmitter, 0, aTransNew, 0, aTransNew.length); + aTransmitter = aTransNew; + aTrans = null; + } + else + { + // construct transmitters + for (int i = 0, c = aTrans.length; i < c; ++i) + { + aTransmitter[iTrans++] = aTrans[i] = new Transmitter(bus, i, setReady, nFlushOn, fBacklogLocal, cbMaxBacklog); + } + } + + // construct processors + for (int i = 0; i < cRxThreads; ++i) + { + aProcessor[iProc++] = aProc[i] = new EventProcessor(bus, setPeer, setReady, aTrans, cbsInProc, nFlushOn, fBacklogLocal); + } + + if (s_fPollingCollector) + { + bus.setEventCollector(new QueueingEventCollector()); + } + else + { + DemultiplexingCollector collector = new DemultiplexingCollector(bus, aProc); + setDemuxer.add(collector); + bus.setEventCollector(collector); + } + bus.open(); + + if (!fPolite) + { + // TODO: wait for open events + // establish connections + for (EndPoint peer : setPeer) + { + bus.connect(peer); + } + } + } + + // start processors + if (!fReentrant) + { + for (EventProcessor proc : aProcessor) + { + proc.start(); + } + } + + // start transmitters; they will wait for connections before sending + long cbsOutTrans = cbsOut == -1 ? cbsOut : Math.max(1, cbsOut / aTransmitter.length); + for (Transmitter trans : aTransmitter) + { + trans.setTransmitRate(cbsOutTrans); + trans.start(); + } + + // stats logging + + // add a blank line printout during shutdown + Runtime.getRuntime().addShutdownHook(new Thread() + { + public void run() + { + System.out.println(); + } + }); + + class Stats + { + public Stats(long ldt) + { + this.ldt = ldt; + } + + long ldt; + long cMsgIn; + long cMsgOut; + long cbIn; + long cbOut; + long cbInCollected; + long cReceipts; + long cReceiptSamples; + long cReceiptNanos; + long cResponses; + long cResponseNanos; + long cResponseNanosMin = Long.MAX_VALUE; + long cResponseNanosMax = -1; + long cBacklogLocal; + long cMillisBacklogLocal; + long cBacklogRemote; + long cMillisBacklogRemote; + long cbInPendingLife; + long cbOutPendingLife; + long cErrors; + long cConnections; + Histogram histLatency = makeLatencyHistogram(); + } + + if (fTabular) + { + System.out.println( + "msg/s in\t" + + "bytes/s in\t" + + "msg/s out\t" + + "bytes/s out\t" + + "avg receipt latency nanos\t" + + "min response latency nanos\t" + + "avg response latency nanos\t" + + "effective latency nanos\t" + + "max response latency nanos\t" + + "in backlog percentage\t" + + "in backlog events\t" + + "in backlog bytes\t" + + "out backlog percentage\t" + + "out backlog events\t" + + "out backlog bytes\t" + + "connections\t" + + "errors"); + } + + long ldtWarmStart = 0; + Stats statsWarm = null; // base stats after warmup has completed + Stats statsPrev = null; // stats from prev cycle + Stats stats = null; // stats from this cycle + + for (int iReport = 0; ; ++iReport) + { + Blocking.sleep(statsWarm == null ? 10 : cReportMillis); + + stats = new Stats(System.currentTimeMillis()); + + for (EventProcessor proc : aProcessor) + { + stats.cReceipts += proc.getReceiptsIn(); + stats.cReceiptSamples += proc.getReceiptSamples(); + stats.cReceiptNanos += proc.getReceiptNanos(); + stats.cResponses += proc.getResponsesIn(); + stats.cResponseNanos += proc.getResponseNanos(); + stats.histLatency.addSamples(proc.getResponseLatencyHistogram()); + stats.cResponseNanosMax = Math.max(proc.m_cResponseNanosMax, stats.cResponseNanosMax); + proc.m_cResponseNanosMax = -1; + stats.cResponseNanosMin = Math.min(proc.m_cResponseNanosMin, stats.cResponseNanosMin); + proc.m_cResponseNanosMin = Long.MAX_VALUE; + stats.cMsgIn += proc.getMessagesIn(); + stats.cMsgOut += proc.getMessagesOut(); + stats.cbIn += proc.getBytesIn(); + stats.cbOut += proc.getBytesOut(); + stats.cBacklogLocal += proc.getLocalBacklogEvents(); + stats.cMillisBacklogLocal += proc.getLocalBacklogMillis(); + stats.cBacklogRemote += proc.getRemoteBacklogEvents(); + stats.cConnections += proc.getConnectionCount(); + } + + for (Transmitter trans : aTransmitter) + { + stats.cMsgOut += trans.getMessagesOut(); + stats.cbOut += trans.getBytesOut(); + stats.cMillisBacklogRemote += trans.getRemoteBacklogMillis(); + } + + if (setDemuxer != null) + { + for (DemultiplexingCollector collector : setDemuxer) + { + stats.cbInCollected += collector.getReceivedBytes(); + } + } + + stats.cErrors = s_cErrors.get(); + + if (statsWarm == null) + { + if (stats.cConnections > 0 && Math.max(stats.cMsgIn, stats.cMsgOut) > cMsgWarmup) + { + if (ldtWarmStart == 0) + { + ldtWarmStart = stats.ldt; + } + + if (stats.ldt - ldtWarmStart >= cMillisWarmup) + { + // we've completed warmup, note that the duration between ldtWarmStart and stastWarm.ldt is + // questionable, but also never used + statsPrev = statsWarm = stats; + } + + iReport = 0; + } + continue; + } + else if (stats.cConnections == 0) + { + statsWarm = null; + ldtWarmStart = 0; + continue; + } + + for (Stats statsComp : new Stats[]{statsPrev, statsWarm}) + { + long cDeltaMillis = stats.ldt - statsComp.ldt; + long cMsgInDelta = stats.cMsgIn - statsComp.cMsgIn; + long cMsgOutDelta = stats.cMsgOut - statsComp.cMsgOut; + long cbInDelta = stats.cbIn - statsComp.cbIn; + long cbOutDelta = stats.cbOut - statsComp.cbOut; + long cReceiptDelta = stats.cReceiptSamples - statsComp.cReceiptSamples; + long cReceiptNanosDelta = stats.cReceiptNanos - statsComp.cReceiptNanos; + long cResponseDelta = stats.cResponses - statsComp.cResponses; + long cResponseNanosDelta = stats.cResponseNanos - statsComp.cResponseNanos; + long cBacklogLocalDelta = stats.cBacklogLocal - statsComp.cBacklogLocal; + long cMillisBacklogLocalDelta = stats.cMillisBacklogLocal - statsComp.cMillisBacklogLocal; + long cBacklogRemoteDelta = stats.cBacklogRemote - statsComp.cBacklogRemote; + long cMillisBacklogRemoteDelta = stats.cMillisBacklogRemote - statsComp.cMillisBacklogRemote; + long cErrorsDelta = stats.cErrors - statsComp.cErrors; + + long cbOutPending = (stats.cMsgOut - stats.cReceipts) * cbAvg; + long cbInPending = s_fPollingCollector ? -1 : stats.cbInCollected - stats.cbIn; + + double dflSeconds = cDeltaMillis / 1000.0; + + long MSGsIn = Math.round(cMsgInDelta / dflSeconds); + long MSGsOut = Math.round(cMsgOutDelta / dflSeconds); + long BLsLocal = Math.round(cBacklogLocalDelta / dflSeconds); + long BLsRemote = Math.round(cBacklogRemoteDelta / dflSeconds); + + long lPctBacklogLocal = (100 * cMillisBacklogLocalDelta) / (cDeltaMillis * cBus); + long lPctBacklogRemote = aTransmitter.length == 0 + ? -1 : (100 * cMillisBacklogRemoteDelta) / (cDeltaMillis * aTransmitter.length); + long cResponseNanosDeltaEff = (long) (cResponseNanosDelta * (1.0 / (1.0 - lPctBacklogRemote / 100.0))); + + if (statsComp == statsWarm) + { + // for lifetime we compute the average cost + stats.cbOutPendingLife = statsPrev.cbOutPendingLife + cbOutPending; + cbOutPending = stats.cbOutPendingLife / (iReport + 1); + + stats.cbInPendingLife = statsPrev.cbInPendingLife + cbInPending; + cbInPending = stats.cbInPendingLife / (iReport + 1); + + // carry min/max across iterations + stats.cConnections = Math.max(stats.cConnections, statsPrev.cConnections); + stats.cResponseNanosMin = Math.min(stats.cResponseNanosMin, statsPrev.cResponseNanosMin); + stats.cResponseNanosMax = Math.max(stats.cResponseNanosMax, statsPrev.cResponseNanosMax); + } + + if (fTabular) + { + System.out.println("" + + MSGsIn + '\t' + + cbInDelta / dflSeconds + '\t' + + MSGsOut + '\t' + + cbOutDelta / dflSeconds + '\t' + + (cReceiptDelta == 0 ? -1 : cReceiptNanosDelta / cReceiptDelta) + '\t' + + (cResponseDelta == 0 ? -1 : stats.cResponseNanosMin) + '\t' + + (cResponseDelta == 0 ? -1 : cResponseNanosDelta / cResponseDelta) + '\t' + + (cResponseDelta == 0 ? -1 : cResponseNanosDeltaEff / cResponseDelta) + '\t' + + (cResponseDelta == 0 ? -1 : stats.cResponseNanosMax) + '\t' + + lPctBacklogLocal + '\t' + + BLsLocal + '\t' + + cbInPending + '\t' + + lPctBacklogRemote + '\t' + + BLsRemote + '\t' + + cbOutPending + '\t' + + stats.cConnections + '\t' + + cErrorsDelta); + break; // skip over lifetime step + } + else + { + System.out.println((statsComp == statsPrev ? "now: " : "life: ") + + "throughput(" + + "out " + MSGsOut + "msg/s " + new Bandwidth(8 * cbOutDelta / dflSeconds, Rate.BITS) + + ", in " + MSGsIn + "msg/s " + new Bandwidth(8 * cbInDelta / dflSeconds, Rate.BITS) + "), " + + "latency(" + + "response" + (cResponseDelta == 0 ? " n/a" + : "(avg " + new Duration(cResponseNanosDelta / cResponseDelta) + ", effective " + new Duration(cResponseNanosDeltaEff / cResponseDelta) + ", min " + new Duration(stats.cResponseNanosMin) + ", max " + new Duration(stats.cResponseNanosMax) + ")") + + ", receipt " + (cReceiptDelta == 0 ? "n/a" : new Duration(cReceiptNanosDelta / cReceiptDelta)) + "), " + + "backlog(" + + "out " + (lPctBacklogRemote < 0 ? "n/a " : lPctBacklogRemote + "% ") + BLsRemote + "/s " + (s_fReceipts ? new MemorySize(cbOutPending) : "n/a") + + ", in " + lPctBacklogLocal + "% " + BLsLocal + "/s " + (cbInPending < 0 ? "n/a" : new MemorySize(cbInPending)) + "), " + + "connections " + stats.cConnections + + ", errors " + cErrorsDelta); + + if (s_fVerbose && stats.histLatency.getSampleCount() > 0) + { + System.out.println("\tlatency detail: " + stats.histLatency.compare(statsComp.histLatency)); + } + } + } + + if (s_fVerbose) + { + for (Bus bus : listBus) + { + System.out.println("bus: " + bus); + } + System.out.println("mgr: " + s_manager); + RuntimeMXBean beanRuntime = ManagementFactory.getRuntimeMXBean(); + System.out.print("jvm: " + beanRuntime.getSpecVersion() + " " + beanRuntime.getVmVersion() + " "); + for (String s : beanRuntime.getInputArguments()) + { + System.out.print(s + " "); + } + System.out.print("\ncmd: "); + for (String s : asArg) + { + System.out.print(s + " "); + } + System.out.println(); + System.out.println("time: " + new Date() + "/" + new Duration(System.currentTimeMillis() - statsWarm.ldt, Duration.Magnitude.MILLI)); + } + + if (!fTabular) + { + System.out.println(); + } + + statsPrev = stats; + } + } + + // ---- constants ------------------------------------------------------- + + /** + * The message header size used in the test. + */ + public static final int MSG_HEADER_SIZE = 21; + + + // ---- static data members --------------------------------------------- + + /** + * The BufferManager to use in the test. + */ + protected static BufferManager s_manager; + + /** + * Flag for verbose logging. + */ + protected static boolean s_fVerbose; + + /** + * True if user should be prompted before sending. + */ + protected static boolean s_fPrompt; + + /** + * Flag for using cached messages + */ + protected static boolean s_fCached; + + /** + * Flag for using polling collector + */ + protected static boolean s_fPollingCollector; + + /** + * The frequency (in messages), at which latency will be sampled. + */ + public static int s_nLatencyFreq = 100; + + /** + * True if receipts should be used. + */ + public static boolean s_fReceipts = true; + + /** + * The minimum message size. + */ + public static long s_cbMsgMin; + + /** + * The maximum message size + */ + public static long s_cbMsgMax; + + /** + * The unit size to process at. + */ + public static int s_cbChunk; + + /** + * To be used for chunked writes. + */ + public static byte[] s_abChunk; + + /** + * How long to spin waiting for results + */ + protected static long AWAIT_SPIN_NANOS; + + /** + * Switch govering if flow control events should be respected. + */ + public static boolean s_fFlowControl = true; + + /** + * True if this instance is to act as a message relay + */ + public static boolean s_fRelay = false; + + /** + * True if this instance is in blocking mode. + */ + public static boolean s_fBlock = false; + + /** + * True to use each connection for just one message. + */ + public static boolean s_fSingleUseConnection; + + /** + * Count of the number of errors encountered during the test. + */ + public static AtomicLong s_cErrors = new AtomicLong(); + + /** + * Shared randomizer. + */ + public static Random s_rand = new Random(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/PollingEventCollector.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/PollingEventCollector.java new file mode 100644 index 0000000000000..2c3a1a7ec7422 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/PollingEventCollector.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus.util; + +import com.oracle.coherence.common.base.Collector; +import com.oracle.coherence.common.net.exabus.Event; + +import java.util.concurrent.TimeUnit; + +/** + * A PollingEventCollector is a collector for applications which wish to have a greater level of control + * as to when and where events are generated. Bus implementations may be optimized for for this type of + * collector, and {@link #bind(PollingEventCollector.BusProcessor) bind} + * to it. In such a case it will be up to the collector to call {@link BusProcessor#poll} in order to + * run the bus and ensure it can process work and emit events. The collector should still be ready + * to have events {@link Collector#add(Object) added} to it at any point, even if the bus has bound itself + * to the collector. + * + * @author mf 02.18.2013 + */ +public interface PollingEventCollector + extends Collector + { + // ----- inner class: BusProcessor ------------------------------------- + + /** + * The BusProcessor can be provided to a bus in {@link #bind} to allow + * the bus to be more tightly coupled to the application thread. + */ + public static interface BusProcessor + { + /** + * Poll for events. + *

+ * The method may execute for up to the specified timeout and may emit + * multiple events within that time. To terminate eagerly return from + * poll the collector may invoke {@link #cancel}. + *

+ * + * @param timeout the timeout + * @param unit the time unit for timeout + * + * @throws InterruptedException if the thread is interrupted + */ + public void poll(long timeout, TimeUnit unit) + throws InterruptedException; + + /** + * Cancel the current or next {@link #poll execution} as soon as possible. + */ + public void cancel(); + } + + /** + * Called by a supporting Bus implementation to bind itself to the collector, and + * provide the collector with a {@link BusProcessor callback} with which to control + * the bus execution + * + * @param processor the bus processor + */ + public void bind(BusProcessor processor); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/QueueingEventCollector.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/QueueingEventCollector.java new file mode 100644 index 0000000000000..23808de13c3d2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/QueueingEventCollector.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus.util; + +import com.oracle.coherence.common.base.Pollable; +import com.oracle.coherence.common.net.exabus.Event; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + + +/** + * A Queue based PollingEventCollector implementation. + *

+ * This implementation will internally queue all {@link #add added} events, and return them only when they are + * {@link #poll polled} for. + *

+ * + * @author mf 02.18.2013 + */ +public class QueueingEventCollector + extends AbstractPollingEventCollector + implements Pollable + { + // ----- QueueingEventCollector interface -------------------------------- + + @Override + public Event poll(long timeout, TimeUnit unit) + throws InterruptedException + { + BusProcessor processor = m_busProcessor; + if (processor == null) + { + return f_queue.poll(timeout, unit); + } + else + { + Event event = f_queue.poll(); + if (event == null) + { + processor.poll(timeout, unit); + event = f_queue.poll(); + } + return event; + } + } + + @Override + public Event poll() + { + Event event = f_queue.poll(); + BusProcessor processor = m_busProcessor; + if (processor == null) + { + return event; + } + else if (event == null) + { + try + { + processor.poll(0, TimeUnit.MILLISECONDS); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + event = f_queue.poll(); + } + + return event; + } + + // ----- Collector interface -------------------------------------------- + + @Override + public void add(Event event) + { + f_queue.add(event); + } + + /** + * In the case of a {@link #bind bound} collector, the BusProcessor will be + * {@link BusProcessor#cancel() canceled} as part + * of this operation. + *

+ * {@inheritDoc} + */ + @Override + public void flush() + { + BusProcessor processor = m_busProcessor; + if (processor != null) + { + // in case the bus also emits async events (outside of while polling is blocked), it would + // be best to terminate the poll to allow the events to be processed asap + processor.cancel(); + } + } + + // ----- data members --------------------------------------------------- + + /** + * Queue of polled events, for non-PollBus based implementations + */ + protected final BlockingQueue f_queue = new LinkedBlockingQueue(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/SimpleDepot.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/SimpleDepot.java new file mode 100644 index 0000000000000..192741ba8a107 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/SimpleDepot.java @@ -0,0 +1,768 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus.util; + + +import com.oracle.coherence.common.net.SdpSocketProvider; +import com.oracle.coherence.common.net.TcpSocketProvider; +import com.oracle.coherence.common.net.exabus.Bus; +import com.oracle.coherence.common.net.exabus.Depot; +import com.oracle.coherence.common.net.exabus.EndPoint; +import com.oracle.coherence.common.net.exabus.Event; +import com.oracle.coherence.common.net.exabus.MessageBus; +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.base.Collector; +import com.oracle.coherence.common.base.Factory; +import com.oracle.coherence.common.internal.Platform; +import com.oracle.coherence.common.net.exabus.spi.Driver; +import com.oracle.coherence.common.net.exabus.MemoryBus; +import com.oracle.coherence.common.net.SSLSettings; +import com.oracle.coherence.common.net.SSLSocketProvider; +import com.oracle.coherence.common.internal.net.socketbus.SocketBusDriver; + +import java.io.Closeable; +import java.util.Collections; +import java.util.Map; +import java.util.HashMap; +import java.util.logging.Logger; + + +/** + * SimpleDepot is a Depot of well-known bus drivers. + *

+ * While users may create their own depot instances, it is generally preferable to use the shared + * {@link #getInstance() singleton} instances. + *

+ * + * @author mf 2010.12.03 + */ +public class SimpleDepot + implements Depot + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the Depot with default dependencies. + */ + public SimpleDepot() + { + this (null); + } + + /** + * Construct the Depot from the specified dependencies. + * + * @param deps the depot's dependencies + */ + public SimpleDepot(Dependencies deps) + { + m_dependencies = deps = copyDependencies(deps).validate(); + + for (Driver driver : deps.getDrivers().values()) + { + driver.setDepot(this); + } + } + + + // ----- Singleton accessor --------------------------------------------- + + /** + * Return a singleton instance of the SimpleDepot. + * + * @return the singleton SimpleDepot instance. + */ + public static SimpleDepot getInstance() + { + return SingletonHolder.INSTANCE; + } + + /** + * Helper class to delay the initialization of the SimpleDepot singleton. + */ + private static class SingletonHolder + { + public static final SimpleDepot INSTANCE = new SimpleDepot(); + } + + + // ----- Depot interface ------------------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public EndPoint resolveEndPoint(String sName) + { + for (Driver driver : getDependencies().getDrivers().values()) + { + EndPoint ep = driver.resolveEndPoint(sName); + if (ep != null) + { + return ep; + } + } + throw new IllegalArgumentException("unresolvable endpoint " + sName + + "; no supporting driver registered"); + } + + /** + * {@inheritDoc} + */ + @Override + public MessageBus createMessageBus(EndPoint pointLocal) + { + if (pointLocal == null) + { + pointLocal = getDefaultMessageBusEndPoint(); + } + + for (Driver driver : getDependencies().getDrivers().values()) + { + if (driver.isSupported(pointLocal)) + { + Bus bus = driver.createBus(pointLocal); + if (bus instanceof MessageBus) + { + return (MessageBus) bus; + } + new BusCloser(bus).close(); + throw new IllegalArgumentException(pointLocal + + " does not describe a MessageBus"); + } + } + + throw new IllegalArgumentException(pointLocal + + " is not creatable; no supporting driver registered"); + } + + /** + * {@inheritDoc} + */ + @Override + public MemoryBus createMemoryBus(EndPoint pointLocal) + { + if (pointLocal == null) + { + pointLocal = getDefaultMemoryBusEndPoint(); + } + + for (Driver driver : getDependencies().getDrivers().values()) + { + if (driver.isSupported(pointLocal)) + { + Bus bus = driver.createBus(pointLocal); + if (bus instanceof MemoryBus) + { + return (MemoryBus) bus; + } + new BusCloser(bus).close(); + throw new IllegalArgumentException(pointLocal + + " does not describe a MemoryBus"); + } + } + + throw new IllegalArgumentException(pointLocal + + " is not creatable; no supporting driver registered"); + } + + + // ----- helpers -------------------------------------------------------- + + /** + * BusCloser is a collector which closes a bus as soon as it is opened. + */ + protected class BusCloser + implements Collector, Closeable + { + BusCloser(Bus bus) + { + m_bus = bus; + bus.setEventCollector(this); + bus.open(); + } + + @Override + public void add(Event event) + { + switch (event.getType()) + { + case OPEN: + m_bus.close(); + break; + + case CLOSE: + synchronized (this) + { + m_bus = null; + notify(); + } + } + } + + @Override + public void flush() {} + + @Override + public void close() + { + boolean fInterrupted = false; + synchronized (this) + { + while (m_bus != null) + { + try + { + Blocking.wait(this); + } + catch (InterruptedException e) + { + fInterrupted = true; + } + } + } + + if (fInterrupted) + { + Thread.currentThread().interrupt(); + } + } + + private Bus m_bus; + } + + /** + * Return the default local MessageBus EndPoint. + * + * @return the default local MessageBus EndPoint + */ + protected EndPoint getDefaultMessageBusEndPoint() + { + return resolveEndPoint(getDependencies().getDefaultMessageBusEndPoint()); + } + + /** + * Return the default local MemoryBus EndPoint. + * + * @return the default local MemoryBus EndPoint + */ + protected EndPoint getDefaultMemoryBusEndPoint() + { + return resolveEndPoint(getDependencies().getDefaultMemoryBusEndPoint()); + } + + /** + * Register a Driver with the Depot. + * + * @param driver the driver to register + */ + protected void registerDriver(Driver driver) + { + if (driver == null) + { + throw new IllegalArgumentException("driver cannot be null"); + } + + driver.setDepot(this); + } + + + /** + * Return the Depot's dependencies. + * + * @return the dependencies + */ + public Dependencies getDependencies() + { + return m_dependencies; + } + + /** + * Produce a shallow copy of the supplied dependencies. + * + * @param deps the dependencies to copy + * + * @return the dependencies + */ + protected DefaultDependencies copyDependencies(Dependencies deps) + { + return new DefaultDependencies(deps); + } + + + // ----- interface: Dependencies ---------------------------------------- + + /** + * Dependencies specifies all dependency requirements of the SimpleDepot. + */ + public interface Dependencies + { + /** + * Return the default MessageBus EndPoint name. + * + * @return the default MessageBus EndPoint name + */ + public String getDefaultMessageBusEndPoint(); + + /** + * Specify the default MessageBus EndPoint name. + * + * @param sEp the EndPoint name + * + * @return this object + */ + public Dependencies setDefaultMessageBusEndPoint(String sEp); + + /** + * Return the default MessageBus EndPoint name. + * + * @return the default MessageBus EndPoint name + */ + public String getDefaultMemoryBusEndPoint(); + + /** + * Specify the default MemoryBus EndPoint name. + * + * @param sEp the EndPoint name + * + * @return this object + */ + public Dependencies setDefaultMemoryBusEndPoint(String sEp); + + /** + * Return the Drivers to use in this Depot. + * + * @return the drivers to use + */ + public Map getDrivers(); + + /** + * Specify the set of Drivers to use in this Depot, keyed by a + * descriptive name. + * + * @param mapDriver the drivers to utilize + * + * @return this object + */ + public Dependencies setDrivers(Map mapDriver); + + /** + * Return the Logger to use. + * + * @return the logger + */ + public Logger getLogger(); + + /** + * Return the SSLSettings. + * + * @return the SSLSettings + */ + public SSLSettings getSSLSettings(); + } + + + // ----- inner class: DefaultDependencies ------------------------------- + + /** + * DefaultDepenencies provides a default implmentation of the Depot's + * depencies. + */ + public static class DefaultDependencies + implements Dependencies + { + /** + * Construct a DefaultDependencies object. + */ + public DefaultDependencies() + { + } + + /** + * Construct a DefaultDependencies object copying the values from the + * specified dependencies object. + * + * @param deps the dependencies to copy, or null + */ + public DefaultDependencies(Dependencies deps) + { + if (deps != null) + { + m_sDefaultMsgBusEndPoint = deps.getDefaultMessageBusEndPoint(); + m_sDefaultMemBusEndPoint = deps.getDefaultMemoryBusEndPoint(); + m_mapDriver = deps.getDrivers(); + m_logger = deps.getLogger(); + } + } + + + // ----- Dependencies interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getDefaultMessageBusEndPoint() + { + String sEp = m_sDefaultMsgBusEndPoint; + return sEp == null ? "tmb://0.0.0.0:-1" : sEp; + } + + /** + * {@inheritDoc} + */ + @Override + public DefaultDependencies setDefaultMessageBusEndPoint(String sEp) + { + m_sDefaultMsgBusEndPoint = sEp; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public String getDefaultMemoryBusEndPoint() + { + String sEp = m_sDefaultMemBusEndPoint; + return sEp == null ? "trb://0.0.0.0:-1" : sEp; + } + + /** + * {@inheritDoc} + */ + @Override + public DefaultDependencies setDefaultMemoryBusEndPoint(String sEp) + { + m_sDefaultMemBusEndPoint = sEp; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Map getDrivers() + { + Map mapDriver = m_mapDriver; + if (mapDriver == null) + { + mapDriver = new HashMap(); + + mapDriver.put(TCP_SOCKET_BUS, new SocketBusDriver( + new SocketBusDriver.DefaultDependencies() + .setMessageBusProtocol(TCP_MESSAGE_BUS_PROTOCOL) + .setMemoryBusProtocol(TCP_MEMORY_BUS_PROTOCOL) + .setSocketProvider(TcpSocketProvider.MULTIPLEXED))); + mapDriver.put(SDP_SOCKET_BUS, new SocketBusDriver( + new SocketBusDriver.DefaultDependencies() + .setMessageBusProtocol(SDP_MESSAGE_BUS_PROTOCOL) + .setMemoryBusProtocol(SDP_MEMORY_BUS_PROTOCOL) + .setSocketProvider(SdpSocketProvider.MULTIPLEXED))); + + SSLSettings settingsSSL = m_settingsSSL; + if (settingsSSL != null) + { + mapDriver.put(TCP_SECURE_SOCKET_BUS, new SocketBusDriver( + new SocketBusDriver.DefaultDependencies() + .setMessageBusProtocol(TCP_SECURE_MESSAGE_BUS_PROTOCOL) + .setMemoryBusProtocol(TCP_SECURE_MEMORY_BUS_PROTOCOL) + .setSocketProvider(new SSLSocketProvider( + new SSLSocketProvider.DefaultDependencies() + .applySSLSettings(settingsSSL) + .setDelegate(TcpSocketProvider.MULTIPLEXED))))); + + mapDriver.put(SDP_SECURE_SOCKET_BUS, new SocketBusDriver( + new SocketBusDriver.DefaultDependencies() + .setMessageBusProtocol(SDP_SECURE_MESSAGE_BUS_PROTOCOL) + .setMemoryBusProtocol(SDP_SECURE_MEMORY_BUS_PROTOCOL) + .setSocketProvider(new SSLSocketProvider( + new SSLSocketProvider.DefaultDependencies() + .applySSLSettings(settingsSSL) + .setDelegate(SdpSocketProvider.MULTIPLEXED))))); + } + + m_mapDriver = mapDriver = Collections.unmodifiableMap(mapDriver); + } + return mapDriver; + } + + /** + * {@inheritDoc} + */ + @Override + public DefaultDependencies setDrivers(Map mapDriver) + { + m_mapDriver = mapDriver; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public Logger getLogger() + { + Logger logger = m_logger; + return logger == null ? LOGGER : logger; + } + + /** + * Specify the Logger to use. + * + * @param logger the logger + * + * @return this object + */ + public DefaultDependencies setLogger(Logger logger) + { + m_logger = logger; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public SSLSettings getSSLSettings() + { + return m_settingsSSL; + } + + /** + * Set the SSLSettings. + * + * @param settingsSSL the ssl properties + * + * @return this object + */ + public DefaultDependencies setSSLSettings(SSLSettings settingsSSL) + { + m_settingsSSL = settingsSSL; + return this; + } + + + // ----- helpers ------------------------------------------------ + + /** + * Validate the supplied dependencies. + * + * @throws IllegalArgumentException on an argument error + * + * @return this object + */ + protected DefaultDependencies validate() + { + ensureArgument(getDefaultMemoryBusEndPoint(), "DefaultMemoryBusEndPoint"); + ensureArgument(getDefaultMessageBusEndPoint(), "DefaultMessageBusEndPoint"); + ensureArgument(getDrivers(), "Drivers"); + + for (Driver driver: getDrivers().values()) + { + ensureArgument(driver, "driver"); + } + + return this; + } + + /** + * Ensure that the specified object is non-null. + * + * @param o the object to ensure + * @param sName the name of the corresponding parameter + * + * @throws IllegalArgumentException if o is null + */ + protected static void ensureArgument(Object o, String sName) + { + if (o == null) + { + throw new IllegalArgumentException(sName + " cannot be null"); + } + } + + + // ----- data members ------------------------------------------- + + /** + * The default MessageBus EndPoint name. + */ + protected String m_sDefaultMsgBusEndPoint; + + /** + * The default MemoryBus EndPoint name. + */ + protected String m_sDefaultMemBusEndPoint; + + /** + * The Drivers to use. + */ + protected Map m_mapDriver; + + /** + * The Logger. + */ + protected Logger m_logger; + + /** + * The configured ssl properties. + */ + protected SSLSettings m_settingsSSL; + } + + + // ----- inner class: DeferredDriver ------------------------------------- + + /** + * A DeferredDriver defers the instantiation of the specified driver until it is used. + * + * This is beneficial for drivers which are rarely used but which are expensive to instantiate + */ + protected static class DeferredDriver + implements Driver + { + public DeferredDriver(Factory factory) + { + f_factory = factory; + } + + @Override + public void setDepot(Depot depot) + { + m_depot = depot; + if (m_delegate != null) + { + m_delegate.setDepot(depot); + } + } + + @Override + public Depot getDepot() + { + return m_depot; + } + + @Override + public EndPoint resolveEndPoint(String sName) + { + return delegate().resolveEndPoint(sName); + } + + @Override + public boolean isSupported(EndPoint point) + { + return delegate().isSupported(point); + } + + @Override + public Bus createBus(EndPoint pointLocal) + { + return delegate().createBus(pointLocal); + } + + protected Driver delegate() + { + Driver driver = m_delegate; + if (driver == null) + { + synchronized (this) + { + try + { + m_delegate = driver = f_factory.create(); + } + catch (Exception e) + { + throw new UnsupportedOperationException(e); + } + } + } + + return driver; + } + + protected final Factory f_factory; + protected volatile Driver m_delegate; + protected Depot m_depot; + } + + // ----- constants ------------------------------------------------------ + + /** + * Driver name for TCP Socket Bus. + */ + private static final String TCP_SOCKET_BUS = "TcpSocketBus"; + + /** + * Protocol name for the TCP message bus. + */ + public static final String TCP_MESSAGE_BUS_PROTOCOL = "tmb"; + + /** + * Protocol name for the TCP memory bus. + */ + public static final String TCP_MEMORY_BUS_PROTOCOL = "trb"; + + /** + * Driver name for Secure TCP Socket Bus. + */ + private static final String TCP_SECURE_SOCKET_BUS = "SecureTcpSocketBus"; + + /** + * Protocol name for the SSL protected TCP message bus. + */ + public static final String TCP_SECURE_MESSAGE_BUS_PROTOCOL = "tmbs"; + + /** + * Protocol name for the SSL protected TCP memory bus. + */ + public static final String TCP_SECURE_MEMORY_BUS_PROTOCOL = "trbs"; + + /** + * Driver name for SDP Socket Bus. + */ + private static final String SDP_SOCKET_BUS = "SdpSocketBus"; + + /** + * Protocol name for the SDP message bus. + */ + public static final String SDP_MESSAGE_BUS_PROTOCOL = "sdmb"; + + /** + * Protocol name for the SDP memory bus. + */ + public static final String SDP_MEMORY_BUS_PROTOCOL = "sdrb"; + + /** + * Driver name for Secure SDP Socket Bus. + */ + private static final String SDP_SECURE_SOCKET_BUS = "SecureSdpSocketBus"; + + /** + * Protocol name for the SSL protected SDP message bus. + */ + public static final String SDP_SECURE_MESSAGE_BUS_PROTOCOL = "sdmbs"; + + /** + * Protocol name for the SSL protected SDP memory bus. + */ + public static final String SDP_SECURE_MEMORY_BUS_PROTOCOL = "sdrbs"; + + /** + * The default Logger for the depot. + */ + private static Logger LOGGER = Logger.getLogger(SimpleDepot.class.getName()); + + + // ----- data members --------------------------------------------------- + + /** + * The Depot's Dependencies. + */ + protected Dependencies m_dependencies; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/SimpleEvent.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/SimpleEvent.java new file mode 100644 index 0000000000000..cbcea0135825d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/SimpleEvent.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus.util; + +import com.oracle.coherence.common.net.exabus.EndPoint; +import com.oracle.coherence.common.net.exabus.Event; + +public class SimpleEvent + implements Event + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a SimpleEvent. + * + * @param type the event type + * @param point the associated EndPoint + */ + public SimpleEvent(Type type, EndPoint point) + { + this(type, point, null); + } + + /** + * Construct a SimpleEvent. + * + * @param type the event type + * @param point the associated EndPoint + * @param oContent the event content + */ + public SimpleEvent(Type type, EndPoint point, Object oContent) + { + m_type = type; + m_point = point; + m_oContent = oContent; + } + + + // ----- Event interface ------------------------------------------------ + + /** + * {@inheritDoc} + */ + public Type getType() + { + return m_type; + } + + /** + * {@inheritDoc} + */ + public EndPoint getEndPoint() + { + return m_point; + } + + /** + * {@inheritDoc} + */ + public Object getContent() + { + return m_oContent; + } + + /** + * {@inheritDoc} + */ + public Object dispose(boolean fTakeContent) + { + return fTakeContent ? m_oContent : null; + } + + /** + * {@inheritDoc} + */ + public void dispose() + { + dispose(/*fTakeContent*/ false); + } + + + // ----- Object interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + String s = getType() + " event for " + getEndPoint(); + Object oContent = getContent(); + return oContent == null ? s : s + " content " + oContent; + } + + + // ----- data members --------------------------------------------------- + + /** + * The event type. + */ + private final Type m_type; + + /** + * The EndPoint associated with the event + */ + private final EndPoint m_point; + + /** + * The Event content. + */ + private final Object m_oContent; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/UrlEndPoint.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/UrlEndPoint.java new file mode 100644 index 0000000000000..c4197f66dc300 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/exabus/util/UrlEndPoint.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.net.exabus.util; + + +import com.oracle.coherence.common.base.Hasher; +import com.oracle.coherence.common.net.SocketProvider; +import com.oracle.coherence.common.net.exabus.EndPoint; + +import java.net.SocketAddress; + + +/** + * UrlEndPoint is an EndPoint formatted using URL like syntax. + *

+ * The basic syntax is protocol://address[?query], the format for the address portion + * is ultimately parsed by supplied {@link SocketProvider}, and may deviate + * from proper URL syntax. + *

+ * The UrlEndPoint does not currently support URL portions beyond protocol, address, and query string, + * such as path. + * + * @author mf 2011.01.13 + */ +public class UrlEndPoint + implements EndPoint + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a SocketEndPoint. + * + * @param sName the endpoint name + * @param provider the provider + * @param hasher the SocketAddress hasher + */ + public UrlEndPoint(String sName, SocketProvider provider, + Hasher hasher) + { + if (sName == null) + { + throw new IllegalArgumentException("name cannot be null"); + } + + int ofProtocolEnd = sName.indexOf(PROTOCOL_DELIMITER); + if (ofProtocolEnd == -1) + { + throw new IllegalArgumentException("name does not contain a protocol"); + } + String sProtocol = sName.substring(0, ofProtocolEnd); + String sRemainder = sName.substring(ofProtocolEnd + PROTOCOL_DELIMITER.length()); + String sAddress; + String sQuery; + + if (sRemainder.indexOf('/') != -1) + { + throw new IllegalArgumentException("URL paths are not supported"); + } + else if (sRemainder.indexOf('?') != -1) + { + // URL contains a query string; pull it out + int ofQuery = sRemainder.indexOf('?'); + sAddress = sRemainder.substring(0, ofQuery); + sQuery = sRemainder.substring(ofQuery + 1); + } + else + { + sAddress = sRemainder; + sQuery = null; + } + + f_sName = sName; + f_sProtocol = sProtocol; + f_hasher = hasher; + f_address = provider.resolveAddress(sAddress); + f_sQuery = sQuery; + f_nHashCode = sProtocol.hashCode() + hasher.hashCode(f_address); + } + + + // ----- URLEndPoint interface --------------------------------------- + + /** + * Return the SocketAddress represented by this EndPoint. + * + * @return the SocketAddress represented by this EndPoint + */ + public SocketAddress getAddress() + { + return f_address; + } + + /** + * Return the protocol represented by this EndPoint. + * + * @return the protocol represented by this EndPoint + */ + public String getProtocol() + { + return f_sProtocol; + } + + /** + * Return the URL EndPoints query string if any. + * + * @return the query string or null + */ + public String getQueryString() + { + return f_sQuery; + } + + + // ----- EndPoint interface --------------------------------------------- + + /** + * {@inheritDoc} + */ + public String getCanonicalName() + { + return f_sName; + } + + + // ----- Object interface ---------------------------------------------- + + /** + * {@inheritDoc} + */ + public int hashCode() + { + return f_nHashCode; + } + + /** + * Compare two UrlEndPoints for equality. + *

+ * The equality is not String equality, but rather logical equality based upon the protocol and resolved address. + * The query string if any is not considered in the equality comparison. + *

+ * + */ + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + else if (o instanceof UrlEndPoint) + { + UrlEndPoint that = (UrlEndPoint) o; + return getProtocol().equals(that.getProtocol()) && + f_hasher.equals(getAddress(), that.getAddress()); + } + else + { + return false; + } + } + + /** + * {@inheritDoc} + */ + public String toString() + { + return f_sName; + } + + + // ----- constants ----------------------------------------------------- + + /** + * The protocol delimiter + */ + public static final String PROTOCOL_DELIMITER = "://"; + + // ----- data members -------------------------------------------------- + + /** + * The canonical name. + */ + private final String f_sName; + + /** + * The protocol name. + */ + private final String f_sProtocol; + + /** + * The SocketAddress. + */ + private final SocketAddress f_address; + + /** + * The query string if any. + */ + private final String f_sQuery; + + /** + * The endpoint's hashcode + */ + private final int f_nHashCode; + + /** + * The SocketAddress hasher. + */ + private final Hasher f_hasher; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/package.html new file mode 100644 index 0000000000000..37d55c2253c71 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/net/package.html @@ -0,0 +1,6 @@ + +The net package provides a number networking related utility classes. Including a series of SocketProvider classes and +the SelectionService framework. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractCanonicalProperty.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractCanonicalProperty.java new file mode 100644 index 0000000000000..98d886c45f5f6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractCanonicalProperty.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +/** + * An abstract base class for canonical (not platform specific) property + * extension implementations. + * + * @author as 2013.08.30 + */ +public abstract class AbstractCanonicalProperty + extends AbstractProperty + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct an {@code AbstractCanonicalProperty} instance. + * + * @param parent the parent {@code ExtensibleProperty} instance + */ + protected AbstractCanonicalProperty(ExtensibleProperty parent) + { + super(parent); + } + + // ---- Property implementation ----------------------------------------- + + @Override + public CanonicalTypeDescriptor getType() + { + return getParent().getType(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractCanonicalType.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractCanonicalType.java new file mode 100644 index 0000000000000..4214c90947379 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractCanonicalType.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +/** + * An abstract base class for canonical (not platform specific) type extension + * implementations. + * + * @author as 2013.08.30 + */ +public abstract class AbstractCanonicalType

+ extends AbstractType + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct an {@code AbstractCanonicalType} instance. + * + * @param parent the parent {@code ExtensibleType} instance + */ + protected AbstractCanonicalType(ExtensibleType parent) + { + super(parent); + } + + // ---- Type implementation ----------------------------------------- + + @Override + public CanonicalTypeDescriptor getDescriptor() + { + return getParent().getDescriptor(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractProperty.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractProperty.java new file mode 100644 index 0000000000000..e6a85b21f3f14 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractProperty.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.lang.reflect.ParameterizedType; + + +/** + * An abstract base class for property extension implementations. + * + * @author as 2013.07.08 + */ +@SuppressWarnings("unchecked") +public abstract class AbstractProperty + implements Property + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct an {@code AbstractProperty} instance. + * + * @param parent the parent {@code ExtensibleProperty} instance + */ + protected AbstractProperty(ExtensibleProperty parent) + { + m_parent = parent; + + java.lang.reflect.Type superclass = getClass().getGenericSuperclass(); + try + { + this.m_typeDescriptorClass = (Class) + ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } + catch (Exception e) + { + this.m_typeDescriptorClass = (Class) CanonicalTypeDescriptor.class; + } + } + + // ---- Property implementation ----------------------------------------- + + @Override + public String getName() + { + return m_parent.getName(); + } + + @Override + public T getExtension(Class extensionType) + { + return m_parent.getExtension(extensionType); + } + + @Override + public void accept(SchemaVisitor visitor) + { + visitor.visitProperty(this); + } + + // ---- public API ------------------------------------------------------ + + /** + * Return the parent {@code ExtensibleProperty} instance. + * + * @return the parent {@code ExtensibleProperty} instance + */ + public ExtensibleProperty getParent() + { + return m_parent; + } + + // ---- Object methods -------------------------------------------------- + + public String toString() + { + return getClass().getSimpleName() + "{" + + "name=" + getName() + + ", type=" + getType() + + '}'; + } + + // ---- helper methods -------------------------------------------------- + + /** + * Return the type descriptor class. + * + * @return the type descriptor class + */ + protected Class getTypeDescriptorClass() + { + return m_typeDescriptorClass; + } + + // ---- data members ---------------------------------------------------- + + private ExtensibleProperty m_parent; + private Class m_typeDescriptorClass; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractPropertyHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractPropertyHandler.java new file mode 100644 index 0000000000000..82b1649511516 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractPropertyHandler.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.lang.reflect.Constructor; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + + +/** + * Abstract base class for {@link PropertyHandler} implementations. + * + * @param the internal representation of property metadata + * @param the external representation of property metadata + * + * @author as 2013.11.20 + */ +@SuppressWarnings("unchecked") +public abstract class AbstractPropertyHandler + implements PropertyHandler + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct an {@code AbstractPropertyHandler} instance. + */ + protected AbstractPropertyHandler() + { + Class clazz = getClass(); + Type superclass = clazz.getGenericSuperclass(); + + Type internalType = ((ParameterizedType) superclass).getActualTypeArguments()[0]; + + while (!superclass.toString().startsWith("com.oracle.coherence.common.schema.AbstractPropertyHandler")) + { + clazz = clazz.getSuperclass(); + superclass = clazz.getGenericSuperclass(); + } + Type externalType = ((ParameterizedType) superclass).getActualTypeArguments()[1]; + + if (internalType instanceof Class) + { + setInternalClass((Class) internalType); + } + + if (externalType instanceof Class) + { + m_externalClass = (Class) externalType; + } + } + + // ---- helper methods -------------------------------------------------- + + /** + * Set the internal class this property handler is for, and ensure that it + * implements a constructor that accepts {@link ExtensibleProperty} instance + * as a sole argument. + * + * @param internalType the internal class this type handler is for; must + * have a constructor that accepts {@link ExtensibleProperty} + * instance as a sole argument + */ + protected void setInternalClass(Class internalType) + { + m_internalClass = internalType; + try + { + m_ctorInternal = m_internalClass.getConstructor(ExtensibleProperty.class); + } + catch (NoSuchMethodException e) + { + throw new RuntimeException("Property class " + m_internalClass.getName() + + " does not implement a constructor that accepts " + ExtensibleProperty.class.getName()); + } + } + + // ---- PropertyHandler implementation ---------------------------------- + + @Override + public Class getInternalPropertyClass() + { + return m_internalClass; + } + + @Override + public Class getExternalPropertyClass() + { + return m_externalClass; + } + + @Override + public TInternal createProperty(ExtensibleProperty parent) + { + try + { + return m_ctorInternal.newInstance(parent); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** + * No-op implementation, which should be overridden by the concrete property + * handler implementations responsible for importing property information from + * an external source. + * + * @inheritDoc + */ + @Override + public void importProperty(TInternal property, TExternal source, Schema schema) + { + } + + /** + * No-op implementation, which should be overridden by the concrete property + * handler implementations responsible for exporting properties from a schema. + * + * @inheritDoc + */ + @Override + public void exportProperty(TInternal property, TExternal target, Schema schema) + { + } + + // ---- data members ---------------------------------------------------- + + private Class m_internalClass; + private Class m_externalClass; + + private Constructor m_ctorInternal; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractSchemaExporter.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractSchemaExporter.java new file mode 100644 index 0000000000000..9625d682da3a0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractSchemaExporter.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +/** + * An abstract base class for {@link SchemaExporter} implementations. + * + * @author as 2013.12.02 + */ +public abstract class AbstractSchemaExporter + implements SchemaExporter + { + // ---- SchemaExporter implementation ----------------------------------- + + @Override + public Class getInternalTypeClass() + { + return ExtensibleType.class; + } + + @Override + public void importType(ExtensibleType type, TExternal source, Schema schema) + { + } + + @Override + public Class getInternalPropertyClass() + { + return ExtensibleProperty.class; + } + + @Override + public void importProperty(ExtensibleProperty property, PExternal source, Schema schema) + { + } + + @Override + public ExtensibleType createType(ExtensibleType parent) + { + return new ExtensibleType(); + } + + @Override + public ExtensibleProperty createProperty(ExtensibleProperty parent) + { + return new ExtensibleProperty(); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractSchemaSource.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractSchemaSource.java new file mode 100644 index 0000000000000..c8fefa4e603d1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractSchemaSource.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.util.Collection; + + +/** + * An abstract class that should be used as a base class for the custom + * {@link SchemaSource} implementations. + * + * @param the type of the external representation of the types + * handled by this schema source + * @param the type of the external representation of the properties + * handled by this schema source + * + * @author as 2013.07.11 + */ +public abstract class AbstractSchemaSource + implements SchemaSource + { + // ---- abstract methods ------------------------------------------------ + + /** + * Return the property name from the specified external representation. + * + * @param source the external representation of a property + * + * @return the property name from the specified external representation + */ + protected abstract String getPropertyName(PExternal source); + + /** + * Return a collection of available properties from the specified external + * type representation. + * + * @param source the external representation of a type + * + * @return a collection of available properties from the specified external + * type representation + */ + protected abstract Collection getProperties(TExternal source); + + // ---- TypeHandler implementation -------------------------------------- + + @Override + public Class getInternalTypeClass() + { + return ExtensibleType.class; + } + + @Override + public ExtensibleType createType(ExtensibleType parent) + { + return new ExtensibleType(); + } + + // ---- PropertyHandler implementation ---------------------------------- + + @Override + public Class getInternalPropertyClass() + { + return ExtensibleProperty.class; + } + + @Override + public ExtensibleProperty createProperty(ExtensibleProperty parent) + { + return new ExtensibleProperty(); + } + + // ---- helper methods -------------------------------------------------- + + /** + * Helper method that can be used to correctly populate an {@link + * ExtensibleType} instance based on registered type handler and external + * source of type metadata. + * + * @param schema the schema instance that is being populated + * @param type the {@code ExtensibleType} instance to populate + * @param source the external source of type metadata + * + * @return the populated {@code ExtensibleType} instance + */ + protected ExtensibleType populateTypeInternal(Schema schema, ExtensibleType type, TExternal source) + { + if (type == null) + { + type = createType(null); + } + importType(type, source, schema); + + for (TypeHandler handler : schema.getTypeHandlers(getExternalTypeClass())) + { + Type ext = type.getExtension(handler.getInternalTypeClass()); + if (ext == null) + { + ext = handler.createType(type); + type.addExtension(ext); + } + handler.importType(ext, source, schema); + } + + for (PExternal propertySource : getProperties(source)) + { + ExtensibleProperty property = type.getProperty(getPropertyName(propertySource)); + if (property == null) + { + property = createProperty(null); + } + importProperty(property, propertySource, schema); + + for (PropertyHandler handler : schema.getPropertyHandlers( + getExternalPropertyClass())) + { + Property ext = property.getExtension(handler.getInternalPropertyClass()); + if (ext == null) + { + ext = handler.createProperty(property); + property.addExtension(ext); + } + handler.importProperty(ext, propertySource, schema); + } + type.addProperty(property); + } + + return type; + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractType.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractType.java new file mode 100644 index 0000000000000..f5080de9c5f35 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractType.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + + +/** + * An abstract base class for type extension implementations. + * + * @author as 2013.07.08 + */ +@SuppressWarnings("unchecked") +public abstract class AbstractType

+ implements Type + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct an {@code AbstractType} instance. + * + * @param parent the parent {@code ExtensibleType} instance + */ + protected AbstractType(ExtensibleType parent) + { + m_parent = parent; + + java.lang.reflect.Type superclass = getClass().getGenericSuperclass(); + java.lang.reflect.Type[] actualTypeArguments = ((ParameterizedType) superclass).getActualTypeArguments(); + this.m_propertyClass = (Class

) actualTypeArguments[0]; + this.m_typeDescriptorClass = actualTypeArguments.length > 1 + ? (Class) actualTypeArguments[1] + : (Class) CanonicalTypeDescriptor.class; + } + + // ---- Type implementation --------------------------------------------- + + @Override + public String getNamespace() + { + if (getDescriptor() == null) + { + return null; + } + return getDescriptor().getNamespace(); + } + + @Override + public String getName() + { + if (getDescriptor() == null) + { + return null; + } + return getDescriptor().getName(); + } + + @Override + public String getFullName() + { + if (getDescriptor() == null) + { + return null; + } + return getDescriptor().getFullName(); + } + + @Override + public T getExtension(Class clzExtension) + { + return m_parent.getExtension(clzExtension); + } + + @Override + public void accept(SchemaVisitor visitor) + { + visitor.visitType(this); + } + + @Override + public P getProperty(String propertyName) + { + return m_parent.getProperty(propertyName).getExtension(m_propertyClass); + } + + @Override + public List

getProperties() + { + Collection properties = m_parent.getProperties(); + List

results = new ArrayList<>(properties.size()); + for (ExtensibleProperty p : properties) + { + P property = p.getExtension(m_propertyClass); + if (property != null) + { + results.add(property); + } + } + return results; + } + + // ---- public API ------------------------------------------------------ + + /** + * Return the parent {@code ExtensibleType} instance. + * + * @return the parent {@code ExtensibleType} instance + */ + public ExtensibleType getParent() + { + return m_parent; + } + + // ---- Object methods -------------------------------------------------- + + public String toString() + { + return getClass().getSimpleName() + "{" + + "name=" + getName() + + ", desc=" + getDescriptor() + + '}'; + } + + // ---- helper methods -------------------------------------------------- + + /** + * Return the property class. + * + * @return the property class + */ + protected Class

getPropertyClass() + { + return m_propertyClass; + } + + /** + * Return the type descriptor class. + * + * @return the type descriptor class + */ + protected Class getTypeDescriptorClass() + { + return m_typeDescriptorClass; + } + + // ---- data members ---------------------------------------------------- + + private ExtensibleType m_parent; + private Class

m_propertyClass; + private Class m_typeDescriptorClass; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractTypeDescriptor.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractTypeDescriptor.java new file mode 100644 index 0000000000000..ec3bba0db39bb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractTypeDescriptor.java @@ -0,0 +1,588 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +import com.oracle.coherence.common.schema.util.StringUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +/** + * An abstract base class for {@link TypeDescriptor} implementations. + * + * @author as 2013.08.28 + */ +public abstract class AbstractTypeDescriptor + implements TypeDescriptor + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code AbstractTypeDescriptor} instance. + * + * @param name the type name + */ + protected AbstractTypeDescriptor(String name) + { + this(null, name, false); + } + + /** + * Construct {@code AbstractTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + */ + protected AbstractTypeDescriptor(String[] namespace, String name) + { + this(namespace, name, false); + } + + /** + * Construct {@code AbstractTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + */ + protected AbstractTypeDescriptor(String[] namespace, String name, boolean fArray) + { + this(namespace, name, fArray, null); + } + + /** + * Construct {@code AbstractTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + * @param genericArgs the list of generic argument descriptors + */ + protected AbstractTypeDescriptor(String[] namespace, String name, + boolean fArray, List genericArgs) + { + m_namespace = namespace; + m_name = name; + m_fArray = fArray; + m_genericArgs = genericArgs; + + if (!fArray) + { + m_arrayType = createArrayType(namespace, name); + } + } + + // ---- abstract methods ------------------------------------------------ + + /** + * Create an array type for a given namespace and type name. + * + * @param namespace the type namespace + * @param name the type name + * + * @return an array type for a given namespace and type name + */ + protected abstract T createArrayType(String[] namespace, String name); + + /** + * Return a parser for a string representation of this type descriptor. + * + * @return a parser for a string representation of this type descriptor + */ + protected abstract Parser getParser(); + + // ---- TypeDescriptor implementation ----------------------------------- + + @Override + public String getNamespace() + { + return m_namespace == null + ? null + : String.join(getParser().getSeparator(), m_namespace); + } + + @Override + public String getName() + { + return m_name; + } + + @Override + public String getFullName() + { + String ns = getNamespace(); + return ns == null ? m_name : ns + getParser().getSeparator() + m_name; + } + + @Override + public boolean isArray() + { + return m_fArray; + } + + @Override + public boolean isGenericType() + { + return m_genericArgs != null && m_genericArgs.size() > 0; + } + + @Override + public List getGenericArguments() + { + return m_genericArgs; + } + + // ----- object methods ------------------------------------------------- + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + AbstractTypeDescriptor that = (AbstractTypeDescriptor) o; + + if (m_fArray != that.m_fArray) + { + return false; + } + // Probably incorrect - comparing Object[] arrays with Arrays.equals + if (!Arrays.equals(m_namespace, that.m_namespace)) + { + return false; + } + if (m_name != null ? !m_name.equals(that.m_name) : that.m_name != null) + { + return false; + } + if (m_arrayType != null ? !m_arrayType.equals(that.m_arrayType) : that.m_arrayType != null) + { + return false; + } + return m_genericArgs != null ? m_genericArgs.equals(that.m_genericArgs) : that.m_genericArgs == null; + } + + @Override + public int hashCode() + { + int result = Arrays.hashCode(m_namespace); + result = 31 * result + (m_name != null ? m_name.hashCode() : 0); + result = 31 * result + (m_fArray ? 1 : 0); + result = 31 * result + (m_arrayType != null ? m_arrayType.hashCode() : 0); + result = 31 * result + (m_genericArgs != null ? m_genericArgs.hashCode() : 0); + return result; + } + + // ---- helper methods -------------------------------------------------- + + /** + * Return the namespace components as an array. + * + * @return the namespace components as an array + */ + public String[] getNamespaceComponents() + { + return m_namespace; + } + + /** + * Set the type namespace. + * + * @param namespace an array of the type namespace components + */ + public void setNamespace(String[] namespace) + { + m_namespace = namespace; + } + + /** + * Set the type namespace. + * + * @param namespace the type namespace + */ + public void setNamespace(String namespace) + { + m_namespace = namespace == null + ? null + : StringUtils.split(namespace, getParser().getSeparator()); + } + + /** + * Set the type name. + * + * @param name the type name + */ + public void setName(String name) + { + m_name = name; + } + + /** + * Return {@code true} if the full name of the specified type is equal to + * the full name of the type represented by this type descriptor. + * + * @param other the type descriptor to compare with + * + * @return {@code true} if the full name of the specified type is equal to + * the full name of the type represented by this type descriptor; + * {@code false} otherwise + */ + public boolean isNameEqual(AbstractTypeDescriptor other) + { + return m_name.equals(other.m_name) + && Arrays.equals(m_namespace, other.m_namespace); + } + + /** + * Return the array type represented by this type descriptor. + * + * @return the array type represented by this type descriptor + */ + protected T getArrayType() + { + return m_arrayType; + } + + // ---- Object methods -------------------------------------------------- + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + + if (m_namespace != null) + { + sb.append(getNamespace()); + sb.append(getParser().getSeparator()); + } + + sb.append(m_name); + + if (m_genericArgs != null) + { + sb.append('('); + for (int i = 0; i < m_genericArgs.size(); i++) + { + if (i > 0) + { + sb.append(", "); + } + sb.append(m_genericArgs.get(i)); + } + sb.append(')'); + } + + if (m_fArray) + { + sb.append("[]"); + } + + return sb.toString(); + } + + // ---- abstract class: Parser ------------------------------------------ + + /** + * An abstract base class for type descriptor parser implementations. + * + * @param the type of {@code TypeDescriptor}; must extend {@code + * AbstractTypeDescriptor} + */ + @SuppressWarnings("unchecked") + protected static abstract class Parser + { + // ---- constructors ------------------------------------------------ + + /** + * Construct {@code Parser} instance. + * + * @param separator the namespace separator + */ + protected Parser(String separator) + { + m_separator = separator; + } + + // ---- abstract methods -------------------------------------------- + + /** + * Return the descriptor for a standard (built-in) data type. + * + * @param type the fully qualified type name + * + * @return the descriptor for a standard (built-in) data type + */ + protected abstract T getStandardType(String type); + + /** + * Create the {@link TypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + * @param genericArgs the list of generic argument descriptors + * + * @return the {@code TypeDescriptor} instance + */ + protected abstract T createTypeDescriptor( + String[] namespace, String name, boolean fArray, List genericArgs); + + // ---- public API -------------------------------------------------- + + /** + * Return the namespace separator. + * + * @return the namespace separator + */ + public String getSeparator() + { + return m_separator; + } + + /** + * Parse string representation of a type name in a format used in XML + * schema definition. + * + * @param type the fully qualified type name in XML schema-compatible + * format + * + * @return the {@code TypeDescriptor} for the specified type name + */ + public T parse(String type) + { + boolean fArray = type.endsWith("[]"); + if (fArray) + { + type = type.substring(0, type.length() - 2); + } + + List genericArgs = null; + boolean fGeneric = type.contains("("); + if (fGeneric) + { + genericArgs = parseGenericArgs( + type.substring(type.indexOf("(") + 1, type.lastIndexOf(")"))); + type = type.substring(0, type.indexOf("(")); + } + + return createTypeDescriptor(type, fArray, genericArgs); + } + + /** + * Parse string representation of a type name in a Java internal format. + * + * @param type the fully qualified type name in internal format + * + * @return the {@code TypeDescriptor} for the specified type name + */ + public T parseInternal(String type) + { + boolean fArray = type.charAt(0) == '['; + if (fArray) + { + type = type.substring(1); + } + + List genericArgs = null; + boolean fGeneric = type.contains("<"); + if (fGeneric) + { + genericArgs = parseGenericArgsInternal( + type.substring(type.indexOf("<") + 1, type.lastIndexOf(">"))); + type = type.substring(0, type.indexOf("<")) + ";"; + } + + if (type.startsWith("L") && type.endsWith(";")) + { + type = type.substring(1, type.length() - 1); + } + type = type.replaceAll("/", getSeparator()); + + if (!fGeneric && type.length() == 1) + { + T primitiveType = getPrimitiveType(type); + if (primitiveType != null) + { + return fArray ? (T) primitiveType.getArrayType() : primitiveType; + } + } + + return createTypeDescriptor(type, fArray, genericArgs); + } + + // ---- helper methods ---------------------------------------------- + + /** + * Return the {@link TypeDescriptor} for a primitive type based on the + * internal type name. + * + * @param type the internal Java type name of a primitive type + * + * @return the {@link TypeDescriptor} for a primitive type based on the + * internal type name + */ + protected T getPrimitiveType(String type) + { + if ("Z".equals(type)) + { + return getStandardType("boolean"); + } + if ("B".equals(type)) + { + return getStandardType("byte"); + } + if ("C".equals(type)) + { + return getStandardType("char"); + } + if ("S".equals(type)) + { + return getStandardType("short"); + } + if ("I".equals(type)) + { + return getStandardType("int"); + } + if ("J".equals(type)) + { + return getStandardType("long"); + } + if ("F".equals(type)) + { + return getStandardType("float"); + } + if ("D".equals(type)) + { + return getStandardType("double"); + } + + return null; + } + + /** + * Create the {@link TypeDescriptor} instance. + * + * @param type the fully qualified type name + * @param fArray the flag specifying whether the type is an array type + * @param genericArgs the list of generic argument descriptors + * + * @return the {@code TypeDescriptor} instance + */ + protected T createTypeDescriptor( + String type, boolean fArray, List genericArgs) + { + T standardType = getStandardType(type); + if (standardType != null) + { + return fArray ? (T) standardType.getArrayType() : standardType; + } + + String[] tmp = StringUtils.split(type, getSeparator()); + String[] namespace = null; + if (tmp.length > 1) + { + namespace = new String[tmp.length - 1]; + System.arraycopy(tmp, 0, namespace, 0, tmp.length - 1); + } + String name = tmp[tmp.length - 1]; + + return createTypeDescriptor(namespace, name, fArray, genericArgs); + } + + /** + * Parse the generic arguments represented in a format used in XML + * schema definition. + * + * @param sArgs the generic arguments in a format used in XML schema + * definition + * + * @return a list of type descriptors for the generic arguments + */ + protected List parseGenericArgs(String sArgs) + { + List args = new ArrayList<>(); + + int i = 0; + while (i < sArgs.length()) + { + int start = i; + int c = 0; + while (i < sArgs.length() && (c > 0 || sArgs.charAt(i) != ',')) + { + if (sArgs.charAt(i) == '(') + { + c++; + } + else if (sArgs.charAt(i) == ')') + { + c--; + } + i++; + } + T ref = parse(sArgs.substring(start, i)); + args.add(ref); + } + + return args; + } + + /** + * Parse the generic arguments represented in a Java internal format. + * + * @param sArgs the generic arguments in a Java internal format + * + * @return a list of type descriptors for the generic arguments + */ + protected List parseGenericArgsInternal(String sArgs) + { + List args = new ArrayList<>(); + + int i = 0; + while (i < sArgs.length()) + { + int start = i; + int c = 0; + while (c > 0 || sArgs.charAt(i) != ';') + { + if (sArgs.charAt(i) == '<') + { + c++; + } + else if (sArgs.charAt(i) == '>') + { + c--; + } + i++; + } + i++; + T ref = parseInternal(sArgs.substring(start, i)); + args.add(ref); + } + + return args; + } + + protected final String m_separator; + } + + // ---- data members ---------------------------------------------------- + + protected String[] m_namespace; + protected String m_name; + protected boolean m_fArray; + protected T m_arrayType; + protected List m_genericArgs; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractTypeHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractTypeHandler.java new file mode 100644 index 0000000000000..1b88de88d6c48 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/AbstractTypeHandler.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.lang.reflect.Constructor; +import java.lang.reflect.ParameterizedType; + + +/** + * An abstract class that should be used as a base class for the custom + * {@link TypeHandler} implementations. + * + * @param the internal representation of type metadata + * @param the external representation of type metadata + * + * @author as 2013.11.20 + */ +@SuppressWarnings("unchecked") +public abstract class AbstractTypeHandler + implements TypeHandler + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code AbstractTypeHandler} instance. + */ + protected AbstractTypeHandler() + { + Class clazz = getClass(); + java.lang.reflect.Type superclass = clazz.getGenericSuperclass(); + + java.lang.reflect.Type internalType = ((ParameterizedType) superclass).getActualTypeArguments()[0]; + + while (!superclass.toString().startsWith("com.oracle.coherence.common.schema.AbstractTypeHandler")) + { + clazz = clazz.getSuperclass(); + superclass = clazz.getGenericSuperclass(); + } + java.lang.reflect.Type externalType = ((ParameterizedType) superclass).getActualTypeArguments()[1]; + + if (internalType instanceof Class) + { + setInternalClass((Class) internalType); + } + + if (externalType instanceof Class) + { + m_externalClass = (Class) externalType; + } + } + + // ---- helper methods -------------------------------------------------- + + /** + * Set the internal class this type handler is for, and ensure that it + * implements a constructor that accepts {@link ExtensibleType} instance + * as a sole argument. + * + * @param internalType the internal class this type handler is for; must + * have a constructor that accepts {@link ExtensibleType} + * instance as a sole argument + */ + protected void setInternalClass(Class internalType) + { + m_internalClass = internalType; + try + { + m_ctorInternal = m_internalClass.getConstructor(ExtensibleType.class); + } + catch (NoSuchMethodException e) + { + throw new RuntimeException("Type class " + m_internalClass.getName() + + " does not implement a constructor that accepts " + ExtensibleType.class.getName()); + } + } + + // ---- TypeHandler implementation -------------------------------------- + + @Override + public Class getInternalTypeClass() + { + return m_internalClass; + } + + @Override + public Class getExternalTypeClass() + { + return m_externalClass; + } + + @Override + public TInternal createType(ExtensibleType parent) + { + try + { + return m_ctorInternal.newInstance(parent); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** + * No-op implementation, which should be overridden by the concrete type + * handler implementations responsible for importing type information from + * an external source. + * + * @inheritDoc + */ + @Override + public void importType(TInternal type, TExternal source, Schema schema) + { + // no-op implementation + } + + /** + * No-op implementation, which should be overridden by the concrete type + * handler implementations responsible for exporting types from a schema. + * + * @inheritDoc + */ + @Override + public void exportType(TInternal type, TExternal target, Schema schema) + { + } + + // ---- data members ---------------------------------------------------- + + private Class m_internalClass; + private Class m_externalClass; + + private Constructor m_ctorInternal; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/CanonicalTypeDescriptor.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/CanonicalTypeDescriptor.java new file mode 100644 index 0000000000000..fb45c5bae593a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/CanonicalTypeDescriptor.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +import com.oracle.coherence.common.schema.lang.java.JavaTypeDescriptor; +import com.oracle.coherence.common.schema.util.StringUtils; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * Canonical implementation of a {@link TypeDescriptor}. + * + * @author as 2013.08.20 + */ +public class CanonicalTypeDescriptor + extends AbstractTypeDescriptor + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code CanonicalTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + */ + public CanonicalTypeDescriptor(String namespace, String name) + { + this(StringUtils.split(namespace, PARSER.getSeparator()), name); + } + + /** + * Construct {@code CanonicalTypeDescriptor} instance. + * + * @param name the type name + */ + private CanonicalTypeDescriptor(String name) + { + super(null, name, false); + } + + /** + * Construct {@code CanonicalTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + */ + private CanonicalTypeDescriptor(String[] namespace, String name) + { + super(namespace, name); + } + + /** + * Construct {@code CanonicalTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + */ + private CanonicalTypeDescriptor(String[] namespace, String name, boolean fArray) + { + super(namespace, name, fArray); + } + + /** + * Construct {@code CanonicalTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + * @param genericArgs the list of generic argument descriptors + */ + private CanonicalTypeDescriptor(String[] namespace, String name, boolean fArray, + List genericArgs) + { + super(namespace, name, fArray, genericArgs); + } + + // ---- factory methods ------------------------------------------------- + + /** + * Create {@code CanonicalTypeDescriptor} by parsing fully qualified + * canonical type name. + * + * @param type the fully qualified canonical type name + * + * @return a {@code CanonicalTypeDescriptor} for the specified type name + */ + public static CanonicalTypeDescriptor parse(String type) + { + return PARSER.parse(type); + } + + /** + * Create {@code CanonicalTypeDescriptor} from a Java type descriptor. + * + * @param type the Java type descriptor + * @param schema the schema specified type is defined in + * + * @return a {@code CanonicalTypeDescriptor} for the specified Java type + * descriptor + */ + public static CanonicalTypeDescriptor from(JavaTypeDescriptor type, Schema schema) + { + CanonicalTypeDescriptor ctd = + new CanonicalTypeDescriptor(type.getNamespaceComponents(), type.getName(), + type.isArray(), from(type.getGenericArguments(), schema)); + + ExtensibleType extType = schema.findTypeByJavaName(type.getFullName()); + if (extType != null) + { + ctd.setNamespace(extType.getNamespace()); + ctd.setName(extType.getName()); + } + + return ctd; + } + + /** + * Create a list of {@code CanonicalTypeDescriptor}s from a list of Java + * type descriptors. + * + * @param types the list of canonical type descriptor + * @param schema the schema specified types are defined in + * + * @return a list of {@code CanonicalTypeDescriptor}s for the specified + * list of Java type descriptors + */ + private static List from(List types, Schema schema) + { + if (types != null) + { + List result = new ArrayList<>(types.size()); + for (JavaTypeDescriptor jtd : types) + { + ExtensibleType type = schema.findTypeByJavaName(jtd.getFullName()); + if (type == null) + { + throw new IllegalStateException("Generic argument type " + + jtd.getFullName() + " is not present in the schema."); + } + result.add(type.getDescriptor()); + } + + return result; + } + + return null; + } + + // ---- AbstractTypeDescriptor implementation --------------------------- + + @Override + protected CanonicalTypeDescriptor createArrayType(String[] namespace, String name) + { + return new CanonicalTypeDescriptor(namespace, name, true); + } + + @Override + protected Parser getParser() + { + return PARSER; + } + + // ---- inner class: CanonicalTypeParser -------------------------------- + + /** + * An implementation of a canonical type name parser. + */ + private static class CanonicalTypeParser extends Parser + { + // ---- constructors ------------------------------------------------ + + /** + * Construct {@code CanonicalTypeParser} instance. + * + * @param separator the namespace separator + */ + public CanonicalTypeParser(String separator) + { + super(separator); + } + + // ---- AbstractTypeDescriptor.Parser implementation ---------------- + + @Override + protected CanonicalTypeDescriptor createTypeDescriptor( + String[] namespace, String name, boolean fArray, + List genericArgs) + { + return new CanonicalTypeDescriptor( + namespace, name, fArray, genericArgs); + } + + @Override + protected CanonicalTypeDescriptor getStandardType(String type) + { + return STANDARD_TYPES.get(type); + } + + // ---- static initializer ------------------------------------------ + + private static final Map STANDARD_TYPES; + static + { + Map map = new HashMap<>(); + + // primitive types + map.put("boolean", BOOLEAN); + map.put("byte", new CanonicalTypeDescriptor("byte")); + map.put("char", new CanonicalTypeDescriptor("char")); + map.put("short", new CanonicalTypeDescriptor("short")); + map.put("int", new CanonicalTypeDescriptor("int")); + map.put("long", new CanonicalTypeDescriptor("long")); + map.put("float", new CanonicalTypeDescriptor("float")); + map.put("double", new CanonicalTypeDescriptor("double")); + + // reference types + map.put("Object", OBJECT); + map.put("String", new CanonicalTypeDescriptor("String")); + map.put("BigDecimal", new CanonicalTypeDescriptor("BigDecimal")); + map.put("BigInteger", new CanonicalTypeDescriptor("BigInteger")); + map.put("DateTime", new CanonicalTypeDescriptor("DateTime")); + + STANDARD_TYPES = map; + } + } + + // ---- static members -------------------------------------------------- + + /** + * A type descriptor for a canonical representation of an Object. + */ + public static final CanonicalTypeDescriptor OBJECT = new CanonicalTypeDescriptor("Object"); + + /** + * A type descriptor for a canonical representation of a Boolean. + */ + public static final CanonicalTypeDescriptor BOOLEAN = new CanonicalTypeDescriptor("boolean"); + + /** + * An instance of a canonical type name parser. + */ + public static final CanonicalTypeParser PARSER = new CanonicalTypeParser("."); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/ClassFileSchemaSource.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/ClassFileSchemaSource.java new file mode 100644 index 0000000000000..566b9686b4530 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/ClassFileSchemaSource.java @@ -0,0 +1,660 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +import com.oracle.coherence.common.base.Classes; +import com.oracle.coherence.common.schema.lang.java.JavaTypeDescriptor; +import com.oracle.coherence.common.schema.util.NameTransformer; +import com.oracle.coherence.common.schema.util.NameTransformerChain; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; + + +/** + * A {@link SchemaSource} implementation that reads type and property metadata + * from a compiled Java class file using ASM. + * + * @author as 2013.06.26 + */ +@SuppressWarnings("unchecked") +public class ClassFileSchemaSource + extends AbstractSchemaSource + { + /** + * The logger object to use. + */ + private static final Logger LOG = Logger.getLogger(ClassFileSchemaSource.class.getName()); + + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@link ClassFileSchemaSource} instance. + */ + public ClassFileSchemaSource() + { + } + + // ---- fluent API ------------------------------------------------------ + + /** + * Add the directory on the file system to read the class files from. + * + * @param directoryName the name of the directory on the file system to + * read the class files from + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withClassesFromDirectory(String directoryName) + { + return withClassesFromDirectory(new File(directoryName)); + } + + /** + * Add the directory on the file system to read the class files from. + * + * @param directory the directory on the file system to read the class + * files from + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withClassesFromDirectory(File directory) + { + m_files.add(directory); + return this; + } + + /** + * Add the JAR file on the file system to read the class files from. + * + * @param jarFileName the name of the JAR file on the file system to read + * the class files from + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withClassesFromJarFile(String jarFileName) + { + return withClassesFromJarFile(new File(jarFileName)); + } + + /** + * Add the JAR file on the file system to read the class files from. + * + * @param jarFile the JAR file on the file system to read the class files + * from + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withClassesFromJarFile(File jarFile) + { + m_files.add(jarFile); + return this; + } + + /** + * Add the individual class file on the file system to import. + * + * @param fileName the name of the class file on the file system to import + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withClassFile(String fileName) + { + return withClassFile(new File(fileName)); + } + + /** + * Add the individual class file on the file system to import. + * + * @param file the class file on the file system to import + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withClassFile(File file) + { + m_files.add(file); + return this; + } + + /** + * Set the filter that should be used to determine whether the type should + * be imported. + * + * @param typeFilter the filter that should be used to determine whether + * the type should be imported + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withTypeFilter(Predicate typeFilter) + { + m_typeFilter = typeFilter; + return this; + } + + /** + * Set the filter that should be used to determine whether the property + * should be imported. + * + * @param propertyFilter the filter that should be used to determine + * whether the type should be imported + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withPropertyFilter(Predicate propertyFilter) + { + m_propertyFilter = m_propertyFilter.and(propertyFilter); + return this; + } + + /** + * Set the {@link NameTransformer} that should be used to transform the + * namespace name. + * + * @param transformer the {@link NameTransformer} that should be used to + * transform the namespace name + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withNamespaceTransformer(NameTransformer transformer) + { + m_namespaceTransformer = transformer; + return this; + } + + /** + * Set the {@link NameTransformer} that should be used to transform the + * class name. + * + * @param transformer the {@link NameTransformer} that should be used to + * transform the class name + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withClassNameTransformer(NameTransformer transformer) + { + m_classNameTransformer = transformer; + return this; + } + + /** + * Set the {@link NameTransformer} that should be used to transform the + * property names. + * + * @param transformer the {@link NameTransformer} that should be used to + * transform the property names + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withPropertyNameTransformer(NameTransformer transformer) + { + m_propertyNameTransformer = transformer; + return this; + } + + /** + * Treat properties that cannot be found in the schema as Object. + * + * @return this {@code ClassFileSchemaSource} + */ + public ClassFileSchemaSource withMissingPropertiesAsObject() + { + m_fMissingPropsAsObject = true; + return this; + } + + // ---- SchemaSource implementation ------------------------------------- + + @Override + public void populateSchema(Schema schema) + { + try + { + // do two passes in order to ensure that all types are defined + // before attempting to resolve base types, property types and interfaces + for (int i = 1; i <= 2; i++) + { + m_nPass = i; + + for (File file : m_files) + { + if (file.isDirectory()) + { + populateSchemaFromDirectory(schema, file); + } + else if (file.getName().endsWith(".jar")) + { + populateSchemaFromJarFile(schema, file); + } + else if (file.getName().endsWith(".class")) + { + populateSchemaFromFile(schema, file); + } + else + { + LOG.finer("Ignoring " + file + ". Unknown file type."); + } + } + } + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + // ---- helper methods -------------------------------------------------- + + /** + * Populate schema by iterating over classes within a file system directory + * and its sub-directories. + * + * @param schema the schema to populate + * @param directory the directory to search for class files + * + * @throws IOException if an error occurs + */ + protected void populateSchemaFromDirectory(Schema schema, File directory) + throws IOException + { + LOG.finer("Populating schema from class files in " + directory); + if (!directory.exists()) + { + throw new IllegalArgumentException( + "Specified path [" + directory.getAbsolutePath() + + "] does not exist"); + } + + File[] files = directory.listFiles(); + if (files != null) + { + for (File file : files) + { + if (isPass(1)) + { + LOG.finer("Processing " + file.getAbsolutePath()); + } + + if (file.isDirectory()) + { + populateSchemaFromDirectory(schema, file); + } + else if (file.getName().endsWith(".class")) + { + populateSchemaFromFile(schema, file); + } + } + } + } + + /** + * Populate schema by iterating over classes within a JAR file. + * + * @param schema the schema to populate + * @param jarFile the JAR file to search for class files + * + * @throws IOException if an error occurs + */ + protected void populateSchemaFromJarFile(Schema schema, File jarFile) + throws IOException + { + LOG.finer("Populating schema from JAR file " + jarFile); + + InputStream jarIn; + if (jarFile.exists()) + { + jarIn = new FileInputStream(jarFile); + } + else + { + jarIn = Classes.getContextClassLoader(this).getResourceAsStream(jarFile.toString()); + } + + if (jarIn != null) + { + try (ZipInputStream zipIn = new ZipInputStream(jarIn)) + { + for (ZipEntry entry = zipIn.getNextEntry(); entry != null; entry = zipIn.getNextEntry()) + { + String name = entry.getName(); + + if (name.endsWith(".class") && !"module-info.class".equals(name)) + { + if (isPass(1)) + { + LOG.finer("Processing " + name); + } + populateSchema(schema, zipIn); + } + } + } + } + else + { + throw new IllegalArgumentException( + "Specified JAR file [" + jarFile.getAbsolutePath() + + "] does not exist"); + } + } + + /** + * Populate schema by reading type information from a specified class file. + * + * @param schema the schema to populate + * @param file the class file to read type information from + * + * @throws IOException if an error occurs + */ + protected void populateSchemaFromFile(Schema schema, File file) + throws IOException + { + if (file.exists()) + { + try (FileInputStream in = new FileInputStream(file)) + { + populateSchema(schema, in); + } + } + else + { + try (InputStream in = Classes.getContextClassLoader(this).getResourceAsStream(file.toString())) + { + if (in != null) + { + populateSchema(schema, in); + } + else if (isPass(1)) + { + LOG.finer("Skipping non-existent file " + file); + } + } + } + } + + /** + * Populate schema by reading type information from a specified input stream + * representing a single class file. + * + * @param schema the schema to populate + * @param in the input stream representing a class file to read type + * information from + * + * @throws IOException if an error occurs + */ + protected void populateSchema(Schema schema, InputStream in) + throws IOException + { + ClassReader reader = new ClassReader(in); + ClassNode source = new ClassNode(); + reader.accept(source, 0); + + if (m_typeFilter.test(source)) + { + JavaTypeDescriptor jtd = JavaTypeDescriptor.fromInternal(source.name); + ExtensibleType type = schema.findTypeByJavaName(jtd.getFullName()); + if (type == null) + { + if (isPass(1)) + { + type = new ExtensibleType(); + CanonicalTypeDescriptor ctd = CanonicalTypeDescriptor.from(jtd, schema); + if (m_namespaceTransformer != null) + { + ctd.setNamespace(m_namespaceTransformer.transform(ctd.getNamespaceComponents())); + } + if (m_classNameTransformer != null) + { + ctd.setName(m_classNameTransformer.transform(ctd.getName())); + } + type.setDescriptor(ctd); + } + else + { + throw new IllegalStateException("Type " + jtd.getFullName() + + " should have been added to the schema during the first pass"); + } + } + populateTypeInternal(schema, type, source); + schema.addType(type); + if (isPass(2)) + { + LOG.finer("Added type " + type.getFullName() + " to the schema"); + } + } + } + + /** + * Return {@code true} if the current pass number is equal to the specified + * expected pass number. + * + * @param nPass the expected pass number + * + * @return {@code true} if the current pass number is equal to the specified + * expected pass number, {@code false} otherwise + */ + protected boolean isPass(int nPass) + { + return m_nPass == nPass; + } + + // ---- AbstractSchemaSource implementation ------------------------------- + + @Override + protected String getPropertyName(FieldNode source) + { + return m_propertyNameTransformer == null + ? source.name + : m_propertyNameTransformer.transform(source.name); + } + + @Override + protected Collection getProperties(ClassNode source) + { + List fields = source.fields; + + return fields.stream() + .filter(m_propertyFilter) + .collect(Collectors.toList()); + } + + // ---- TypeHandler implementation -------------------------------------- + + @Override + public Class getExternalTypeClass() + { + return ClassNode.class; + } + + @Override + public void importType(ExtensibleType type, ClassNode source, Schema schema) + { + if (isPass(2)) + { + JavaTypeDescriptor jtd = JavaTypeDescriptor.fromInternal(source.superName); + if (jtd != JavaTypeDescriptor.OBJECT) + { + //if (schema.findTypeByJavaName(jtd.getFullName()) == null) + // { + // throw new IllegalStateException("Base type " + jtd.getFullName() + // + " for type " + type.getFullName() + // + " is not present in the schema."); + // } + type.setBase(CanonicalTypeDescriptor.from(jtd, schema)); + } + + List interfaces = source.interfaces; + for (String intf : interfaces) + { + jtd = JavaTypeDescriptor.fromInternal(intf); + CanonicalTypeDescriptor ctd = CanonicalTypeDescriptor.from(jtd, schema); + if (schema.containsType(ctd.getFullName())) + { + type.addInterface(ctd); + } + } + } + } + + @Override + public void exportType(ExtensibleType type, ClassNode target, Schema schema) + { + // TODO: generate class based on ExtensibleType + } + + // ---- PropertyHandler implementation ---------------------------------- + + @Override + public Class getExternalPropertyClass() + { + return FieldNode.class; + } + + @Override + public void importProperty(ExtensibleProperty property, FieldNode source, Schema schema) + { + if (isPass(1)) + { + String name = m_propertyNameTransformer == null + ? source.name + : m_propertyNameTransformer.transform(source.name); + property.setName(name); + } + if (isPass(2)) + { + JavaTypeDescriptor jtd = JavaTypeDescriptor.fromInternal( + source.signature != null ? source.signature : source.desc); + + if (schema.findTypeByJavaName(jtd.getFullName()) == null) + { + if (m_fMissingPropsAsObject) + { + jtd = JavaTypeDescriptor.OBJECT; + } + else + { + throw new IllegalStateException("Property type " + jtd.getFullName() + + " is not present in the schema. "); + } + } + + property.setType(CanonicalTypeDescriptor.from(jtd, schema)); + } + } + + @Override + public void exportProperty(ExtensibleProperty property, FieldNode target, Schema schema) + { + // TODO: generate property based on ExtensibleProperty + } + + // ---- Filters inner class --------------------------------------------- + + /** + * Custom filters for type and property filtering. + */ + public static class Filters + { + /** + * Return a filter that evaluates to {@code true} if the class + * implements specified interface. + * + * @param clzInterface the interface to check for + * + * @return {@code true} if the class implements specified interface, + * {@code false} otherwise + */ + public static Predicate implementsInterface(final Class clzInterface) + { + return cn -> cn.interfaces.contains( + org.objectweb.asm.Type.getDescriptor(clzInterface)); + } + + /** + * Return a filter that evaluates to {@code true} if the class + * extends specified parent class. + * + * @param clzParent the parent class to check for + * + * @return {@code true} if the class extends specified parent class, + * {@code false} otherwise + */ + public static Predicate extendsClass(final Class clzParent) + { + return cn -> cn.superName.equals( + org.objectweb.asm.Type.getDescriptor(clzParent)); + } + + /** + * Return a filter that evaluates to {@code true} if the class + * is annotated with a specified annotation. + * + * @param clzAnnotation the annotation class to check for + * + * @return {@code true} if the class is annotated with a specified annotation, + * {@code false} otherwise + */ + public static Predicate hasAnnotation(final Class clzAnnotation) + { + return cn -> + { + if (cn.visibleAnnotations != null) + { + String desc = org.objectweb.asm.Type.getDescriptor(clzAnnotation); + for (AnnotationNode an : (List) cn.visibleAnnotations) + { + if (desc.equals(an.desc)) return true; + } + } + + return false; + }; + } + } + + // ---- Constants ------------------------------------------------------- + + private static final int EXCLUDED_FIELDS = + Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_TRANSIENT; + + // ---- Data members ---------------------------------------------------- + + private Set m_files = new LinkedHashSet<>(); + private Predicate m_typeFilter = t -> true; + private Predicate m_propertyFilter = t -> (t.access & EXCLUDED_FIELDS) == 0; + private boolean m_fMissingPropsAsObject = false; + + // name transformers + private NameTransformer m_namespaceTransformer = + new NameTransformerChain() + .removePrefix("com") + .removePrefix("net") + .removePrefix("org") + .firstLetterToUppercase(); + private NameTransformer m_classNameTransformer = null; + private NameTransformer m_propertyNameTransformer = + new NameTransformerChain() + .removePrefix("m_") + .firstLetterToUppercase(); + + private int m_nPass; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/ExtensibleProperty.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/ExtensibleProperty.java new file mode 100644 index 0000000000000..51f1e2541d230 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/ExtensibleProperty.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + + +/** + * The {@code ExtensibleProperty} implementation. + * + * @author as 2013.06.03 + */ +public class ExtensibleProperty + implements Property + { + // ---- Property implementation ----------------------------------------- + + @Override + public String getName() + { + return m_name; + } + + @Override + public CanonicalTypeDescriptor getType() + { + return m_type; + } + + @Override + public T getExtension(Class clzExtension) + { + return (T) m_extensions.get(clzExtension.getName()); + } + + @Override + public void accept(SchemaVisitor visitor) + { + visitor.visitProperty(this); + for (Property ext : getExtensions()) + { + ext.accept(visitor); + } + } + + // ---- public API ------------------------------------------------------ + + /** + * Set the property name. + * + * @param name the property name + */ + public void setName(String name) + { + m_name = name; + } + + /** + * Set the property type. + * + * @param type the property type + */ + public void setType(CanonicalTypeDescriptor type) + { + m_type = type; + } + + /** + * Add the specified extension to this property. + * + * @param extension the extension to add + */ + public void addExtension(T extension) + { + m_extensions.put(extension.getClass().getName(), extension); + } + + /** + * Return all the extensions this property has. + * + * @return all the extensions this property has + */ + public Collection getExtensions() + { + return Collections.unmodifiableCollection(m_extensions.values()); + } + + // ---- Object methods -------------------------------------------------- + + @Override + public String toString() + { + return "ExtensibleProperty{" + + "name='" + m_name + '\'' + + ", type=" + m_type + + '}'; + } + + // ---- data members ---------------------------------------------------- + + private String m_name; + private CanonicalTypeDescriptor m_type; + + private Map m_extensions = new HashMap<>(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/ExtensibleType.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/ExtensibleType.java new file mode 100644 index 0000000000000..88f65f6280352 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/ExtensibleType.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + + +/** + * The {@code ExtensibleType} implementation. + * + * @author as 2013.06.03 + */ +@SuppressWarnings("unchecked") +public class ExtensibleType + implements Type + { + // ---- Type implementation --------------------------------------------- + + @Override + public String getNamespace() + { + return m_descriptor.getNamespace(); + } + + @Override + public String getName() + { + return m_descriptor.getName(); + } + + @Override + public String getFullName() + { + return m_descriptor.getFullName(); + } + + @Override + public CanonicalTypeDescriptor getDescriptor() + { + return m_descriptor; + } + + @Override + public ExtensibleProperty getProperty(String propertyName) + { + return m_properties.get(propertyName); + } + + @Override + public Collection getProperties() + { + return Collections.unmodifiableCollection(m_properties.values()); + } + + @Override + public T getExtension(Class clzExtension) + { + return (T) m_extensions.get(clzExtension.getName()); + } + + @Override + public void accept(SchemaVisitor visitor) + { + visitor.visitType(this); + for (Type ext : getExtensions()) + { + ext.accept(visitor); + } + for (ExtensibleProperty property : getProperties()) + { + property.accept(visitor); + } + } + + // ---- public API ------------------------------------------------------ + + /** + * Set a descriptor for this type. + * + * @param descriptor a descriptor to set + */ + public void setDescriptor(CanonicalTypeDescriptor descriptor) + { + m_descriptor = descriptor; + } + + /** + * Return the base type (supertype) for this type. + * + * @return the base type (supertype) for this type + */ + public CanonicalTypeDescriptor getBase() + { + return m_base; + } + + /** + * Set the base type (supertype) for this type. + * + * @param base the base type (supertype) for this type + */ + public void setBase(CanonicalTypeDescriptor base) + { + m_base = base; + } + + public boolean isExternal() + { + return m_fExternal; + } + + public void setExternal(boolean fExternal) + { + m_fExternal = fExternal; + } + + /** + * Return a set of interfaces implemented by this type. + * + * @return a set of interfaces implemented by this type + */ + public Set getInterfaces() + { + return Collections.unmodifiableSet(m_interfaces); + } + + /** + * Add the specified interface to this type. + * + * @param descriptor the interface to add + */ + public void addInterface(CanonicalTypeDescriptor descriptor) + { + m_interfaces.add(descriptor); + } + + /** + * Add the specified property to this type. + * + * @param property the property to add + */ + public void addProperty(ExtensibleProperty property) + { + m_properties.put(property.getName(), property); + for (Type ext : m_extensions.values()) + { + if (ext instanceof PropertyAware) + { + ((PropertyAware) ext).propertyAdded(property); + } + } + } + + /** + * Add the specified extension to this type. + * + * @param extension the extension to add + */ + public void addExtension(T extension) + { + m_extensions.put(extension.getClass().getName(), extension); + } + + /** + * Return all the extensions this type has. + * + * @return all the extensions this type has + */ + public Collection getExtensions() + { + return Collections.unmodifiableCollection(m_extensions.values()); + } + + // ---- Object methods -------------------------------------------------- + + @Override + public String toString() + { + return "ExtensibleType{" + + "descriptor=" + m_descriptor + + ", base=" + m_base + + ", external=" + m_fExternal + + ", interfaces=" + m_interfaces + + '}'; + } + + // ---- data members ---------------------------------------------------- + + private CanonicalTypeDescriptor m_descriptor; + private CanonicalTypeDescriptor m_base; + private boolean m_fExternal; + private Set m_interfaces = new LinkedHashSet<>(); + + private Map m_properties = new LinkedHashMap<>(); + private Map m_extensions = new HashMap<>(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/JavaSourceSchemaExporter.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/JavaSourceSchemaExporter.java new file mode 100644 index 0000000000000..eda30f885255e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/JavaSourceSchemaExporter.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +import com.oracle.coherence.common.schema.lang.java.JavaProperty; +import com.oracle.coherence.common.schema.lang.java.JavaType; +import com.oracle.coherence.common.schema.lang.java.JavaTypeDescriptor; +import com.oracle.coherence.common.schema.util.CapitalizationTransformer; +import com.oracle.coherence.common.schema.util.NameTransformer; +import com.oracle.coherence.common.schema.util.StringUtils; +import com.sun.codemodel.JClass; +import com.sun.codemodel.JCodeModel; +import com.sun.codemodel.JDefinedClass; +import com.sun.codemodel.JExpr; +import com.sun.codemodel.JFieldVar; +import com.sun.codemodel.JMethod; +import com.sun.codemodel.JMod; +import com.sun.codemodel.JPackage; +import com.sun.codemodel.JVar; +import com.sun.codemodel.writer.SingleStreamCodeWriter; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + + +/** + * A {@link SchemaExporter} implementation that creates Java source files for + * the types defined in the schema. + * + * @author as 2013.12.02 + */ +@SuppressWarnings("unchecked") +public class JavaSourceSchemaExporter + extends AbstractSchemaExporter + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code JavaSourceSchemaExporter} instance that will write schema + * metadata to the console ({@code System.out}). + */ + public JavaSourceSchemaExporter() + { + this(System.out); + } + + /** + * Construct {@code JavaSourceSchemaExporter} instance. + * + * @param outputStream the output stream to write schema metadata to + */ + public JavaSourceSchemaExporter(OutputStream outputStream) + { + m_outputStream = outputStream; + } + + // ---- SchemaExporter implementation ----------------------------------- + + @Override + public void export(Schema schema) + { + try + { + JCodeModel cm = new JCodeModel(); + + for (ExtensibleType t : schema) + { + if (t.getNamespace() != null) + { + JavaType javaType = t.getExtension(JavaType.class); + System.out.println("Generating " + javaType.getFullName()); + + String[] classNames = StringUtils.split(javaType.getName(), "$"); + + String outerClassName = javaType.getNamespace() + "." + classNames[0]; + JDefinedClass clazz = cm._getClass(outerClassName); + if (clazz == null) + { + clazz = cm._class(outerClassName); + } + for (int i = 1; i < classNames.length; i++) + { + JDefinedClass nestedClass = getNestedClass(clazz, classNames[i]); + if (nestedClass == null) + { + nestedClass = clazz._class(JMod.PUBLIC | JMod.STATIC, classNames[i]); + } + clazz = nestedClass; + } + exportType(t, clazz, schema); + + for (TypeHandler handler : schema.getTypeHandlers(getExternalTypeClass())) + { + Type ext = t.getExtension(handler.getInternalTypeClass()); + handler.exportType(ext, clazz, schema); + } + + for (ExtensibleProperty p : t.getProperties()) + { + JProperty property = JProperty.create(clazz, p, schema); + exportProperty(p, property, schema); + + for (PropertyHandler handler : schema.getPropertyHandlers(getExternalPropertyClass())) + { + Property ext = p.getExtension(handler.getInternalPropertyClass()); + handler.exportProperty(ext, property, schema); + } + } + } + } + + Iterator itp = cm.packages(); + while (itp.hasNext()) + { + JPackage p = itp.next(); + System.out.println("package " + p.name()); + Iterator itc = p.classes(); + while (itc.hasNext()) + { + JDefinedClass c = itc.next(); + System.out.println("\tclass " + c.fullName()); + Iterator itc2 = c.classes(); + while (itc2.hasNext()) + { + JDefinedClass c2 = itc2.next(); + System.out.println("\t\tclass " + c2.fullName()); + } + } + } + cm.build(new SingleStreamCodeWriter(m_outputStream)); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + private JDefinedClass getNestedClass(JDefinedClass outerClass, String className) + { + Iterator it = outerClass.classes(); + while (it.hasNext()) + { + JDefinedClass clazz = it.next(); + if (clazz.name().equals(className)) + { + return clazz; + } + } + + return null; + } + + // ---- TypeHandler implementation -------------------------------------- + + @Override + public Class getExternalTypeClass() + { + return JDefinedClass.class; + } + + @Override + public void exportType(ExtensibleType type, JDefinedClass target, Schema schema) + { + if (type.getBase() != null) + { + JavaType baseType = schema.getType(type.getBase(), JavaType.class); + target._extends(target.owner().ref(baseType.getFullName())); + } + + for (CanonicalTypeDescriptor td : type.getInterfaces()) + { + JavaType intfType = schema.getType(td, JavaType.class); + target._implements(target.owner().ref(intfType.getFullName())); + } + } + + // ---- PropertyHandler implementation ---------------------------------- + + @Override + public Class getExternalPropertyClass() + { + return JProperty.class; + } + + @Override + public void exportProperty(ExtensibleProperty property, JProperty target, Schema schema) + { + } + + // ---- Inner class: JProperty ------------------------------------------ + + public static class JProperty + { + private JProperty(JFieldVar field, JMethod getter, JMethod setter) + { + m_field = field; + m_getter = getter; + m_setter = setter; + } + + public static JProperty create(JDefinedClass clazz, ExtensibleProperty p, Schema schema) + { + String fieldName = FIRST_LOWER.transform(p.getName()); + String getterName = + (CanonicalTypeDescriptor.BOOLEAN.equals(p.getType()) + ? "is" : "get") + FIRST_UPPER.transform(p.getName()); + String setterName = "set" + FIRST_UPPER.transform(p.getName()); + + JavaProperty jp = p.getExtension(JavaProperty.class); + JavaTypeDescriptor jtd = jp.resolveType(schema); + + JClass type = clazz.owner().ref(jtd.getFullName().replace("$", ".")); + if (jtd.isGenericType()) + { + type = type.narrow(resolveGenericParameters(clazz.owner(), jtd.getGenericArguments())); + } + + JFieldVar field = clazz.field(JMod.PRIVATE, type, "m_" + fieldName); + + JMethod getter = clazz.method(JMod.PUBLIC, type, getterName); + getter.body()._return(field); + + JMethod setter = clazz.method(JMod.PUBLIC, void.class, setterName); + JVar setterParam = setter.param(type, fieldName); + setter.body().assign(JExpr._this().ref(field), setterParam); + + return new JProperty(field, getter, setter); + } + + private static List resolveGenericParameters(JCodeModel cm, List genericArguments) + { + List params = new ArrayList<>(genericArguments.size()); + for (JavaTypeDescriptor arg : genericArguments) + { + params.add(cm.ref(arg.getFullName())); + } + return params; + } + + public JFieldVar field() + { + return m_field; + } + + public JMethod getter() + { + return m_getter; + } + + public JMethod setter() + { + return m_setter; + } + + private JFieldVar m_field; + private JMethod m_getter; + private JMethod m_setter; + } + + // ---- static members -------------------------------------------------- + + private static NameTransformer FIRST_LOWER = + new CapitalizationTransformer(CapitalizationTransformer.Mode.FIRST_LOWER); + private static NameTransformer FIRST_UPPER = + new CapitalizationTransformer(CapitalizationTransformer.Mode.FIRST_UPPER); + + // ---- data members ---------------------------------------------------- + + private OutputStream m_outputStream; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/Property.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/Property.java new file mode 100644 index 0000000000000..d41aae8598b38 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/Property.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +/** + * An interface that each property implementation must support. + * + * @param the type of {@link TypeDescriptor} for this property + * + * @author as 2013.07.11 + */ +public interface Property + { + /** + * Return the property name. + * + * @return the property name + */ + String getName(); + + /** + * Return the property type. + * + * @return the property type + */ + D getType(); + + /** + * Return the specified extension for this property. + * + * @param clzExtension the type of the extension to return + * + * @return the specified extension + */ + T getExtension(Class clzExtension); + + // ---- Visitor pattern ------------------------------------------------ + + /** + * An acceptor for the {@link SchemaVisitor}. + * + * @param visitor the visitor + */ + void accept(SchemaVisitor visitor); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/PropertyAware.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/PropertyAware.java new file mode 100644 index 0000000000000..e923095e440aa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/PropertyAware.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +/** + * An interface that can be implemented by {@link Type} implementations in + * order to receive notifications when a property is added to the parent + * {@link ExtensibleType}. + * + * @author as 2013.11.18 + */ +public interface PropertyAware + { + /** + * The method that will be called when a property is added to the parent + * {@link ExtensibleType}. + * + * @param property the property that was added to the extensible type + */ + void propertyAdded(ExtensibleProperty property); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/PropertyHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/PropertyHandler.java new file mode 100644 index 0000000000000..c8353dc077c05 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/PropertyHandler.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +/** + * An interface that all property handlers must implement. + * + * @param the internal representation of property metadata + * @param the external representation of property metadata + * + * @author as 2013.06.26 + */ +public interface PropertyHandler + { + /** + * Return the class of the internal property representation this property + * handler understands. + * + * @return the class of the internal property representation this property + * handler understands + */ + Class getInternalPropertyClass(); + + /** + * Return the class of the external property representation this property + * handler understands. + * + * @return the class of the external property representation this property + * handler understands + */ + Class getExternalPropertyClass(); + + /** + * Create the internal property representation and associate it with the + * specified extensible property. + * + * @param parent the extensible property created property should be + * associated with + * + * @return the internal property representation + */ + TInternal createProperty(ExtensibleProperty parent); + + /** + * Import specified property into the schema. + * + * @param property the internal property to populate and import + * @param source the external source to read property metadata from + * @param schema the schema to import property into + */ + void importProperty(TInternal property, TExternal source, Schema schema); + + /** + * Export specified property from the schema. + * + * @param property the internal property to export + * @param target the external target to populate based on the internal + * property metadata + * @param schema the schema to export property from + */ + void exportProperty(TInternal property, TExternal target, Schema schema); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/Schema.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/Schema.java new file mode 100644 index 0000000000000..201b850d4e324 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/Schema.java @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +import com.oracle.coherence.common.schema.lang.cpp.CppExtension; +import com.oracle.coherence.common.schema.lang.dotnet.DotNetExtension; +import com.oracle.coherence.common.schema.lang.java.JavaExtension; +import com.oracle.coherence.common.schema.lang.java.JavaType; +import com.oracle.coherence.common.schema.util.ResourceLoader; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; + +import static com.oracle.coherence.common.base.Classes.getContextClassLoader; + + +/** + * Represents an instance of a {@code Schema} containing a collection of + * {@link ExtensibleType}s. + *

+ * Most applications will typically use only one instance of a schema, but it is + * possible to create multiple schema instances, where each schema instance + * defines an independent type system. + *

+ * {@code Schema} instances are created using a {@link SchemaBuilder} configured + * with a set of {@link SchemaSource}s to use. For example, the Maven plugin + * used to add serialization code to classes marked with PortableType annotation defines and + * populates the schema as follows: + *

+ * Schema schema = new SchemaBuilder()
+ *         .addSchemaSource(
+ *                 new ClassFileSchemaSource()
+ *                         .withClassesFromDirectory(outputDirectory)
+ *                         .withTypeFilter(hasAnnotation(PortableType.class))
+ *         )
+ *         .build();
+ * 
+ * In his example, we are only interested in compiled classes from the output + * directory that are annotated with {@code @PortableType} annotation, so we use + * {@link ClassFileSchemaSource} and the appropriate type filter to select only + * those classes for inclusion into the schema. + *

+ * The {@code Schema} is extensible, meaning that each type within a schema + * can be decorated with additional metadata using {@link SchemaExtension}s. + *

+ * There are three built-in schema extensions: {@link JavaExtension}, {@link + * DotNetExtension} and {@link CppExtension}, which add the metadata necessary + * to represent schema types in Java, .NET and C++, and users are free to + * implement their own extensions in order to add the metadata they need. + *

+ * For example, Coherence provides a custom com.oracle.common.io.pof.schema.PofExtension, + * which adds the metadata necessary for POF serialization to a subset of types + * within the schema. + * + * @author as 2013.06.21 + * + * @see SchemaBuilder + * @see SchemaSource + * @see ExtensibleType + * @see SchemaExtension + */ +public class Schema implements Iterable + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@code Schema} instance. + *

+ * This constructor will automatically register all of the built-in extensions + * ({@link JavaExtension}, {@link DotNetExtension} and {@link CppExtension}), + * as well as all {@link SchemaExtension} implementations that can be found + * in the classpath using Java {@link ServiceLoader} mechanism (which, in the + * case of Coherence, includes com.oracle.common.io.pof.schema.PofExtension). + * + * @param typeMap the map that should be used to store {@link ExtensibleType}s. + * If {@code null}, the {@code LinkedHashMap} will be used. + */ + protected Schema(Map typeMap) + { + m_typeMap = typeMap == null + ? new LinkedHashMap<>() + : typeMap; + + // register built-in extensions + registerExtension(new JavaExtension()); + registerExtension(new DotNetExtension()); + registerExtension(new CppExtension()); + + // register available custom extensions from the classpath + ServiceLoader loader = + ServiceLoader.load(SchemaExtension.class, getContextClassLoader()); + for (SchemaExtension ext : loader) + { + registerExtension(ext); + } + + // load all META-INF/schema.xml resources from the classpath + ResourceLoader resourceLoader = + ResourceLoader.load("META-INF/schema.xml", getContextClassLoader()); + for (InputStream in : resourceLoader) + { + XmlSchemaSource ts = new XmlSchemaSource(in); + ts.populateSchema(this); + } + } + + // ---- Visitor pattern implementation ---------------------------------- + + /** + * An acceptor for the {@link SchemaVisitor}. + * + * @param visitor the visitor + */ + public void accept(SchemaVisitor visitor) + { + for (Type type : this) + { + type.accept(visitor); + } + } + + // ---- Iterable implementation ----------------------------------------- + + /** + * Return an iterator over {@code ExtensibleType}s contained in this schema. + * + * @return an Iterator over {@code ExtensibleType}s contained in this schema + */ + public Iterator iterator() + { + return Collections.unmodifiableCollection(m_typeMap.values()).iterator(); + } + + // ---- public methods -------------------------------------------------- + + /** + * Get {@code ExtensibleType} for the specified type descriptor. + * + * @param td a canonical type descriptor + * + * @return the {@code ExtensibleType} associated with the specified type + * descriptor, or {@code null} if the type for the specified type + * descriptor does not exist in this schema + */ + public ExtensibleType getType(CanonicalTypeDescriptor td) + { + return getType(td.getFullName()); + } + + /** + * Get {@code ExtensibleType} for the specified canonical type name. + * + * @param fullName a full, canonical type name + * + * @return the {@code ExtensibleType} associated with the specified type + * name, or {@code null} if the type for the specified type + * name does not exist in this schema + */ + public ExtensibleType getType(String fullName) + { + return m_typeMap.get(fullName); + } + + /** + * Get {@code Type} extension for the specified type descriptor and + * extension class. + * + * @param td a canonical type descriptor + * @param clazz the class of the type extension to return + * + * @return the {@code Type} extension associated with the specified type + * descriptor; {@code null} if the type for the specified type + * descriptor, or the extension for the specified class does not + * exist in this schema + */ + public T getType(CanonicalTypeDescriptor td, Class clazz) + { + return getType(td.getFullName(), clazz); + } + + /** + * Get {@code Type} extension for the specified canonical type name and + * extension class. + * + * @param fullName a full, canonical type name + * @param clazz the class of the type extension to return + * + * @return the {@code Type} extension associated with the specified type + * descriptor; {@code null} if the type for the specified type + * descriptor, or the extension for the specified class does not + * exist in this schema + */ + public T getType(String fullName, Class clazz) + { + return m_typeMap.get(fullName).getExtension(clazz); + } + + /** + * Return {@code true} if the type associated with the specified type + * descriptor exists in this schema. + * + * @param td a canonical type descriptor + * + * @return {@code true} if the type associated with the specified type + * descriptor exists in this schema, {@code false} otherwise + */ + public boolean containsType(CanonicalTypeDescriptor td) + { + return containsType(td.getFullName()); + } + + /** + * Return {@code true} if the type associated with the specified canonical + * type name exists in this schema. + * + * @param fullName a full, canonical type name + * + * @return {@code true} if the type associated with the specified canonical + * type name exists in this schema, {@code false} otherwise + */ + public boolean containsType(String fullName) + { + return m_typeMap.containsKey(fullName); + } + + /** + * Add type to this schema. + * + * @param type the type to add + */ + public void addType(ExtensibleType type) + { + m_typeMap.put(type.getFullName(), type); + } + + /** + * A convenience method to find {@code ExtensibleType} based on its Java + * type name + * + * @param name a fully-qualified Java type name + * + * @return the {@code ExtensibleType} associated with the specified Java type + * name, or {@code null} if the type for the specified Java type + * name does not exist in this schema + */ + public ExtensibleType findTypeByJavaName(String name) + { + for (ExtensibleType type : m_typeMap.values()) + { + JavaType javaType = type.getExtension(JavaType.class); + if (javaType != null + && (name.equals(javaType.getFullName()) + || (javaType.getWrapperType() != null && name.equals(javaType.getWrapperType().getFullName())) + || javaType.implementsInterface(name))) + { + return type; + } + } + + return null; + } + + // ---- helper methods -------------------------------------------------- + + /** + * Register schema extension with this schema. + * + * @param extension the schema extension to register + */ + protected synchronized void registerExtension(SchemaExtension extension) + { + m_extensions.add(extension.getName()); + + extension.getTypeHandlers().forEach(this::registerTypeHandler); + extension.getPropertyHandlers().forEach(this::registerPropertyHandler); + } + + /** + * Register type handler with this schema. + * + * @param handler the type handler to register + */ + protected synchronized void registerTypeHandler(TypeHandler handler) + { + final String name = handler.getExternalTypeClass().getName(); + m_typeHandlers.computeIfAbsent(name, k -> new HashSet<>()).add(handler); + } + + /** + * Register property handler with this schema. + * + * @param handler the property handler to register + */ + protected synchronized void registerPropertyHandler(PropertyHandler handler) + { + final String name = handler.getExternalPropertyClass().getName(); + m_propertyHandlers.computeIfAbsent(name, k -> new HashSet<>()).add(handler); + } + + /** + * Return the type handlers for the specified external class. + * + * @param external the external class to get the type handlers for + * + * @return the type handlers for the specified external class + */ + public Set getTypeHandlers(Class external) + { + return m_typeHandlers.getOrDefault(external.getName(), Collections.emptySet()); + } + + /** + * Return the property handlers for the specified external class. + * + * @param external the external class to get the property handlers for + * + * @return the property handlers for the specified external class + */ + public Set getPropertyHandlers(Class external) + { + return m_propertyHandlers.getOrDefault(external.getName(), Collections.emptySet()); + } + + // ---- data members ---------------------------------------------------- + + /** + * The map of extensible types within this schema, keyed by full canonical + * type name. + */ + protected Map m_typeMap; + + /** + * The set of schema extensions registered with this schema. + */ + protected Set m_extensions = new HashSet<>(); + + /** + * The map of type handlers provided by the registered schema extensions, + * keyed by external type name. + */ + protected Map> m_typeHandlers = new LinkedHashMap<>(); + + /** + * The map of propery handlers provided by the registered schema extensions, + * keyed by external property type name. + */ + protected Map> m_propertyHandlers = new LinkedHashMap<>(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaBuilder.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaBuilder.java new file mode 100644 index 0000000000000..87342c5a60ab3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaBuilder.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + + +/** + * The {@code SchemaBuilder} is responsible for creating and populating a + * {@link Schema} instance from a set of configured {@link SchemaSource}s. + *

+ * By default, the {@code SchemaBuilder} will look for the {@code SchemaSource}s + * available in the classpath using Java {@code ServiceLoader} mechanism, but + * more often than not, you will want to register {@code SchemaSource}s + * explicitly by calling {@link #addSchemaSource(SchemaSource)} method directly. + *

+ * For example, the Maven plugin used to add serialization code to classes + * marked with {@link com.oracle.common.io.pof.schema.annotation.PortableType} + * annotation defines schema as follows: + *

+ * Schema schema = new SchemaBuilder()
+ *         .addSchemaSource(
+ *                 new ClassFileSchemaSource()
+ *                         .withClassesFromDirectory(outputDirectory)
+ *                         .withTypeFilter(hasAnnotation(PortableType.class))
+ *         )
+ *         .build();
+ * 
+ * In his example, we are only interested in compiled classes from the output + * directory that are annotated with {@code @PortableType} annotation, so we use + * {@link ClassFileSchemaSource} and the appropriate type filter to select only + * those classes for inclusion into the schema. + * + * @author as 2013.07.08 + * + * @see Schema + * @see SchemaSource + */ +public class SchemaBuilder + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@code SchemaBuilder} instance. + *

+ * This constructor will automatically register all {@link SchemaSource} + * implementations that can be found in the classpath using Java {@link + * ServiceLoader} mechanism. + */ + public SchemaBuilder() + { + List schemaSources = new ArrayList<>(); + + // load available schema sources from the classpath + ServiceLoader loader = + ServiceLoader.load(SchemaSource.class, getClass().getClassLoader()); + for (SchemaSource source : loader) + { + schemaSources.add(source); + } + + m_schemaSources = schemaSources; + } + + // ---- fluent API ------------------------------------------------------ + + /** + * Add {@link SchemaSource} to this {@code SchemaBuilder}. + * + * @param schemaSource the {@link SchemaSource} to add + * + * @return this {@code SchemaBuilder} + */ + public SchemaBuilder addSchemaSource(SchemaSource schemaSource) + { + m_schemaSources.add(schemaSource); + return this; + } + + /** + * Set the {@link ExtensibleType} store that the {@link Schema} created by + * this {@code SchemaBuilder} should use. + * + * @param store the {@link ExtensibleType} store that the {@link Schema} + * created by this {@code SchemaBuilder} should use + * + * @return this {@code SchemaBuilder} + */ + public SchemaBuilder setStore(Map store) + { + m_store = store; + return this; + } + + /** + * Create {@link Schema} instance + * + * @return the created {@code Schema} instance + */ + public Schema build() + { + Schema schema = new Schema(m_store); + for (SchemaSource ts : m_schemaSources) + { + ts.populateSchema(schema); + } + + return schema; + } + + // ---- data members ---------------------------------------------------- + + /** + * The list of schema sources to use. + */ + private List m_schemaSources; + + /** + * The {@link ExtensibleType} store that the {@link Schema} created by this + * {@code SchemaBuilder} should use. + */ + private Map m_store; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaExporter.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaExporter.java new file mode 100644 index 0000000000000..015bff4879f1b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaExporter.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +/** + * Defines an interface that must be implemented by all schema exporters. + * + * @param the type of the external representation of the types + * handled by this schema exporter + * @param the type of the external representation of the properties + * handled by this schema exporter + * + * @author as 2013.11.21 + */ +public interface SchemaExporter + extends TypeHandler, PropertyHandler + { + /** + * Export specified schema. + * + * @param schema the schema to export + */ + void export(Schema schema); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaExtension.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaExtension.java new file mode 100644 index 0000000000000..91b2b35f298ce --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaExtension.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.util.Collection; + + +/** + * An interface that must be implemented by the custom schema extensions. + * + * @author as 2013.08.28 + */ +public interface SchemaExtension + { + /** + * Return the name of this schema extension. + * + * @return the name of this schema extension + */ + String getName(); + + /** + * Return a collection of type handlers defined by this schema extension. + * + * @return a collection of type handlers defined by this schema extension + */ + Collection getTypeHandlers(); + + /** + * Return a collection of property handlers defined by this schema extension. + * + * @return a collection of property handlers defined by this schema extension + */ + Collection getPropertyHandlers(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaSource.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaSource.java new file mode 100644 index 0000000000000..e01506e06d3e5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaSource.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +/** + * Defines an interface that must be implemented by all schema sources. + * + * @param the type of the external representation of the types + * handled by this schema source + * @param the type of the external representation of the properties + * handled by this schema source + * + * @author as 2013.06.26 + */ +public interface SchemaSource + extends TypeHandler, PropertyHandler + { + /** + * Populate specified {@link Schema} with the types discoverable by this + * schema source. + * + * @param schema the schema to populate + */ + void populateSchema(Schema schema); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaVisitor.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaVisitor.java new file mode 100644 index 0000000000000..5b271d44dc7e0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/SchemaVisitor.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +/** + * Defines an interface that a visitor passed to {@link Schema#accept(SchemaVisitor)} + * method has to implement. + * + * @author as 2013.07.25 + */ +public interface SchemaVisitor + { + /** + * A method that will be called once for each {@link ExtensibleType} within + * the {@link Schema} this visitor is passed to. + * + * @param type the {@code ExtensibleType} to visit + */ + void visitType(ExtensibleType type); + + /** + * A method that will be called once for each type extension added to an + * {@link ExtensibleType}. + * + * @param type the type extension to visit + */ + void visitType(Type type); + + /** + * A method that will be called once for each property of an {@link ExtensibleType}. + * + * @param property the {@code ExtensibleProperty} to visit + */ + void visitProperty(ExtensibleProperty property); + + /** + * A method that will be called once for each property extension added to an + * {@link ExtensibleProperty}. + * + * @param property the property extension to visit + */ + void visitProperty(Property property); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/Type.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/Type.java new file mode 100644 index 0000000000000..27931049cc3a3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/Type.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.util.Collection; + + +/** + * An interface that each type implementation must support. + * + * @param

the type of {@link Property}s for this type + * @param the type of {@link TypeDescriptor} for this type + * + * @author as 2013.07.03 + */ +public interface Type

+ { + /** + * Return the type namespace. + * + * @return the type namespace + */ + String getNamespace(); + + /** + * Return the type name. + * + * @return the type name + */ + String getName(); + + /** + * Return the fully qualified type name. + * + * @return the fully qualified type name + */ + String getFullName(); + + /** + * Return the descriptor for this type. + * + * @return the descriptor for this type + */ + D getDescriptor(); + + /** + * Return the property with a specified name. + * + * @param propertyName the name of the property to return + * + * @return the property with a specified name + */ + P getProperty(String propertyName); + + /** + * Return a collection of all properties within this type. + * + * @return a collection of all properties within this type + */ + Collection

getProperties(); + + /** + * Return the specified extension for this type. + * + * @param extensionType the type of the extension to return + * + * @return the specified extension + */ + T getExtension(Class extensionType); + + // ---- Visitor pattern ------------------------------------------------ + + /** + * An acceptor for the {@link SchemaVisitor}. + * + * @param visitor the visitor + */ + void accept(SchemaVisitor visitor); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/TypeDescriptor.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/TypeDescriptor.java new file mode 100644 index 0000000000000..af6fbc534d0e2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/TypeDescriptor.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +import java.util.List; + + +/** + * An interface that all type descriptors must implement. + * + * @author as 2013.08.26 + */ +public interface TypeDescriptor + { + /** + * Return the type namespace. + * + * @return the type namespace + */ + String getNamespace(); + + /** + * Return the type name. + * + * @return the type name + */ + String getName(); + + /** + * Return the fully qualified type name. + * + * @return the fully qualified type name + */ + String getFullName(); + + /** + * Return {@code true} if this type descriptor represents an array type. + * + * @return {@code true} if this type descriptor represents an array type, + * {@code false} otherwise + */ + boolean isArray(); + + /** + * Return {@code true} if this type descriptor represents a generic type. + * + * @return {@code true} if this type descriptor represents a generic type, + * {@code false} otherwise + */ + boolean isGenericType(); + + /** + * Return a list of type descriptors for the generic arguments. + * + * @return a list of type descriptors for the generic arguments + */ + List getGenericArguments(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/TypeHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/TypeHandler.java new file mode 100644 index 0000000000000..efbb80b44d914 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/TypeHandler.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + +/** + * An interface that all type handlers must implement. + * + * @param the internal representation of type metadata + * @param the external representation of type metadata + * + * @author as 2013.06.26 + */ +public interface TypeHandler + { + /** + * Return the class of the internal type representation this type handler + * understands. + * + * @return the class of the internal type representation this type handler + * understands + */ + Class getInternalTypeClass(); + + /** + * Return the class of the external type representation this type handler + * understands. + * + * @return the class of the external type representation this type handler + * understands + */ + Class getExternalTypeClass(); + + /** + * Create the internal type representation and associate it with the + * specified extensible type. + * + * @param parent the extensible type created type should be associated with + * + * @return the internal type representation + */ + TInternal createType(ExtensibleType parent); + + /** + * Import specified type into the schema. + * + * @param type the internal type to populate and import + * @param source the external source to read type metadata from + * @param schema the schema to import type into + */ + void importType(TInternal type, TExternal source, Schema schema); + + /** + * Export specified type from the schema. + * + * @param type the internal type to export + * @param target the external target to populate based on the internal + * type metadata + * @param schema the schema to export type from + */ + void exportType(TInternal type, TExternal target, Schema schema); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/XmlSchemaExporter.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/XmlSchemaExporter.java new file mode 100644 index 0000000000000..66ad7b8a2a32a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/XmlSchemaExporter.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +import java.io.OutputStream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + +/** + * A {@link SchemaExporter} implementation that writes type and property metadata + * to an XML file. + * + * @author as 2013.11.21 + */ +@SuppressWarnings("unchecked") +public class XmlSchemaExporter + extends AbstractSchemaExporter + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code XmlSchemaExporter} instance that will write schema + * metadata to the console ({@code System.out}). + */ + public XmlSchemaExporter() + { + this(System.out); + } + + /** + * Construct {@code XmlSchemaExporter} instance. + * + * @param outputStream the output XML stream to write schema metadata to + */ + public XmlSchemaExporter(OutputStream outputStream) + { + m_outputStream = outputStream; + } + + // ---- SchemaExporter implementation ----------------------------------- + + @Override + public void export(Schema schema) + { + try + { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + DocumentBuilder db = dbf.newDocumentBuilder(); + + Document doc = db.newDocument(); + Element root = doc.createElementNS(NS, "schema"); + doc.appendChild(root); + + for (ExtensibleType t : schema) + { + Element type = doc.createElementNS(NS, "type"); + exportType(t, type, schema); + + for (TypeHandler handler : schema.getTypeHandlers(getExternalTypeClass())) + { + Type ext = t.getExtension(handler.getInternalTypeClass()); + handler.exportType(ext, type, schema); + } + + for (ExtensibleProperty p : t.getProperties()) + { + Element property = doc.createElementNS(NS, "property"); + exportProperty(p, property, schema); + + for (PropertyHandler handler : schema.getPropertyHandlers(getExternalPropertyClass())) + { + Property ext = p.getExtension(handler.getInternalPropertyClass()); + handler.exportProperty(ext, property, schema); + } + + type.appendChild(property); + } + + root.appendChild(type); + } + + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount","2"); + + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(m_outputStream); + transformer.transform(source, result); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + // ---- TypeHandler implementation -------------------------------------- + + @Override + public Class getExternalTypeClass() + { + return Element.class; + } + + @Override + public void exportType(ExtensibleType type, Element target, Schema schema) + { + target.setAttribute("name", type.getFullName()); + } + + // ---- PropertyHandler implementation ---------------------------------- + + @Override + public Class getExternalPropertyClass() + { + return Element.class; + } + + @Override + public void exportProperty(ExtensibleProperty property, Element target, Schema schema) + { + target.setAttribute("name", property.getName()); + target.setAttribute("type", property.getType().toString()); + } + + // ---- static members -------------------------------------------------- + + private static final String NS = "http://xmlns.oracle.com/coherence/schema"; + + // ---- data members ---------------------------------------------------- + + private OutputStream m_outputStream; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/XmlSchemaSource.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/XmlSchemaSource.java new file mode 100644 index 0000000000000..dbee2c9505a65 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/XmlSchemaSource.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema; + + +import com.oracle.coherence.common.base.Classes; +import com.oracle.coherence.common.schema.util.StringUtils; +import com.oracle.coherence.common.schema.util.XmlUtils; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + +/** + * A {@link SchemaSource} implementation that reads type and property metadata + * from an XML file. + * + * @author as 2013.07.11 + */ +public class XmlSchemaSource + extends AbstractSchemaSource + { + // ---- Constructors ---------------------------------------------------- + + /** + * Construct {@code XmlSchemaSource} instance. + * + * @param fileName the name of the file or classpath resource to populate + * the schema from + * + * @throws IllegalArgumentException if the specified XML file cannot be found + */ + public XmlSchemaSource(String fileName) + { + File file = new File(fileName); + try + { + m_inputXml = file.exists() + ? new FileInputStream(file) + : Classes.getContextClassLoader().getResourceAsStream(fileName); + } + catch (FileNotFoundException ignore) + { + // should never happen, as we check for the file existence first, + // but even if it does it will be handled below + } + + if (m_inputXml == null) + { + throw new IllegalArgumentException( + String.format("The specified XML file %s cannot be found", fileName)); + } + } + + /** + * Construct {@code XmlSchemaSource} instance. + * + * @param inputXml the input XML stream to populate the schema from + */ + public XmlSchemaSource(InputStream inputXml) + { + m_inputXml = inputXml; + } + + // ---- SchemaSource implementation --------------------------------------- + + @Override + public void populateSchema(Schema schema) + { + try + { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(m_inputXml); + Element root = doc.getDocumentElement(); + boolean fExternal = XmlUtils.getBooleanAttribute(root, "external"); + List types = XmlUtils.toElementList(root.getElementsByTagName("type")); + + for (Element t : types) + { + String fullName = CanonicalTypeDescriptor.parse(t.getAttribute("name")).getFullName(); + ExtensibleType type = populateTypeInternal(schema, schema.getType(fullName), t); + type.setExternal(fExternal); + schema.addType(type); + } + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + // ---- AbstractSchemaSource implementation ------------------------------- + + @Override + protected String getPropertyName(Element source) + { + return source.getAttribute("name"); + } + + @Override + protected Collection getProperties(Element source) + { + return XmlUtils.toElementList(source.getElementsByTagName("property")); + } + + // ---- TypeHandler implementation -------------------------------------- + + @Override + public Class getExternalTypeClass() + { + return Element.class; + } + + @Override + public void importType(ExtensibleType type, Element source, Schema schema) + { + String name = source.getAttribute("name"); + type.setDescriptor(CanonicalTypeDescriptor.parse(name)); + + // optional attributes + String base = source.getAttribute("base"); + if (!StringUtils.isEmpty(base)) + { + type.setBase(CanonicalTypeDescriptor.parse(base)); + } + + if (source.hasAttribute("external")) + { + boolean fExternal = Boolean.parseBoolean(source.getAttribute("external")); + if (!StringUtils.isEmpty(base)) + { + type.setExternal(fExternal); + } + } + + List interfaces = XmlUtils.toElementList(source.getElementsByTagName("interface")); + for (Element e : interfaces) + { + name = e.getAttribute("name"); + type.addInterface(CanonicalTypeDescriptor.parse(name)); + } + } + + @Override + public void exportType(ExtensibleType type, Element target, Schema schema) + { + } + + // ---- PropertyHandler implementation ---------------------------------- + + @Override + public Class getExternalPropertyClass() + { + return Element.class; + } + + @Override + public void importProperty(ExtensibleProperty property, Element source, Schema schema) + { + property.setName(source.getAttribute("name")); + property.setType(CanonicalTypeDescriptor.parse(source.getAttribute("type"))); + } + + @Override + public void exportProperty(ExtensibleProperty property, Element target, Schema schema) + { + } + + // ---- data members ---------------------------------------------------- + + protected InputStream m_inputXml; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/AbstractLangProperty.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/AbstractLangProperty.java new file mode 100644 index 0000000000000..33bffea14f755 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/AbstractLangProperty.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang; + +import com.oracle.coherence.common.schema.AbstractProperty; +import com.oracle.coherence.common.schema.ExtensibleProperty; +import com.oracle.coherence.common.schema.TypeDescriptor; + + +/** + * An abstract base class for language-specific property extension + * implementations. + * + * @author as 2013.11.20 + */ +public abstract class AbstractLangProperty + extends AbstractProperty + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct an {@code AbstractLangProperty} instance. + * + * @param parent the parent {@code ExtensibleProperty} instance + */ + protected AbstractLangProperty(ExtensibleProperty parent) + { + super(parent); + } + + // ---- Property implementation ----------------------------------------- + + @Override + public TD getType() + { + return m_type; + } + + // ---- public API ------------------------------------------------------ + + /** + * Set the property type. + * + * @param type the property type + */ + public void setType(TD type) + { + m_type = type; + } + + // ---- data members ---------------------------------------------------- + + /** + * The property type. + */ + private TD m_type; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/AbstractLangType.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/AbstractLangType.java new file mode 100644 index 0000000000000..d1a194d016f1c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/AbstractLangType.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang; + + +import com.oracle.coherence.common.schema.AbstractType; +import com.oracle.coherence.common.schema.ExtensibleType; +import com.oracle.coherence.common.schema.Property; +import com.oracle.coherence.common.schema.TypeDescriptor; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + + +/** + * An abstract base class for language-specific type extension implementations. + * + * @author as 2013.11.20 + */ +public abstract class AbstractLangType

+ extends AbstractType + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct an {@code AbstractLangType} instance. + * + * @param parent the parent {@code ExtensibleType} instance + */ + protected AbstractLangType(ExtensibleType parent) + { + super(parent); + } + + // ---- Type implementation --------------------------------------------- + + @Override + public TD getDescriptor() + { + return m_descriptor; + } + + // ---- public API ------------------------------------------------------ + + /** + * Set type descriptor for this type. + * + * @param descriptor the type descriptor + */ + public void setDescriptor(TD descriptor) + { + m_descriptor = descriptor; + } + + /** + * Return the aliases for this type. + * + * @return the aliases for this type + */ + public Set getAliases() + { + return Collections.unmodifiableSet(m_aliases); + } + + /** + * Add an alias for this type. + * + * @param descriptor the alias type descriptor + */ + public void addAlias(TD descriptor) + { + m_aliases.add(descriptor); + } + + // ---- data members ---------------------------------------------------- + + /** + * The type descriptor for this type. + */ + private TD m_descriptor; + + /** + * A set of aliases for this type. + */ + private Set m_aliases = new LinkedHashSet<>(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/ClassFilePropertyHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/ClassFilePropertyHandler.java new file mode 100644 index 0000000000000..3e12b2d3e87ba --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/ClassFilePropertyHandler.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang; + + +import com.oracle.coherence.common.schema.AbstractPropertyHandler; +import com.oracle.coherence.common.schema.PropertyHandler; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.TypeDescriptor; +import com.oracle.coherence.common.schema.util.AsmUtils; +import com.oracle.coherence.common.schema.util.StringUtils; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.Arrays; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.FieldNode; + + +/** + * A base class for {@link PropertyHandler} implementations that read and write + * property metadata from/to Java class file. + * + * @param the type of property class that is handled by this {@link + * PropertyHandler} + * @param the type of type descriptor used to represent property type + * @param the annotation type that can be used to provide hints to this + * {@link PropertyHandler} + * + * @author as 2013.11.21 + */ +@SuppressWarnings("unchecked") +public class ClassFilePropertyHandler + < + T extends AbstractLangProperty, + TD extends TypeDescriptor, + A extends Annotation + > + extends AbstractPropertyHandler + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@code ClassFilePropertyHandler} instance. + */ + public ClassFilePropertyHandler() + { + java.lang.reflect.Type superclass = getClass().getGenericSuperclass(); + m_typeDescriptorClass = (Class) + ((ParameterizedType) superclass).getActualTypeArguments()[1]; + m_annotationClass = (Class) + ((ParameterizedType) superclass).getActualTypeArguments()[2]; + } + + // ---- PropertyHandler implementation ---------------------------------- + + @Override + public void importProperty(T property, FieldNode source, Schema schema) + { + AnnotationNode an = AsmUtils.getAnnotation(source, m_annotationClass); + if (an != null) + { + String name = (String) AsmUtils.getAnnotationAttribute(an, "type"); + if (!StringUtils.isEmpty(name)) + { + property.setType(parseTypeName(name)); + } + } + } + + @Override + public void exportProperty(T property, FieldNode target, Schema schema) + { + if (property.getType() != null) + { + AnnotationNode an = new AnnotationNode(Type.getDescriptor(m_annotationClass)); + an.values = Arrays.asList("type", property.getType().getFullName()); + + AsmUtils.addAnnotation(target, an); + } + } + + // ---- helper methods -------------------------------------------------- + + /** + * Parse specified platform-specific type name and return the + * {@link TypeDescriptor} that corresponds to it. + * + * @param name the type name to parse + * + * @return the {@link TypeDescriptor} for the specified type name + */ + protected TD parseTypeName(String name) + { + try + { + if (m_parseMethod == null) + { + m_parseMethod = m_typeDescriptorClass.getMethod("parse", String.class); + } + return (TD) m_parseMethod.invoke(m_typeDescriptorClass, name); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + // ---- data members ---------------------------------------------------- + + private final Class m_typeDescriptorClass; + private final Class m_annotationClass; + private Method m_parseMethod; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/ClassFileTypeHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/ClassFileTypeHandler.java new file mode 100644 index 0000000000000..fc729e35eaf67 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/ClassFileTypeHandler.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang; + + +import com.oracle.coherence.common.schema.AbstractTypeHandler; +import com.oracle.coherence.common.schema.Property; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.TypeDescriptor; +import com.oracle.coherence.common.schema.TypeHandler; +import com.oracle.coherence.common.schema.util.AsmUtils; +import com.oracle.coherence.common.schema.util.StringUtils; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.Arrays; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; + + +/** + * A base class for {@link TypeHandler} implementations that read and write + * type metadata from/to Java class file. + * + * @param the type of the {@link com.oracle.coherence.common.schema.Type} class + * that is handled by this {@link TypeHandler} + * @param the type of the type descriptor used by the type {@code } + * @param the annotation type that can be used to provide hints to this + * {@link TypeHandler} + * + * @author as 2013.11.21 + */ +@SuppressWarnings("unchecked") +public class ClassFileTypeHandler + < + T extends AbstractLangType, + TD extends TypeDescriptor, + A extends Annotation + > + extends AbstractTypeHandler + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@code ClassFileTypeHandler} instance. + */ + public ClassFileTypeHandler() + { + java.lang.reflect.Type superclass = getClass().getGenericSuperclass(); + m_typeDescriptorClass = (Class) + ((ParameterizedType) superclass).getActualTypeArguments()[1]; + m_annotationClass = (Class) + ((ParameterizedType) superclass).getActualTypeArguments()[2]; + } + + // ---- TypeHandler implementation -------------------------------------- + + @Override + public void importType(T type, ClassNode source, Schema schema) + { + AnnotationNode an = AsmUtils.getAnnotation(source, m_annotationClass); + if (an != null) + { + String name = (String) AsmUtils.getAnnotationAttribute(an, "name"); + if (!StringUtils.isEmpty(name)) + { + type.setDescriptor(parseTypeName(name)); + } + } + } + + @Override + public void exportType(T type, ClassNode target, Schema schema) + { + if (type.getDescriptor() != null) + { + AnnotationNode an = new AnnotationNode(Type.getDescriptor(m_annotationClass)); + an.values = Arrays.asList("name", type.getFullName()); + + AsmUtils.addAnnotation(target, an); + } + } + + // ---- helper methods -------------------------------------------------- + + /** + * Parse specified platform-specific type name and return the + * {@link TypeDescriptor} that corresponds to it. + * + * @param name the type name to parse + * + * @return the {@link TypeDescriptor} for the specified type name + */ + protected TD parseTypeName(String name) + { + try + { + if (m_parseMethod == null) + { + m_parseMethod = m_typeDescriptorClass.getMethod("parse", String.class); + } + return (TD) m_parseMethod.invoke(m_typeDescriptorClass, name); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + // ---- data members ---------------------------------------------------- + + private final Class m_typeDescriptorClass; + private final Class m_annotationClass; + private Method m_parseMethod; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/JavaSourcePropertyHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/JavaSourcePropertyHandler.java new file mode 100644 index 0000000000000..052be06809175 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/JavaSourcePropertyHandler.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang; + + +import com.oracle.coherence.common.schema.AbstractPropertyHandler; +import com.oracle.coherence.common.schema.JavaSourceSchemaExporter; +import com.oracle.coherence.common.schema.PropertyHandler; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.TypeDescriptor; +import com.sun.codemodel.JAnnotationUse; +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; + + +/** + * A base class for {@link PropertyHandler} implementations that write + * property metadata to a Java source file. + * + * @param the type of property class that is handled by this {@link + * PropertyHandler} + * @param the type of type descriptor used to represent property type + * @param the annotation type that can be used to provide hints to this + * {@link PropertyHandler} + * + * @author as 2013.11.21 + */ +@SuppressWarnings("unchecked") +public class JavaSourcePropertyHandler + < + T extends AbstractLangProperty, + TD extends TypeDescriptor, + A extends Annotation + > + extends AbstractPropertyHandler + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@code JavaSourcePropertyHandler} instance. + */ + public JavaSourcePropertyHandler() + { + java.lang.reflect.Type superclass = getClass().getGenericSuperclass(); + m_annotationClass = (Class) + ((ParameterizedType) superclass).getActualTypeArguments()[2]; + } + + // ---- PropertyHandler implementation ---------------------------------- + + @Override + public void exportProperty(T property, JavaSourceSchemaExporter.JProperty target, Schema schema) + { + if (property.getType() != null) + { + JAnnotationUse annotation = target.field().annotate(m_annotationClass); + annotation.param("type", property.getType().getFullName()); + } + } + + // ---- data members ---------------------------------------------------- + + private final Class m_annotationClass; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/JavaSourceTypeHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/JavaSourceTypeHandler.java new file mode 100644 index 0000000000000..9e02fcf6f0e29 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/JavaSourceTypeHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang; + + +import com.oracle.coherence.common.schema.AbstractTypeHandler; +import com.oracle.coherence.common.schema.Property; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.TypeDescriptor; +import com.oracle.coherence.common.schema.TypeHandler; + +import com.sun.codemodel.JAnnotationUse; +import com.sun.codemodel.JDefinedClass; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; + + +/** + * A base class for {@link TypeHandler} implementations that write + * type metadata to a Java source file. + * + * @param the type of the {@link com.oracle.coherence.common.schema.Type} class + * that is handled by this {@link TypeHandler} + * @param the type of the type descriptor used by the type {@code } + * @param the annotation type that can be used to provide hints to this + * {@link TypeHandler} + * + * @author as 2013.11.21 + */ +@SuppressWarnings("unchecked") +public class JavaSourceTypeHandler + < + T extends AbstractLangType, + TD extends TypeDescriptor, + A extends Annotation + > + extends AbstractTypeHandler + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@code JavaSourceTypeHandler} instance. + */ + public JavaSourceTypeHandler() + { + java.lang.reflect.Type superclass = getClass().getGenericSuperclass(); + m_annotationClass = (Class) + ((ParameterizedType) superclass).getActualTypeArguments()[2]; + } + + // ---- TypeHandler implementation -------------------------------------- + + @Override + public void exportType(T type, JDefinedClass target, Schema schema) + { + if (type.getDescriptor() != null) + { + JAnnotationUse annotation = target.annotate(m_annotationClass); + annotation.param("name", type.getFullName()); + } + } + + // ---- data members ---------------------------------------------------- + + private final Class m_annotationClass; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/XmlPropertyHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/XmlPropertyHandler.java new file mode 100644 index 0000000000000..c8e8844b60d3b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/XmlPropertyHandler.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang; + + +import com.oracle.coherence.common.schema.AbstractPropertyHandler; +import com.oracle.coherence.common.schema.PropertyHandler; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.TypeDescriptor; +import com.oracle.coherence.common.schema.util.StringUtils; +import com.oracle.coherence.common.schema.util.XmlUtils; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + +/** + * A base class for {@link PropertyHandler} implementations that read and write + * property metadata from/to Java class file. + * + * @param the type of property class that is handled by this {@link + * PropertyHandler} + * @param the type of type descriptor used to represent property type + * + * @author as 2013.11.20 + */ +@SuppressWarnings("unchecked") +public class XmlPropertyHandler, TD extends TypeDescriptor> + extends AbstractPropertyHandler + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct an {@code XmlPropertyHandler} instance. + */ + public XmlPropertyHandler(String ns) + { + m_ns = ns; + + java.lang.reflect.Type superclass = getClass().getGenericSuperclass(); + this.m_typeDescriptorClass = (Class) + ((ParameterizedType) superclass).getActualTypeArguments()[1]; + } + + // ---- PropertyHandler implementation ---------------------------------- + + @Override + public void importProperty(T property, Element source, Schema schema) + { + Element xmlProperty = XmlUtils.getChildElement(source, getNS(), "property"); + if (xmlProperty != null) + { + String type = xmlProperty.getAttributeNS(getNS(), "type"); + property.setType(parseTypeName(type)); + + importPropertyInternal(property, source, xmlProperty, schema); + } + } + + @Override + public void exportProperty(T property, Element target, Schema schema) + { + if (property.getType() != null) + { + Document doc = target.getOwnerDocument(); + doc.getDocumentElement().setAttribute("xmlns:" + getPrefix(), getNS()); + + Element xmlProperty = doc.createElementNS(getNS(), getPrefix() + ":property"); + xmlProperty.setAttribute("type", property.getType().toString()); + target.appendChild(xmlProperty); + + exportPropertyInternal(property, target, xmlProperty, schema); + } + } + + // ---- helper methods -------------------------------------------------- + + /** + * Enables subclasses to provide additional processing during import from + * XML (typically used to read additional, non-standard metadata from the + * source element being imported). + * + * @param property the property to import into + * @param source the root, canonical XML element for the property + * @param xmlProperty the extension-specific sub-element for the property + * @param schema the schema imported property belongs to + */ + protected void importPropertyInternal(T property, Element source, Element xmlProperty, Schema schema) + { + } + + /** + * Enables subclasses to provide additional processing during export into + * XML (typically used to write additional, non-standard metadata into the + * target element being exported). + * + * @param property the property to export + * @param target the root, canonical XML element for the property + * @param xmlProperty the extension-specific sub-element for the property + * @param schema the schema exported property belongs to + */ + protected void exportPropertyInternal(T property, Element target, Element xmlProperty, Schema schema) + { + } + + /** + * Parse specified platform-specific type name and return the + * {@link TypeDescriptor} that corresponds to it. + * + * @param name the type name to parse + * + * @return the {@link TypeDescriptor} for the specified type name + */ + protected TD parseTypeName(String name) + { + try + { + if (m_parseMethod == null) + { + m_parseMethod = m_typeDescriptorClass.getMethod("parse", String.class); + } + return (TD) m_parseMethod.invoke(m_typeDescriptorClass, name); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** + * Return the namespace prefix used within element names. + * + * @return the namespace prefix used within element names + */ + protected String getPrefix() + { + if (m_nsPrefix == null) + { + String[] ns = StringUtils.split(m_ns, "/"); + m_nsPrefix = ns[ns.length-1]; + } + return m_nsPrefix; + } + + /** + * Return the XML namespace this handler is responsible for. + * + * @return the XML namespace this handler is responsible for + */ + protected String getNS() + { + return m_ns; + } + + // ---- data members ---------------------------------------------------- + + private String m_nsPrefix; + private final String m_ns; + private Class m_typeDescriptorClass; + private Method m_parseMethod; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/XmlTypeHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/XmlTypeHandler.java new file mode 100644 index 0000000000000..afcf389b26941 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/XmlTypeHandler.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang; + + +import com.oracle.coherence.common.schema.AbstractTypeHandler; +import com.oracle.coherence.common.schema.Property; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.TypeDescriptor; +import com.oracle.coherence.common.schema.TypeHandler; +import com.oracle.coherence.common.schema.util.StringUtils; +import com.oracle.coherence.common.schema.util.XmlUtils; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.util.List; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + +/** + * A base class for {@link TypeHandler} implementations that read and write + * type metadata from/to XML file. + * + * @param the type of the {@link com.oracle.coherence.common.schema.Type} class + * that is handled by this {@link TypeHandler} + * @param the type of the type descriptor used by the type {@code } + * + * @author as 2013.11.20 + */ +@SuppressWarnings("unchecked") +public class XmlTypeHandler + < + T extends AbstractLangType, + TD extends TypeDescriptor + > + extends AbstractTypeHandler + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct a {@code XmlTypeHandler} instance. + */ + public XmlTypeHandler(String ns) + { + m_ns = ns; + + java.lang.reflect.Type superclass = getClass().getGenericSuperclass(); + this.m_typeDescriptorClass = (Class) + ((ParameterizedType) superclass).getActualTypeArguments()[1]; + } + + // ---- TypeHandler implementation -------------------------------------- + + @Override + public void importType(T type, Element source, Schema schema) + { + Element xmlType = XmlUtils.getChildElement(source, getNS(), "type"); + if (xmlType != null) + { + String name = xmlType.getAttribute("name"); + type.setDescriptor(parseTypeName(name)); + + List aliases = XmlUtils.toElementList( + xmlType.getElementsByTagNameNS(getNS(), "alias")); + for (Element i : aliases) + { + name = i.getAttribute("name"); + type.addAlias(parseTypeName(name)); + } + + importTypeInternal(type, source, xmlType, schema); + } + } + + @Override + public void exportType(T type, Element target, Schema schema) + { + if (type.getDescriptor() != null) + { + Document doc = target.getOwnerDocument(); + doc.getDocumentElement().setAttribute("xmlns:" + getPrefix(), getNS()); + + Element xmlType = doc.createElementNS(getNS(), getPrefix() + ":type"); + xmlType.setAttribute("name", type.getDescriptor().getFullName()); + + for (TD i : type.getAliases()) + { + Element xmlInterface = doc.createElementNS(getNS(), getPrefix() + ":alias"); + xmlInterface.setAttribute("name", i.getFullName()); + xmlType.appendChild(xmlInterface); + } + + exportTypeInternal(type, doc, target, xmlType, schema); + target.appendChild(xmlType); + } + } + + // ---- helper methods -------------------------------------------------- + + /** + * Enables subclasses to provide additional processing during import from + * XML (typically used to read additional, non-standard metadata from the + * source element being imported). + * + * @param type the type to import into + * @param source the root, canonical XML element for the type + * @param xmlType the extension-specific sub-element for the type + * @param schema the schema imported type belongs to + */ + protected void importTypeInternal(T type, Element source, Element xmlType, Schema schema) + { + } + + /** + * Enables subclasses to provide additional processing during export into + * XML (typically used to write additional, non-standard metadata into the + * target element being exported). + * + * @param type the type to export + * @param target the root, canonical XML element for the type + * @param xmlType the extension-specific sub-element for the type + * @param schema the schema exported type belongs to + */ + protected void exportTypeInternal(T type, Document doc, Element target, Element xmlType, Schema schema) + { + } + + /** + * Parse specified platform-specific type name and return the + * {@link TypeDescriptor} that corresponds to it. + * + * @param name the type name to parse + * + * @return the {@link TypeDescriptor} for the specified type name + */ + protected TD parseTypeName(String name) + { + try + { + if (m_parseMethod == null) + { + m_parseMethod = m_typeDescriptorClass.getMethod("parse", String.class); + } + return (TD) m_parseMethod.invoke(m_typeDescriptorClass, name); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** + * Return the namespace prefix used within element names. + * + * @return the namespace prefix used within element names + */ + protected String getPrefix() + { + if (m_nsPrefix == null) + { + String[] ns = StringUtils.split(m_ns, "/"); + m_nsPrefix = ns[ns.length-1]; + } + return m_nsPrefix; + } + + /** + * Return the XML namespace this handler is responsible for. + * + * @return the XML namespace this handler is responsible for + */ + protected String getNS() + { + return m_ns; + } + + // ---- data members ---------------------------------------------------- + + private String m_nsPrefix; + private final String m_ns; + private Class m_typeDescriptorClass; + private Method m_parseMethod; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppExtension.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppExtension.java new file mode 100644 index 0000000000000..14af4945d08c0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppExtension.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.cpp; + + +import com.oracle.coherence.common.schema.PropertyHandler; +import com.oracle.coherence.common.schema.SchemaExtension; +import com.oracle.coherence.common.schema.TypeHandler; +import com.oracle.coherence.common.schema.lang.ClassFilePropertyHandler; +import com.oracle.coherence.common.schema.lang.ClassFileTypeHandler; +import com.oracle.coherence.common.schema.lang.JavaSourcePropertyHandler; +import com.oracle.coherence.common.schema.lang.JavaSourceTypeHandler; +import com.oracle.coherence.common.schema.lang.XmlPropertyHandler; +import com.oracle.coherence.common.schema.lang.XmlTypeHandler; +import java.util.Arrays; +import java.util.Collection; + + +/** + * An implementation of a {@link SchemaExtension} that provides support for C++. + * + * @author as 2013.08.28 + */ +public class CppExtension + implements SchemaExtension + { + // ---- SchemaExtension implementation ---------------------------------- + + @Override + public String getName() + { + return "c++"; + } + + @Override + public Collection getTypeHandlers() + { + return Arrays.asList(new TypeHandler[] { + new CppClassFileTypeHandler(), + new CppJavaSourceTypeHandler(), + new CppXmlTypeHandler() + }); + } + + @Override + public Collection getPropertyHandlers() + { + return Arrays.asList(new PropertyHandler[] { + new CppClassFilePropertyHandler(), + new CppJavaSourcePropertyHandler(), + new CppXmlPropertyHandler() + }); + } + + // ---- inner class: CppClassFileTypeHandler ---------------------------- + + /** + * C++ type handler that reads and writes type metadata from/to Java class + * file. + */ + public static class CppClassFileTypeHandler + extends ClassFileTypeHandler + { + } + + // ---- inner class: CppClassFilePropertyHandler ------------------------ + + /** + * C++ property handler that reads and writes property metadata from/to Java + * class file. + */ + public static class CppClassFilePropertyHandler + extends ClassFilePropertyHandler + { + } + + // ---- inner class: CppJavaSourceTypeHandler --------------------------- + + /** + * C++ type handler that reads and writes type metadata from/to Java + * source file. + */ + public static class CppJavaSourceTypeHandler + extends JavaSourceTypeHandler + { + } + + // ---- inner class: CppJavaSourcePropertyHandler ----------------------- + + /** + * C++ property handler that reads and writes property metadata from/to Java + * source file. + */ + public static class CppJavaSourcePropertyHandler + extends JavaSourcePropertyHandler + { + } + + // ---- inner class: CppXmlTypeHandler ---------------------------------- + + /** + * C++ type handler that reads and writes type metadata from/to XML file. + */ + public static class CppXmlTypeHandler + extends XmlTypeHandler + { + public CppXmlTypeHandler() + { + super(NS); + } + } + + // ---- inner class: CppXmlPropertyHandler ------------------------------ + + /** + * C++ property handler that reads and writes property metadata from/to + * XML file. + */ + public static class CppXmlPropertyHandler + extends XmlPropertyHandler + { + public CppXmlPropertyHandler() + { + super(NS); + } + } + + // ---- static members -------------------------------------------------- + + /** + * The XML namespace handled by {@link CppXmlTypeHandler} and + * {@link CppXmlPropertyHandler}. + */ + private static final String NS = "http://xmlns.oracle.com/coherence/schema/cpp"; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppProperty.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppProperty.java new file mode 100644 index 0000000000000..41c053598f7a8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppProperty.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.cpp; + + +import com.oracle.coherence.common.schema.ExtensibleProperty; +import com.oracle.coherence.common.schema.lang.AbstractLangProperty; + + +/** + * The representation of the C++ property metadata. + * + * @author as 2013.07.12 + */ +public class CppProperty + extends AbstractLangProperty + { + /** + * Construct {@code CppProperty} instance. + * + * @param parent the parent {@link ExtensibleProperty} + */ + public CppProperty(ExtensibleProperty parent) + { + super(parent); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppType.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppType.java new file mode 100644 index 0000000000000..b4c571c5e5d22 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.cpp; + + +import com.oracle.coherence.common.schema.ExtensibleType; +import com.oracle.coherence.common.schema.lang.AbstractLangType; + + +/** + * The representation of the C++ type metadata. + * + * @author as 2013.06.21 + */ +public class CppType + extends AbstractLangType + { + /** + * Construct {@code CppType} instance. + * + * @param parent the parent {@link ExtensibleType} + */ + public CppType(ExtensibleType parent) + { + super(parent); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppTypeDescriptor.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppTypeDescriptor.java new file mode 100644 index 0000000000000..f369b697f1665 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/CppTypeDescriptor.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.cpp; + + +import com.oracle.coherence.common.schema.AbstractTypeDescriptor; +import com.oracle.coherence.common.schema.CanonicalTypeDescriptor; +import com.oracle.coherence.common.schema.TypeDescriptor; +import java.util.ArrayList; +import java.util.List; + + +/** + * Implementation of a {@link TypeDescriptor} that is used to represent C++ + * types. + * + * @author as 2013.08.27 + */ +public class CppTypeDescriptor + extends AbstractTypeDescriptor + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code CppTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + */ + private CppTypeDescriptor(String[] namespace, String name, boolean fArray) + { + super(namespace, name, fArray); + } + + /** + * Construct {@code CppTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + * @param genericArgs the list of generic argument descriptors + */ + private CppTypeDescriptor(String[] namespace, String name, boolean fArray, + List genericArgs) + { + super(namespace, name, fArray, genericArgs); + } + + // ---- factory methods ------------------------------------------------- + + /** + * Create {@code CppTypeDescriptor} by parsing fully qualified type name. + * + * @param type the fully qualified C++ type name + * + * @return a {@code CppTypeDescriptor} for the specified type name + */ + public static CppTypeDescriptor parse(String type) + { + return PARSER.parse(type); + } + + /** + * Create {@code CppTypeDescriptor} from a canonical type descriptor. + * + * @param type the canonical type descriptor + * + * @return a {@code CppTypeDescriptor} for the specified canonical type + * descriptor + */ + public static CppTypeDescriptor fromCanonical(CanonicalTypeDescriptor type) + { + return new CppTypeDescriptor(type.getNamespaceComponents(), type.getName(), + type.isArray(), fromCanonical(type.getGenericArguments())); + } + + /** + * Create a list of {@code CppTypeDescriptor}s from a list of canonical + * type descriptors. + * + * @param types the list of canonical type descriptor + * + * @return a list of {@code CppTypeDescriptor}s for the specified + * list of canonical type descriptors + */ + public static List fromCanonical(List types) + { + List result = new ArrayList<>(types.size()); + for (CanonicalTypeDescriptor type : types) + { + result.add(fromCanonical(type)); + } + + return result; + } + + // ---- AbstractTypeDescriptor implementation --------------------------- + + @Override + protected CppTypeDescriptor createArrayType(String[] namespace, String name) + { + return new CppTypeDescriptor(namespace, name, true); + } + + @Override + protected Parser getParser() + { + return PARSER; + } + + // ---- inner class: CppTypeParser -------------------------------------- + + /** + * An implementation of a C++ type name parser. + */ + private static class CppTypeParser + extends Parser + { + // ---- constructors ------------------------------------------------ + + /** + * Construct {@code CppTypeParser} instance. + * + * @param separator the namespace separator + */ + public CppTypeParser(String separator) + { + super(separator); + } + + // ---- AbstractTypeDescriptor.Parser implementation ---------------- + + @Override + protected CppTypeDescriptor getStandardType(String type) + { + return null; + } + + @Override + protected CppTypeDescriptor createTypeDescriptor( + String[] namespace, String name, boolean fArray, + List genericArgs) + { + return new CppTypeDescriptor(namespace, name, fArray, genericArgs); + } + } + + // ---- static members -------------------------------------------------- + + /** + * An instance of a C++ type name parser. + */ + public static final CppTypeParser PARSER = new CppTypeParser("::"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/annotation/CppProperty.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/annotation/CppProperty.java new file mode 100644 index 0000000000000..9259ab1b25e2d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/annotation/CppProperty.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.cpp.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * An annotation that can be applied to Java fields to specify the C++ type + * that should be used to represent that property. + * + * @author as 2013.11.21 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CppProperty + { + /** + * The fully qualified name of the C++ type that should be used for + * this property. + * + * @return the name of the C++ property type + */ + String type() default ""; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/annotation/CppType.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/annotation/CppType.java new file mode 100644 index 0000000000000..485232c48e041 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/annotation/CppType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.cpp.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * An annotation that can be applied to Java classes to specify the name of the + * corresponding C++ type. + * + * @author as 2013.11.21 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface CppType + { + /** + * The fully qualified name of the C++ type this type should map to. + * + * @return the name of the C++ type + */ + String name() default ""; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/annotation/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/annotation/package.html new file mode 100644 index 0000000000000..be8bd14278faa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/annotation/package.html @@ -0,0 +1,5 @@ + +Defines annotations for C++ type definitions. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/package.html new file mode 100644 index 0000000000000..bf89dcc47fe0d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/cpp/package.html @@ -0,0 +1,5 @@ + +Defines C++ schema extensions. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetExtension.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetExtension.java new file mode 100644 index 0000000000000..8cd34e25c597d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetExtension.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.dotnet; + + +import com.oracle.coherence.common.schema.PropertyHandler; +import com.oracle.coherence.common.schema.SchemaExtension; +import com.oracle.coherence.common.schema.TypeHandler; +import com.oracle.coherence.common.schema.lang.ClassFilePropertyHandler; +import com.oracle.coherence.common.schema.lang.ClassFileTypeHandler; +import com.oracle.coherence.common.schema.lang.JavaSourcePropertyHandler; +import com.oracle.coherence.common.schema.lang.JavaSourceTypeHandler; +import com.oracle.coherence.common.schema.lang.XmlPropertyHandler; +import com.oracle.coherence.common.schema.lang.XmlTypeHandler; +import java.util.Arrays; +import java.util.Collection; + + +/** + * An implementation of a {@link SchemaExtension} that provides support for the + * .NET platform. + * + * @author as 2013.08.28 + */ +public class DotNetExtension + implements SchemaExtension + { + // ---- SchemaExtension implementation ---------------------------------- + + @Override + public String getName() + { + return ".net"; + } + + @Override + public Collection getTypeHandlers() + { + return Arrays.asList(new TypeHandler[] { + new DotNetClassFileTypeHandler(), + new DotNetJavaSourceTypeHandler(), + new DotNetXmlTypeHandler() + }); + } + + @Override + public Collection getPropertyHandlers() + { + return Arrays.asList(new PropertyHandler[] { + new DotNetClassFilePropertyHandler(), + new DotNetJavaSourcePropertyHandler(), + new DotNetXmlPropertyHandler() + }); + } + + // ---- inner class: DotNetClassFileTypeHandler ------------------------- + + /** + * .NET type handler that reads and writes type metadata from/to Java class + * file. + */ + public static class DotNetClassFileTypeHandler + extends ClassFileTypeHandler + { + } + + // ---- inner class: DotNetClassFilePropertyHandler --------------------- + + /** + * .NET property handler that reads and writes property metadata from/to + * Java class file. + */ + public static class DotNetClassFilePropertyHandler + extends ClassFilePropertyHandler + { + } + + // ---- inner class: DotNetJavaSourceTypeHandler ------------------------ + + /** + * .NET type handler that reads and writes type metadata from/to Java + * source file. + */ + public static class DotNetJavaSourceTypeHandler + extends JavaSourceTypeHandler + { + } + + // ---- inner class: DotNetJavaSourcePropertyHandler -------------------- + + /** + * .NET property handler that reads and writes property metadata from/to + * Java source file. + */ + public static class DotNetJavaSourcePropertyHandler + extends JavaSourcePropertyHandler + { + } + + // ---- inner class: DotNetXmlTypeHandler ------------------------------- + + /** + * .NET type handler that reads and writes type metadata from/to XML file. + */ + public static class DotNetXmlTypeHandler + extends XmlTypeHandler + { + public DotNetXmlTypeHandler() + { + super(NS); + } + } + + // ---- inner class: DotNetXmlPropertyHandler --------------------------- + + /** + * .NET property handler that reads and writes property metadata from/to + * XML file. + */ + public static class DotNetXmlPropertyHandler + extends XmlPropertyHandler + { + public DotNetXmlPropertyHandler() + { + super(NS); + } + } + + // ---- static members -------------------------------------------------- + + /** + * The XML namespace handled by {@link DotNetXmlTypeHandler} and + * {@link DotNetXmlPropertyHandler}. + */ + private static final String NS = "http://xmlns.oracle.com/coherence/schema/dotnet"; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetProperty.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetProperty.java new file mode 100644 index 0000000000000..4f4a745eca76d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetProperty.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.dotnet; + + +import com.oracle.coherence.common.schema.ExtensibleProperty; +import com.oracle.coherence.common.schema.lang.AbstractLangProperty; + + +/** + * The representation of the .NET property metadata. + * + * @author as 2013.07.12 + */ +public class DotNetProperty + extends AbstractLangProperty + { + /** + * Construct {@code CppProperty} instance. + * + * @param parent the parent {@link ExtensibleProperty} + */ + public DotNetProperty(ExtensibleProperty parent) + { + super(parent); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetType.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetType.java new file mode 100644 index 0000000000000..d07370ae13ea3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.dotnet; + + +import com.oracle.coherence.common.schema.ExtensibleType; +import com.oracle.coherence.common.schema.lang.AbstractLangType; + + +/** + * The representation of the .NET type metadata. + * + * @author as 2013.06.21 + */ +public class DotNetType + extends AbstractLangType + { + /** + * Construct {@code DotNetType} instance. + * + * @param parent the parent {@link ExtensibleType} + */ + public DotNetType(ExtensibleType parent) + { + super(parent); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetTypeDescriptor.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetTypeDescriptor.java new file mode 100644 index 0000000000000..99f5aba583253 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/DotNetTypeDescriptor.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.dotnet; + + +import com.oracle.coherence.common.schema.AbstractTypeDescriptor; +import com.oracle.coherence.common.schema.CanonicalTypeDescriptor; +import com.oracle.coherence.common.schema.TypeDescriptor; +import java.util.ArrayList; +import java.util.List; + + +/** + * Implementation of a {@link TypeDescriptor} that is used to represent .NET + * types. + * + * @author as 2013.08.27 + */ +public class DotNetTypeDescriptor + extends AbstractTypeDescriptor + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code DotNetTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + */ + private DotNetTypeDescriptor(String[] namespace, String name, boolean fArray) + { + super(namespace, name, fArray); + } + + /** + * Construct {@code DotNetTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + * @param genericArgs the list of generic argument descriptors + */ + private DotNetTypeDescriptor(String[] namespace, String name, boolean fArray, + List genericArgs) + { + super(namespace, name, fArray, genericArgs); + } + + // ---- factory methods ------------------------------------------------- + + /** + * Create {@code DotNetTypeDescriptor} by parsing fully qualified type name. + * + * @param type the fully qualified .NET type name + * + * @return a {@code DotNetTypeDescriptor} for the specified type name + */ + public static DotNetTypeDescriptor parse(String type) + { + return PARSER.parse(type); + } + + /** + * Create {@code DotNetTypeDescriptor} from a canonical type descriptor. + * + * @param type the canonical type descriptor + * + * @return a {@code DotNetTypeDescriptor} for the specified canonical type + * descriptor + */ + public static DotNetTypeDescriptor fromCanonical(CanonicalTypeDescriptor type) + { + return new DotNetTypeDescriptor(type.getNamespaceComponents(), type.getName(), + type.isArray(), fromCanonical(type.getGenericArguments())); + } + + /** + * Create a list of {@code DotNetTypeDescriptor}s from a list of canonical + * type descriptors. + * + * @param types the list of canonical type descriptor + * + * @return a list of {@code DotNetTypeDescriptor}s for the specified + * list of canonical type descriptors + */ + public static List fromCanonical(List types) + { + List result = new ArrayList<>(types.size()); + for (CanonicalTypeDescriptor type : types) + { + result.add(fromCanonical(type)); + } + + return result; + } + + // ---- AbstractTypeDescriptor implementation --------------------------- + + @Override + protected DotNetTypeDescriptor createArrayType(String[] namespace, String name) + { + return new DotNetTypeDescriptor(namespace, name, true); + } + + @Override + protected Parser getParser() + { + return PARSER; + } + + // ---- inner class: DotNetTypeParser ----------------------------------- + + /** + * An implementation of a .NET type name parser. + */ + private static class DotNetTypeParser + extends Parser + { + // ---- constructors ------------------------------------------------ + + /** + * Construct {@code DotNetTypeParser} instance. + * + * @param separator the namespace separator + */ + public DotNetTypeParser(String separator) + { + super(separator); + } + + // ---- AbstractTypeDescriptor.Parser implementation ---------------- + + @Override + protected DotNetTypeDescriptor getStandardType(String type) + { + return null; + } + + @Override + protected DotNetTypeDescriptor createTypeDescriptor( + String[] namespace, String name, boolean fArray, + List genericArgs) + { + return new DotNetTypeDescriptor(namespace, name, fArray, genericArgs); + } + } + + // ---- static members -------------------------------------------------- + + /** + * An instance of a .NET type name parser. + */ + public static final DotNetTypeParser PARSER = new DotNetTypeParser("."); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/annotation/DotNetProperty.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/annotation/DotNetProperty.java new file mode 100644 index 0000000000000..b2399cf4f906f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/annotation/DotNetProperty.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.dotnet.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * An annotation that can be applied to Java fields to specify the .NET type + * that should be used to represent that property. + * + * @author as 2013.11.21 + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface DotNetProperty + { + /** + * The fully qualified name of the .NET type that should be used for + * this property. + * + * @return the name of the .NET property type + */ + String type() default ""; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/annotation/DotNetType.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/annotation/DotNetType.java new file mode 100644 index 0000000000000..015cb73420ca6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/annotation/DotNetType.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.dotnet.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * An annotation that can be applied to Java classes to specify the name of the + * corresponding .NET type. + * + * @author as 2013.11.21 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface DotNetType + { + /** + * The fully qualified name of the .NET type this type should map to. + * + * @return the name of the .NET type + */ + String name() default ""; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/annotation/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/annotation/package.html new file mode 100644 index 0000000000000..14e944c2f823f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/annotation/package.html @@ -0,0 +1,5 @@ + +Defines annotations for .Net type definitions. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/package.html new file mode 100644 index 0000000000000..6c6203f7dba13 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/dotnet/package.html @@ -0,0 +1,5 @@ + +Defines .Net schema extensions. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaExtension.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaExtension.java new file mode 100644 index 0000000000000..c10d8d7c0ab6d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaExtension.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.java; + + +import com.oracle.coherence.common.schema.PropertyHandler; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.SchemaExtension; +import com.oracle.coherence.common.schema.TypeHandler; +import com.oracle.coherence.common.schema.lang.XmlPropertyHandler; +import com.oracle.coherence.common.schema.lang.XmlTypeHandler; +import com.oracle.coherence.common.schema.lang.java.handler.ClassFileHandler; +import com.oracle.coherence.common.schema.util.StringUtils; +import java.util.Arrays; +import java.util.Collection; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + +/** + * An implementation of a {@link SchemaExtension} that provides support for the + * Java platform. + * + * @author as 2013.08.28 + */ +public class JavaExtension implements SchemaExtension + { + // ---- SchemaExtension implementation ---------------------------------- + + @Override + public String getName() + { + return "java"; + } + + @Override + public Collection getTypeHandlers() + { + return Arrays.asList(new TypeHandler[] { + new ClassFileHandler.ClassFileTypeHandler(), + new JavaXmlTypeHandler() + }); + } + + @Override + public Collection getPropertyHandlers() + { + return Arrays.asList(new PropertyHandler[] { + new ClassFileHandler.ClassFilePropertyHandler(), + new JavaXmlPropertyHandler() + }); + } + + // ---- inner class: JavaXmlTypeHandler --------------------------------- + + /** + * Java type handler that reads and writes type metadata from/to XML file. + */ + public static class JavaXmlTypeHandler + extends XmlTypeHandler + { + public JavaXmlTypeHandler() + { + super(NS); + } + + @Override + protected void importTypeInternal(JavaType type, Element source, Element xmlType, Schema schema) + { + String wrapper = xmlType.getAttribute("wrapper"); + if (!StringUtils.isEmpty(wrapper)) + { + type.setWrapperType(parseTypeName(wrapper)); + } + } + + @Override + protected void exportTypeInternal(JavaType type, Document doc, Element target, Element xmlType, Schema schema) + { + if (type.getWrapperType() != null) + { + xmlType.setAttribute("wrapper", type.getWrapperType().getFullName()); + } + } + } + + // ---- inner class: JavaXmlPropertyHandler ----------------------------- + + /** + * Java property handler that reads and writes property metadata from/to + * XML file. + */ + public static class JavaXmlPropertyHandler + extends XmlPropertyHandler + { + public JavaXmlPropertyHandler() + { + super(NS); + } + } + + // ---- static members -------------------------------------------------- + + /** + * The XML namespace handled by {@link JavaXmlTypeHandler} and + * {@link JavaXmlPropertyHandler}. + */ + private static final String NS = "http://xmlns.oracle.com/coherence/schema/java"; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaProperty.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaProperty.java new file mode 100644 index 0000000000000..2a3fb9e7fad5f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaProperty.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.java; + + +import com.oracle.coherence.common.schema.ExtensibleProperty; +import com.oracle.coherence.common.schema.ExtensibleType; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.lang.AbstractLangProperty; + + +/** + * The representation of the Java property metadata. + * + * @author as 2013.07.12 + */ +public class JavaProperty + extends AbstractLangProperty + { + /** + * Construct {@code JavaProperty} instance. + * + * @param parent the parent {@link ExtensibleProperty} + */ + public JavaProperty(ExtensibleProperty parent) + { + super(parent); + } + + /** + * Resolve and return the type of this property. + * + * @param schema the schema to use for type lookup + * + * @return the descriptor for the property type + * + * @throws IllegalStateException if the property type cannot be resolved + */ + public JavaTypeDescriptor resolveType(Schema schema) + { + if (getType() != null) + { + return getType(); + } + else + { + ExtensibleType extType = schema.getType(getParent().getType()); + if (extType != null) + { + JavaType javaType = extType.getExtension(JavaType.class); + if (javaType != null) + { + return javaType.getDescriptor(); + } + } + } + + throw new IllegalStateException("Unable to resolve property type"); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaType.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaType.java new file mode 100644 index 0000000000000..1f5bc71fad8f5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaType.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.java; + + +import com.oracle.coherence.common.schema.ExtensibleType; +import com.oracle.coherence.common.schema.lang.AbstractLangType; + + +/** + * The representation of the Java type metadata. + * + * @author as 2013.06.21 + */ +public class JavaType extends AbstractLangType + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code JavaType} instance. + * + * @param parent the parent {@link ExtensibleType} + */ + public JavaType(ExtensibleType parent) + { + super(parent); + } + + // ---- public API ------------------------------------------------------ + + /** + * Return a descriptor for the wrapper type associated with this type, + * if any. + * + * @return a descriptor for the wrapper type associated with this type + */ + public JavaTypeDescriptor getWrapperType() + { + return m_wrapperType; + } + + /** + * Set the wrapper type associated with this type. + * + * @param wrapperType the wrapper type associated with this type + */ + public void setWrapperType(JavaTypeDescriptor wrapperType) + { + m_wrapperType = wrapperType; + } + + /** + * Return {@code true} if this type implements the specified interface. + * + * @param name the name of the interface to check + * + * @return {@code true} if this type implements the specified interface, + * {@code false} otherwise + */ + public boolean implementsInterface(String name) + { + for (JavaTypeDescriptor td : getAliases()) + { + if (name.equals(td.getFullName())) + { + return true; + } + } + + return false; + } + + // ---- Object methods -------------------------------------------------- + + @Override + public String toString() + { + return getClass().getSimpleName() + "{" + + "name=" + getName() + + ", desc=" + getDescriptor() + + ", wrapper=" + getWrapperType() + + '}'; + } + + // ---- data members ---------------------------------------------------- + + private JavaTypeDescriptor m_wrapperType; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaTypeDescriptor.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaTypeDescriptor.java new file mode 100644 index 0000000000000..beb2ca34f5625 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/JavaTypeDescriptor.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.java; + + +import com.oracle.coherence.common.schema.AbstractTypeDescriptor; +import com.oracle.coherence.common.schema.CanonicalTypeDescriptor; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.TypeDescriptor; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +/** + * Implementation of a {@link TypeDescriptor} that is used to represent Java + * types. + * + * @author as 2013.08.27 + */ +public class JavaTypeDescriptor + extends AbstractTypeDescriptor + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code JavaTypeDescriptor} instance. + * + * @param name the type name + */ + private JavaTypeDescriptor(String name) + { + super(name); + } + + /** + * Construct {@code JavaTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + */ + private JavaTypeDescriptor(String[] namespace, String name) + { + super(namespace, name); + } + + /** + * Construct {@code JavaTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + */ + private JavaTypeDescriptor(String[] namespace, String name, boolean fArray) + { + super(namespace, name, fArray); + } + + /** + * Construct {@code JavaTypeDescriptor} instance. + * + * @param namespace the type namespace + * @param name the type name + * @param fArray the flag specifying whether the type is an array type + * @param genericArgs the list of generic argument descriptors + */ + private JavaTypeDescriptor(String[] namespace, String name, boolean fArray, + List genericArgs) + { + super(namespace, name, fArray, genericArgs); + } + + // ---- factory methods ------------------------------------------------- + + /** + * Create {@code JavaTypeDescriptor} by parsing fully qualified type name. + * + * @param type the fully qualified Java type name + * + * @return a {@code JavaTypeDescriptor} for the specified type name + */ + public static JavaTypeDescriptor parse(String type) + { + return PARSER.parse(type); + } + + /** + * Create {@code JavaTypeDescriptor} by parsing fully qualified internal + * type name. + * + * @param type the fully qualified internal Java type name + * + * @return a {@code JavaTypeDescriptor} for the specified type name + */ + public static JavaTypeDescriptor fromInternal(String type) + { + return PARSER.parseInternal(type); + } + + /** + * Create {@code JavaTypeDescriptor} from a canonical type descriptor. + * + * @param type the canonical type descriptor + * @param schema the schema specified type is defined in + * + * @return a {@code JavaTypeDescriptor} for the specified canonical type + * descriptor + */ + public static JavaTypeDescriptor from(CanonicalTypeDescriptor type, Schema schema) + { + JavaTypeDescriptor jtd = + new JavaTypeDescriptor(type.getNamespaceComponents(), type.getName(), + type.isArray(), from(type.getGenericArguments(), schema)); + + JavaType javaType = schema.getType(type, JavaType.class); + if (javaType != null) + { + String namespace = javaType.getNamespace(); + if (namespace != null) + { + jtd.setNamespace(namespace); + } + String name = javaType.getName(); + if (name != null) + { + jtd.setName(name); + } + } + + return jtd; + } + + /** + * Create a list of {@code JavaTypeDescriptor}s from a list of canonical + * type descriptors. + * + * @param types the list of canonical type descriptor + * @param schema the schema specified types are defined in + * + * @return a list of {@code JavaTypeDescriptor}s for the specified + * list of canonical type descriptors + */ + private static List from(List types, Schema schema) + { + if (types == null) + { + return null; + } + + List result = new ArrayList<>(types.size()); + + for (CanonicalTypeDescriptor type : types) + { + result.add(from(type, schema)); + } + + return result; + } + + // ---- AbstractTypeDescriptor implementation --------------------------- + + @Override + protected JavaTypeDescriptor createArrayType(String[] namespace, String name) + { + return new JavaTypeDescriptor(namespace, name, true); + } + + @Override + protected Parser getParser() + { + return PARSER; + } + + // ---- inner class: JavaTypeParser ------------------------------------- + + /** + * An implementation of a Java type name parser. + */ + private static class JavaTypeParser extends Parser + { + // ---- constructors ------------------------------------------------ + + /** + * Construct {@code JavaTypeParser} instance. + * + * @param separator the namespace separator + */ + public JavaTypeParser(String separator) + { + super(separator); + } + + // ---- AbstractTypeDescriptor.Parser implementation ---------------- + + @Override + protected JavaTypeDescriptor createTypeDescriptor( + String[] namespace, String name, boolean fArray, + List genericArgs) + { + return new JavaTypeDescriptor(namespace, name, fArray, genericArgs); + } + + @Override + protected JavaTypeDescriptor getStandardType(String type) + { + return STANDARD_TYPES.get(type); + } + + // ---- static initializer ------------------------------------------ + + private static final Map STANDARD_TYPES; + static + { + Map map = new HashMap<>(); + + // primitive types + map.put("boolean", new JavaTypeDescriptor("boolean")); + map.put("byte", new JavaTypeDescriptor("byte")); + map.put("char", new JavaTypeDescriptor("char")); + map.put("short", new JavaTypeDescriptor("short")); + map.put("int", new JavaTypeDescriptor("int")); + map.put("long", new JavaTypeDescriptor("long")); + map.put("float", new JavaTypeDescriptor("float")); + map.put("double", new JavaTypeDescriptor("double")); + + // reference types + map.put("java.lang.Object", OBJECT); + map.put("java.lang.String", new JavaTypeDescriptor(JAVA_LANG, "String")); + map.put("java.math.BigDecimal", new JavaTypeDescriptor(JAVA_MATH, "BigDecimal")); + map.put("java.math.BigInteger", new JavaTypeDescriptor(JAVA_MATH, "BigInteger")); + map.put("java.util.Date", new JavaTypeDescriptor(JAVA_UTIL, "Date")); + map.put("java.time.LocalDate", new JavaTypeDescriptor(JAVA_TIME, "LocalDate")); + map.put("java.time.LocalDateTime", new JavaTypeDescriptor(JAVA_TIME, "LocalDateTime")); + map.put("java.time.LocalTime", new JavaTypeDescriptor(JAVA_TIME, "LocalTime")); + map.put("java.time.OffsetDateTime", new JavaTypeDescriptor(JAVA_TIME, "OffsetDateTime")); + map.put("java.time.OffsetTime", new JavaTypeDescriptor(JAVA_TIME, "OffsetTime")); + map.put("java.time.ZonedDateTime", new JavaTypeDescriptor(JAVA_TIME, "ZonedDateTime")); + + STANDARD_TYPES = map; + } + } + + // ---- static members -------------------------------------------------- + + private static String[] JAVA_LANG = new String[] {"java", "lang"}; + private static String[] JAVA_MATH = new String[] {"java", "math"}; + private static String[] JAVA_UTIL = new String[] {"java", "util"}; + private static String[] JAVA_TIME = new String[] {"java", "time"}; + + /** + * A type descriptor for java.lang.Object. + */ + public static final JavaTypeDescriptor OBJECT = new JavaTypeDescriptor(JAVA_LANG, "Object"); + + /** + * An instance of a Java type name parser. + */ + public static final JavaTypeParser PARSER = new JavaTypeParser("."); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/handler/ClassFileHandler.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/handler/ClassFileHandler.java new file mode 100644 index 0000000000000..3185edfa36d62 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/handler/ClassFileHandler.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.lang.java.handler; + + +import com.oracle.coherence.common.schema.AbstractPropertyHandler; +import com.oracle.coherence.common.schema.AbstractTypeDescriptor; +import com.oracle.coherence.common.schema.AbstractTypeHandler; +import com.oracle.coherence.common.schema.CanonicalTypeDescriptor; +import com.oracle.coherence.common.schema.Schema; +import com.oracle.coherence.common.schema.lang.java.JavaProperty; +import com.oracle.coherence.common.schema.lang.java.JavaType; +import com.oracle.coherence.common.schema.lang.java.JavaTypeDescriptor; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; + + +/** + * A container class for a type and property handler that read and write type + * metadata from/to Java class file. + * + * @author as 2013.11.20 + */ +public class ClassFileHandler + { + // ---- inner class: ClassFileTypeHandler ------------------------------- + + /** + * Java type handler that reads and writes type metadata from/to Java class + * file. + */ + public static class ClassFileTypeHandler + extends AbstractTypeHandler + { + @Override + public void importType(JavaType type, ClassNode source, Schema schema) + { + JavaTypeDescriptor jtd = JavaTypeDescriptor.fromInternal(source.name); + if (!jtd.isNameEqual(type.getParent().getDescriptor())) + { + type.setDescriptor(jtd); + } + } + } + + // ---- inner class: ClassFilePropertyHandler --------------------------- + + /** + * Java property handler that reads and writes property metadata from/to + * Java class file. + */ + public static class ClassFilePropertyHandler + extends AbstractPropertyHandler + { + @Override + public void importProperty(JavaProperty property, FieldNode source, Schema schema) + { + CanonicalTypeDescriptor ctd = property.getParent().getType(); + if (ctd != null) + { + JavaType javaType = schema.getType(ctd, JavaType.class); + JavaTypeDescriptor jtd = JavaTypeDescriptor.fromInternal( + source.signature != null ? source.signature : source.desc); + if (javaType == null || jtd.isGenericType()) + { + property.setType(jtd); + } + else + { + AbstractTypeDescriptor desc = + javaType.getDescriptor() == null + ? ctd + : javaType.getDescriptor(); + + if (!jtd.isNameEqual(desc)) + { + property.setType(jtd); + } + } + } + } + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/handler/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/handler/package.html new file mode 100644 index 0000000000000..c8485b22fa3a8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/handler/package.html @@ -0,0 +1,5 @@ + +Defines handlers for reading and writing Java class files. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/package.html new file mode 100644 index 0000000000000..023f6bc0efb7f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/java/package.html @@ -0,0 +1,5 @@ + +Defines Java schema extensions. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/package.html new file mode 100644 index 0000000000000..60cf62b002e21 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/lang/package.html @@ -0,0 +1,5 @@ + +Defines schema extensions for different languages. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/package.html new file mode 100644 index 0000000000000..bd8bd2361707b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/package.html @@ -0,0 +1,6 @@ + +Contains the schema functionality to define and hold information +about extensible types. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/AsmUtils.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/AsmUtils.java new file mode 100644 index 0000000000000..c0c8c0de701dd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/AsmUtils.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + + +import java.util.ArrayList; +import java.util.List; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; + + +/** + * Various ASM helpers. + * + * @author as 2013.07.11 + */ +@SuppressWarnings("unchecked") +public class AsmUtils + { + /** + * Convert internal class name to Java class name. + * + * @param sInternalName internal name to convert + * + * @return Java class name + */ + public static String javaName(String sInternalName) + { + return sInternalName.replace('/', '.'); + } + + /** + * Convert Java class name to internal class name. + * + * @param sJavaName Java name to convert + * + * @return internal class name + */ + public static String internalName(String sJavaName) + { + return sJavaName.replace('.', '/'); + } + + /** + * Add specified annotation to the class. + * + * @param node the {@code ClassNode} to add annotation to + * @param annotation the annotation to add + */ + public static void addAnnotation(ClassNode node, AnnotationNode annotation) + { + if (node.visibleAnnotations == null) + { + node.visibleAnnotations = new ArrayList(); + } + node.visibleAnnotations.add(annotation); + } + + /** + * Add specified annotation to the field. + * + * @param node the {@code FieldNode} to add annotation to + * @param annotation the annotation to add + */ + public static void addAnnotation(FieldNode node, AnnotationNode annotation) + { + if (node.visibleAnnotations == null) + { + node.visibleAnnotations = new ArrayList(); + } + node.visibleAnnotations.add(annotation); + } + + /** + * Return {@code true} if the class has specified annotation. + * + * @param node the {@code ClassNode} to check + * @param annotationClass the class of the annotation to check for + * + * @return {@code true} if the class has specified annotation, + * {@code false} otherwise + */ + public static boolean hasAnnotation(ClassNode node, Class annotationClass) + { + return getAnnotation(node, annotationClass) != null; + } + + /** + * Return {@code true} if the field has specified annotation. + * + * @param node the {@code FieldNode} to check + * @param annotationClass the class of the annotation to check for + * + * @return {@code true} if the field has specified annotation, + * {@code false} otherwise + */ + public static boolean hasAnnotation(FieldNode node, Class annotationClass) + { + return getAnnotation(node, annotationClass) != null; + } + + /** + * Return the first of the specified annotations that is present on the + * class. + * + * @param node the {@code ClassNode} to get the annotation from + * @param annotationClasses the ordered list of annotations to search for + * + * @return the first of the specified annotations that is present on the + * class, or {@code null} if none of the specified annotations are + * present + */ + public static AnnotationNode getAnnotation(ClassNode node, Class... annotationClasses) + { + return findAnnotationNode(node.visibleAnnotations, annotationClasses); + } + + /** + * Return the first of the specified annotations that is present on the + * field. + * + * @param node the {@code FieldNode} to get the annotation from + * @param annotationClasses the ordered list of annotations to search for + * + * @return the first of the specified annotations that is present on the + * field, or {@code null} if none of the specified annotations are + * present + */ + public static AnnotationNode getAnnotation(FieldNode node, Class... annotationClasses) + { + return findAnnotationNode(node.visibleAnnotations, annotationClasses); + } + + /** + * Find first of the specified annotation classes that is present in the + * list of annotation nodes. + * + * @param annotations a list of annotation nodes + * @param annotationClasses the annotations to search for, in order + * + * @return first of the specified annotation classes that is present in the + * list of annotation nodes; {@code null} if none are present + */ + private static AnnotationNode findAnnotationNode(List annotations, Class[] annotationClasses) + { + if (annotations != null) + { + for (Class annotationClass : annotationClasses) + { + String desc = Type.getDescriptor(annotationClass); + for (AnnotationNode an : annotations) + { + if (desc.equals(an.desc)) + { + return an; + } + } + } + } + return null; + } + + /** + * Return a single attribute value from the annotation. + * + * @param an the annotation to get the attribute value from + * @param name the name of the annotation attribute + * + * @return the value of the specified attribute, or its default value if + * the attribute is optional and not present within the annotation + */ + public static Object getAnnotationAttribute(AnnotationNode an, String name) + { + if (an.values != null) + { + for (int i = 0; i < an.values.size(); i += 2) + { + if (name.equals(an.values.get(i))) + { + Object val = an.values.get(i + 1); + if (val instanceof String[]) + { + // enum + String[] asVal = (String[]) val; + String sEnumClass = Type.getType(asVal[0]).getClassName(); + String sEnumValue = asVal[1]; + try + { + return Enum.valueOf((Class) Class.forName(sEnumClass), sEnumValue); + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + } + } + return val; + } + } + } + + // annotation attribute was not explicitly specified, use default from the annotation class + try + { + Class clazz = AsmUtils.class.getClassLoader().loadClass( + an.desc.substring(1, an.desc.length() - 1).replace('/', '.')); + Object defaultValue = clazz.getMethod(name).getDefaultValue(); + return defaultValue instanceof Class + ? Type.getType((Class) defaultValue) + : defaultValue; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + /** + * Create an instance of the specified class using default constructor. + * + * @param clazz the class to create an instance of; must have default + * constructor + * @param the type of the created instance + * + * @return an instance of the specified class + */ + public static T createInstance(Class clazz) + { + try + { + return clazz.newInstance(); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/CapitalizationTransformer.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/CapitalizationTransformer.java new file mode 100644 index 0000000000000..d6c660445495b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/CapitalizationTransformer.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + + +/** + * Capitalizes the input string according to the specified capitalization mode. + * + * @author as 2013.11.21 + */ +public class CapitalizationTransformer + implements NameTransformer + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code CapitalizationTransformer} instance. + * + * @param mode the capitalization mode to apply + */ + public CapitalizationTransformer(Mode mode) + { + m_mode = mode; + } + + // ---- NameTransformer implementation ---------------------------------- + + @Override + public String transform(String source) + { + if (StringUtils.isEmpty(source)) + { + return null; + } + + switch (m_mode) + { + case UPPER: + return source.toUpperCase(); + case LOWER: + return source.toLowerCase(); + case FIRST_UPPER: + return source.substring(0, 1).toUpperCase() + + source.substring(1); + case FIRST_LOWER: + return source.substring(0, 1).toLowerCase() + + source.substring(1); + default: + return source; + } + } + + @Override + public String[] transform(String[] source) + { + for (int i = 0; i < source.length; i++) + { + source[i] = transform(source[i]); + } + + return source; + } + + // ---- data members ---------------------------------------------------- + + private Mode m_mode; + + // ---- inner enum: Mode ------------------------------------------------ + + /** + * Capitalization mode enum. + */ + public enum Mode + { + UPPER, + LOWER, + FIRST_UPPER, + FIRST_LOWER + // CAMEL + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/Logger.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/Logger.java new file mode 100644 index 0000000000000..0356d4205af31 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/Logger.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + +/** + * @author as 2014.10.15 + */ +public class Logger + { + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(Logger.class.getName()); + + public static void error(String sMessage) + { + LOG.severe(sMessage); + } + + public static void warn(String sMessage) + { + LOG.warning(sMessage); + } + + public static void info(String sMessage) + { + LOG.info(sMessage); + } + + public static void debug(String sMessage) + { + LOG.finer(sMessage); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/NameTransformer.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/NameTransformer.java new file mode 100644 index 0000000000000..a215f272d5adc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/NameTransformer.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + + +/** + * Defines an interface that various name transformers must implement. + * + * @author as 2013.11.21 + */ +public interface NameTransformer + { + /** + * Transform the specified source name. + * + * @param source the source name to transform + * + * @return the transformed name + */ + String transform(String source); + + /** + * Transform the specified source name components. + * + * @param source the source name components to transform + * + * @return the transformed name components + */ + String[] transform(String[] source); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/NameTransformerChain.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/NameTransformerChain.java new file mode 100644 index 0000000000000..8996f4ca202c6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/NameTransformerChain.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + + +/** + * The chain of {@link NameTransformer}s, where each transformer in the chain + * operates on the result of the previous transformer. + * + * @author as 2013.11.21 + */ +public class NameTransformerChain + implements NameTransformer + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code NameTransformerChain} instance. + */ + public NameTransformerChain() + { + } + + /** + * Construct {@code NameTransformerChain} instance. + * + * @param transformers the transformers that should be added to the chain + */ + public NameTransformerChain(NameTransformer... transformers) + { + m_transformers.addAll(Arrays.asList(transformers)); + } + + /** + * Construct {@code NameTransformerChain} instance. + * + * @param transformers the transformers that should be added to the chain + */ + public NameTransformerChain(Collection transformers) + { + m_transformers.addAll(transformers); + } + + // ---- NameTransformer implementation ---------------------------------- + + @Override + public String transform(String source) + { + String result = source; + for (NameTransformer t : m_transformers) + { + result = t.transform(result); + } + return result; + } + + @Override + public String[] transform(String[] source) + { + String[] result = source; + for (NameTransformer t : m_transformers) + { + result = t.transform(result); + } + return result; + } + + // ---- fluent API ------------------------------------------------------ + + /** + * Add specified transformer to this chain. + * + * @param transformer the transformer to add + * + * @return this {@code NameTransformerChain} + */ + public NameTransformerChain add(NameTransformer transformer) + { + m_transformers.add(transformer); + return this; + } + + /** + * Add a transformer that converts input string to uppercase to the chain. + * + * @return this {@code NameTransformerChain} + */ + public NameTransformerChain toUppercase() + { + return add(new CapitalizationTransformer(CapitalizationTransformer.Mode.UPPER)); + } + + /** + * Add a transformer that converts input string to lowercase to the chain. + * + * @return this {@code NameTransformerChain} + */ + public NameTransformerChain toLowercase() + { + return add(new CapitalizationTransformer(CapitalizationTransformer.Mode.LOWER)); + } + + /** + * Add a transformer that converts first letter of the input string to + * uppercase to the chain. + * + * @return this {@code NameTransformerChain} + */ + public NameTransformerChain firstLetterToUppercase() + { + return add(new CapitalizationTransformer(CapitalizationTransformer.Mode.FIRST_UPPER)); + } + + /** + * Add a transformer that converts first letter of the input string to + * lowercase to the chain. + * + * @return this {@code NameTransformerChain} + */ + public NameTransformerChain firstLetterToLowercase() + { + return add(new CapitalizationTransformer(CapitalizationTransformer.Mode.FIRST_LOWER)); + } + + /** + * Add a transformer that adds specified prefix to the input string + * to the chain. + * + * @return this {@code NameTransformerChain} + */ + public NameTransformerChain addPrefix(String prefix) + { + return add(new PrefixTransformer(prefix, PrefixTransformer.Mode.ADD)); + } + + /** + * Add a transformer that removes specified prefix from the input string + * to the chain. + * + * @return this {@code NameTransformerChain} + */ + public NameTransformerChain removePrefix(String prefix) + { + return add(new PrefixTransformer(prefix, PrefixTransformer.Mode.REMOVE)); + } + + /** + * Add a transformer that adds specified suffix to the input string + * to the chain. + * + * @return this {@code NameTransformerChain} + */ + public NameTransformerChain addSuffix(String suffix) + { + return add(new SuffixTransformer(suffix, SuffixTransformer.Mode.ADD)); + } + + /** + * Add a transformer that removes specified suffix from the input string + * to the chain. + * + * @return this {@code NameTransformerChain} + */ + public NameTransformerChain removeSuffix(String suffix) + { + return add(new SuffixTransformer(suffix, SuffixTransformer.Mode.REMOVE)); + } + + // ---- Data members ---------------------------------------------------- + + private List m_transformers = new ArrayList<>(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/PrefixTransformer.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/PrefixTransformer.java new file mode 100644 index 0000000000000..f5ebaf7e2b63b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/PrefixTransformer.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + + +/** + * A {@link NameTransformer} implementation that adds or removes specified + * prefix to/from an input string. + * + * @author as 2013.11.21 + */ +public class PrefixTransformer + implements NameTransformer + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code PrefixTransformer} instance. + * + * @param prefix the prefix to add or remove to/from an input string + * @param mode the transformer mode + */ + public PrefixTransformer(String prefix, Mode mode) + { + if (StringUtils.isEmpty(prefix)) + { + throw new IllegalArgumentException("prefix cannot be null or empty"); + } + + m_prefix = prefix; + m_mode = mode; + } + + // ---- NameTransformer implementation ---------------------------------- + + @Override + public String transform(String source) + { + if (StringUtils.isEmpty(source)) + { + return null; + } + + String prefix = m_prefix; + if (m_mode == Mode.ADD && !source.startsWith(prefix)) + { + return prefix + source; + } + else if (source.startsWith(prefix)) + { + return source.substring(prefix.length()); + } + + return source; + } + + @Override + public String[] transform(String[] source) + { + String prefix = m_prefix; + if (m_mode == Mode.ADD && !prefix.equals(source[0])) + { + String[] result = new String[source.length + 1]; + result[0] = prefix; + System.arraycopy(source, 0, result, 1, source.length); + return result; + } + else if (prefix.equals(source[0])) + { + String[] result = new String[source.length - 1]; + System.arraycopy(source, 1, result, 0, source.length - 1); + return result; + } + + return source; + } + + // ---- data members ---------------------------------------------------- + + private String m_prefix; + private Mode m_mode; + + // ---- inner enum: Mode ------------------------------------------------ + + /** + * Prefix mode enum. + */ + public enum Mode + { + ADD, + REMOVE + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/ResourceLoader.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/ResourceLoader.java new file mode 100644 index 0000000000000..caa19fbe3510f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/ResourceLoader.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Iterator; + + +/** + * A simple resource-loading facility. + *

+ * This class is similar to the JDK-provided ServiceLoader, but instead of + * discovering and instantiating service classes it will find the resources + * with a specified name. + * + * @author as 2013.11.21 + */ +public class ResourceLoader + implements Iterable + { + /** + * Construct ResourceLoader instance. + * + * @param resourceName the resource name + * @param loader the class loader to be used to load resources + */ + private ResourceLoader(String resourceName, ClassLoader loader) + { + try + { + m_resourceName = resourceName; + m_resources = loader.getResources(resourceName); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + /** + * Iterates over the discovered resources and returns an InputStream + * for each resource. + * + * @return resource iterator + */ + @Override + public Iterator iterator() + { + return new Iterator() + { + @Override + public boolean hasNext() + { + return m_resources.hasMoreElements(); + } + + @Override + public InputStream next() + { + try + { + return m_resources.nextElement().openStream(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + @Override + public void remove() + { + } + }; + } + + /** + * Creates a new resource loader for the given resource name and class + * loader. + * + * @param resourceName the resource name + * @param loader the class loader to be used to load resources + * + * @return A new resource loader + */ + public static ResourceLoader load(String resourceName, ClassLoader loader) + { + return new ResourceLoader(resourceName, loader); + } + + /** + * Creates a new resource loader for the given resource name, using the + * current thread's {@linkplain Thread#getContextClassLoader + * context class loader}. + * + *

An invocation of this convenience method of the form + * + *

+     * ResourceLoader.load(resourceName)
+ * + * is equivalent to + * + *
+     * ResourceLoader.load(resourceName,
+     *                    Thread.currentThread().getContextClassLoader())
+ * + * @param resourceName the resource name + * + * @return A new resource loader + */ + public static ResourceLoader load(String resourceName) { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + return ResourceLoader.load(resourceName, cl); + } + + /** + * Creates a new resource loader for the given resource name, using the + * extension class loader. + * + *

This convenience method simply locates the extension class loader, + * call it extClassLoader, and then returns + * + *

+ *
+     * ServiceLoader.load(resourceName, extClassLoader)
+     *     
+ *
+ * + *

If the extension class loader cannot be found then the system class + * loader is used; if there is no system class loader then the bootstrap + * class loader is used. + * + *

This method is intended for use when only installed providers are + * desired. The resulting loader will only find and load resources that + * have been installed into the current Java virtual machine; resources on + * the application's class path will be ignored. + * + * @param resourceName the resource name + * + * @return A new resource loader + */ + public static ResourceLoader loadInstalled(String resourceName) { + ClassLoader cl = ClassLoader.getSystemClassLoader(); + ClassLoader prev = null; + while (cl != null) { + prev = cl; + cl = cl.getParent(); + } + return ResourceLoader.load(resourceName, prev); + } + + /** + * Returns a string describing this resource loader. + * + * @return A descriptive string + */ + public String toString() + { + return "ResourceLoader{" + + "resourceName='" + m_resourceName + '\'' + + '}'; + } + + // ---- Data members ---------------------------------------------------- + + /** + * Resource name to search for. + */ + private final String m_resourceName; + + /** + * Discovered resources. + */ + private final Enumeration m_resources; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/StringUtils.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/StringUtils.java new file mode 100644 index 0000000000000..103a6cbb717ae --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/StringUtils.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + +import java.util.ArrayList; +import java.util.List; + + +/** + * Various String helpers. + * + * @author as 2013.07.12 + */ +public class StringUtils + { + /** + * Return {@code true} if the specified string is empty or {@code null}. + * + * @param text the string to check + * + * @return {@code true} if the specified string is empty or {@code null} + */ + public static boolean isEmpty(String text) + { + return text == null || text.length() == 0; + } + +/* + public static String join(String[] strings, String separator) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < strings.length; i++) + { + if (i > 0) + { + sb.append(separator); + } + sb.append(strings[i]); + } + return sb.toString(); + } +*/ + + /** + * Splits this string around matches of the given separator. + * Unlike {@link String#split(String)}, this method does not + * treat the separator string as a regex + * + * @param text the text to split + * @param separator the separator string + * + * @return the array of strings computed by splitting this string + * around matches of the given separator + */ + public static String[] split(String text, String separator) + { + List strings = new ArrayList<>(); + int start = 0; + int n; + + while ((n = text.indexOf(separator, start)) > 0) + { + strings.add(text.substring(start, n)); + start = n + separator.length(); + } + + strings.add(text.substring(start, text.length())); + return strings.toArray(new String[strings.size()]); + } + + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/SuffixTransformer.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/SuffixTransformer.java new file mode 100644 index 0000000000000..7e1eb64c3347b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/SuffixTransformer.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + + +/** + * A {@link NameTransformer} implementation that adds or removes specified + * suffix to/from an input string. + * + * @author as 2013.11.21 + */ +public class SuffixTransformer + implements NameTransformer + { + // ---- constructors ---------------------------------------------------- + + /** + * Construct {@code PrefixTransformer} instance. + * + * @param suffix the suffix to add or remove to/from an input string + * @param mode the transformer mode + */ + public SuffixTransformer(String suffix, Mode mode) + { + if (StringUtils.isEmpty(suffix)) + { + throw new IllegalArgumentException("suffix cannot be null or empty"); + } + + m_suffix = suffix; + m_mode = mode; + } + + // ---- NameTransformer implementation ---------------------------------- + + @Override + public String transform(String source) + { + if (StringUtils.isEmpty(source)) + { + return null; + } + + String suffix = m_suffix; + if (m_mode == Mode.ADD && !source.endsWith(suffix)) + { + return source + suffix; + } + else if (source.endsWith(suffix)) + { + return source.substring(0, source.length() - suffix.length()); + } + + return source; + } + + @Override + public String[] transform(String[] source) + { + String suffix = m_suffix; + if (m_mode == Mode.ADD && !suffix.equals(source[source.length - 1])) + { + String[] result = new String[source.length + 1]; + result[source.length] = suffix; + System.arraycopy(source, 0, result, 0, source.length); + return result; + } + else if (suffix.equals(source[source.length - 1])) + { + String[] result = new String[source.length - 1]; + System.arraycopy(source, 0, result, 0, source.length - 1); + return result; + } + + return source; + } + + // ---- data members ---------------------------------------------------- + + private String m_suffix; + private Mode m_mode; + + // ---- inner enum: Mode ------------------------------------------------ + + /** + * Suffix mode enum. + */ + public enum Mode + { + ADD, + REMOVE + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/XmlUtils.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/XmlUtils.java new file mode 100644 index 0000000000000..2d7a63f065907 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/XmlUtils.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2020 Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.schema.util; + + +import java.util.ArrayList; +import java.util.List; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.NodeList; + + +/** + * Various XML helpers. + * + * @author as 2013.07.12 + */ +public class XmlUtils + { + /** + * Return the first child element with a given name and namespace. + * + * @param parent the element to get the child from + * @param ns the namespace of the child element + * @param name the name of the child element + * + * @return the first child element with a given name and namespace, or + * {@code null} if no such child elements exist + */ + public static Element getChildElement(Element parent, String ns, String name) + { + NodeList nodes = parent.getElementsByTagNameNS(ns, name); + if (nodes != null && nodes.getLength() > 0) + { + return (Element) nodes.item(0); + } + return null; + } + + /** + * Convert the DOM {@code NodeList} to a list of elements. + * + * @param nodes the {@code NodeList} to convert + * + * @return the list of elements from the specified {@code NodeList} + */ + public static List toElementList(NodeList nodes) + { + List elements = new ArrayList<>(nodes.getLength()); + for (int i = 0; i < nodes.getLength(); i++) + { + elements.add((Element) nodes.item(i)); + } + return elements; + } + + /** + * Convert the DOM {@code NamedNodeMap} to a list of attributes. + * + * @param attributeMap the {@code NamedNodeMap} to convert + * + * @return the list of attributes from the specified {@code NamedNodeMap} + */ + public static List toAttributeList(NamedNodeMap attributeMap) + { + List attributes = new ArrayList<>(attributeMap.getLength()); + for (int i = 0; i < attributeMap.getLength(); i++) + { + attributes.add((Attr) attributeMap.item(i)); + } + return attributes; + } + + /** + * Return the value of a boolean XML attribute. + *

+ * If the attribute is not present within the specified element, this method + * will return {@code false}. + * + * @param element the element to get the attribute value from + * @param name the name of the boolean attribute to get + * + * @return the boolean value of the specified attribute, or {@code false} + * if the attribute is not present within the specified element + */ + public static boolean getBooleanAttribute(Element element, String name) + { + String value = element.getAttribute(name); + return !StringUtils.isEmpty(value) && Boolean.parseBoolean(value); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/package.html new file mode 100644 index 0000000000000..5222deb9f1c16 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/schema/util/package.html @@ -0,0 +1,5 @@ + +Defines schema utility classes. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/AssociationPile.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/AssociationPile.java new file mode 100644 index 0000000000000..4dbc6539c136c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/AssociationPile.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import com.oracle.coherence.common.base.Associated; + +/** + * The AssociationPile defines an abstract data structure holding elements in a + * loosely ordered way with a queue-like contract that respects the possibility + * that some elements can be {@link Associated associated} with one another. + * Elements associated to the same {@link Associated#getAssociatedKey() + * associated key} will maintain FIFO ordering, but may be re-ordered with + * respect to elements with different associations. + *

+ * Moreover, the AssociationPile assumes that {@link #poll polled} elements are + * being processed in parallel on multiple threads and prevents polling of an + * element with an association until any previously polled associated element + * has been explicitly {@link #release released}. + *

+ * Note: any element returned by the {@link #poll} methods must be passed + * to a subsequent {@link #release} call. + * + * @param the element type + * + * @author gg 2013.02.27 + */ +public interface AssociationPile + { + /** + * Add the specified element to the pile. + * + * @param element element to be added + * + * @return true if the element was accepted; false if it was rejected for + * any reason + */ + public boolean add(T element); + + /** + * Remove the first element without any association or with an uncontended + * association from the front of this pile. An element is considered + * uncontended if any of the following holds true: + *

    + *
  • it has no association; + *
  • it has an association, but no other associated element has been + * polled; + *
  • it has an association, but all associated element that have been + * previously polled, have been released. + *
+ * If the pile is empty or all elements are contended, null is returned. + * When the caller has finished processing the returned element, it must + * release it by calling the {@link #release} method. + *

+ * There is a special {@link #ASSOCIATION_ALL} object that is associated with + * any non-null associations. In regard to this association, the previous + * rules apply. As a result, the following holds true: + *

    + *
  • an element associated with {@link #ASSOCIATION_ALL} is considered + * contended if any elements with a non-null association have been + * previously polled, but have not yet been released; + *
  • any elements that have any association which were added to the pile + * after an element associated with with {@link #ASSOCIATION_ALL} + * are considered to be contended and will remain contended until that + * element is released. + *
+ * + * @return the first uncontended element in the front of this pile or null + * otherwise + */ + public T poll(); + + /** + * Release a previously polled element. This will allow an associated + * element (if it exists) in this pile to be {@link #poll polled}. + * + * @param element an element to release + */ + public void release(T element); + + /** + * Determine the number of elements in this pile. + * + * @return the number of elements in this pile + */ + public int size(); + + /** + * Check whether or not there are any uncontended elements in this pile. + *

+ * Note: implementations are permitted to return "true" if the pile + * contains only contended elements; however, implementations must not + * return "false" if the pile contains one or more uncontended elements. + * + * @return true iff there are potentially uncontended elements + */ + public boolean isAvailable(); + + /** + * A special association value that is associated with all elements that + * have any associations. + */ + public final static Object ASSOCIATION_ALL = new Object(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/AutoLock.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/AutoLock.java new file mode 100644 index 0000000000000..1f7cc28af49e8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/AutoLock.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import com.oracle.coherence.common.base.Blocking; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + + +/** + * A wrapper lock which supports AutoCloseable via {@link #acquire} and thus supports try with resources. + * Additionally the AutoLock and Sentry can contain knowledge of the entity which they protect, this is + * obtainable via {@link #getResource()}. + *

+ * Example usage: + *

try (Sentry<Foo> sentry = f_lock.acquire()) { Foo foo = sentry.getResource(); ... }

+ * + * Note: the AutoLock is {@link com.oracle.coherence.common.base.Timeout Timeout} compatible. + * + * @author mf 2014.10.02 + */ +public class AutoLock + implements Lock + { + /** + * Construct an AutoLock around the specified delegate. + * + * The lock status of the delegate will not be changed as part of this operation. + * + * @param delegate the delegate + */ + public AutoLock(Lock delegate) + { + this(delegate, null); + } + + /** + * Construct an AutoLock around the specified delegate. + * + * The lock status of the delegate will not be changed as part of this operation. + * + * @param delegate the delegate + * @param resource the associated resource + */ + public AutoLock(Lock delegate, R resource) + { + f_lockDelegate = delegate; + f_resource = resource; + } + + /** + * Acquire the lock and return the auto-closeable Sentry. + * + * @return the auto-closeable Sentry which will unlock on close + */ + public Sentry acquire() + { + f_lockDelegate.lock(); + return f_sentry; + } + + /** + * Return the resource associated with the lock. + * + * @return the resource + */ + public R getResource() + { + return f_resource; + } + + @Override + public void lock() + { + f_lockDelegate.lock(); + } + + @Override + public void lockInterruptibly() + throws InterruptedException + { + Blocking.lockInterruptibly(f_lockDelegate); + } + + @Override + public boolean tryLock() + { + return f_lockDelegate.tryLock(); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) + throws InterruptedException + { + return Blocking.tryLock(f_lockDelegate, time, unit); + } + + @Override + public void unlock() + { + f_lockDelegate.unlock(); + } + + @Override + public Condition newCondition() + { + return f_lockDelegate.newCondition(); + } + + // ----- inner class: Sentry ------------------------------------------ + + /** + * AutoCloseable which unlocks this lock. + */ + public static class Sentry + implements com.oracle.coherence.common.util.Sentry + { + /** + * Construct a Sentry for a given AutoLock. + * + * @param parent the parent + */ + protected Sentry(AutoLock parent) + { + f_parent = parent; + } + + @Override + public void close() + { + f_parent.unlock(); + } + + /** + * Return the resource associated with the lock. + * + * @return the resource. + */ + public V getResource() + { + return f_parent.getResource(); + } + + /** + * The associated lock. + */ + protected final AutoLock f_parent; + } + + + // ----- data members --------------------------------------------------- + + /** + * The delegate lock. + */ + protected Lock f_lockDelegate; + + /** + * The associated resource. + */ + protected final R f_resource; + + /** + * The associated Sentry. + */ + protected final Sentry f_sentry = new Sentry<>(this); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Bandwidth.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Bandwidth.java new file mode 100644 index 0000000000000..771e78feb86b1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Bandwidth.java @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link Bandwidth} represents an amount of memory (measured in bits) being + * transferred per second. + *

+ * Measurements are based on the decimal system as outlined by the IEC. + *

+ * eg: In this implementation 1 kbps = 1000 bits per second, not 1024 bits per second + * + * @author cp, bko 2011.07.15 + */ +public class Bandwidth + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a {@link Bandwidth} by parsing the specified {@link String}. + * + * @param s The {@link String} containing the definition of a {@link Bandwidth} + */ + public Bandwidth(String s) + { + s = (s == null) + ? null + : s.trim(); + + if ((s == null) || s.isEmpty()) + { + throw new IllegalArgumentException("An empty or null string was provided. Expected a bandwidth"); + } + + if (!s.equals("0")) + { + Matcher matcher = REGEX_PATTERN.matcher(s); + + if (!matcher.matches()) + { + throw new IllegalArgumentException(String.format("The specified %s [%s] is invalid.", this.getClass().getName(), s)); + } + + // determine the rate (using group 3) + Rate rate = Rate.fromSuffix(matcher.group(3)); + + // determine the magnitude (using group 2) + Magnitude magnitude = Magnitude.fromSuffix(matcher.group(2)); + + // determine the amount (using group 1) + double cUnits = Double.valueOf(matcher.group(1)); + + m_cBits = rate.toBits(Math.round(cUnits * magnitude.getFactor())); + } + } + + /** + * Construct a {@link Bandwidth} given a specified units and {@link Rate}. + *

+ * As the amount is a double precision value, the resulting {@link Bandwidth} will be rounded to the closest unit. + * + * @param cUnits The number of units of the {@link Magnitude} and {@link Rate}. + * @param rate The {@link Rate} + */ + public Bandwidth(double cUnits, Rate rate) + { + assert cUnits >= 0.0; + m_cBits = rate.toBits(Math.round(cUnits)); + } + + /** + * Construct a {@link Bandwidth} given a specified units and {@link Rate}. + * + * @param cUnits The number of units of the {@link Magnitude} and {@link Rate}. + * @param rate The {@link Rate} + */ + public Bandwidth(int cUnits, Rate rate) + { + this((long) cUnits, rate); + } + + /** + * Construct a {@link Bandwidth} give a specified number of bytes. + * + * @param cBytes The number of bytes in the memory size + * @param rate The {@link Rate} + */ + public Bandwidth(long cBytes, Rate rate) + { + assert cBytes >= 0; + m_cBits = rate.toBits(cBytes); + } + + // ----- Bandwidth methods --------------------------------------------- + + /** + * Obtain the {@link Bandwidth} as a value in the specified {@link Magnitude}. + * + * @param magnitude The {@link Magnitude} + * + * @return The maximum number of units of the specified {@link Magnitude} + * for the {@link Bandwidth}. + */ + public long as(Magnitude magnitude) + { + return m_cBits / magnitude.getFactor(); + } + + /** + * Obtain the {@link Bandwidth} as a value in the specified {@link Rate}. + * + * @param rate The {@link Rate} + * + * @return The number of units of the specified {@link Rate} + * for the {@link Bandwidth}. + */ + public long as(Rate rate) + { + return rate.fromBits(m_cBits); + } + + /** + * Obtains a {@link String} representation of the {@link Bandwidth} (in {@link Rate#BITS}). + * + * @param fExact Indicates an exact value is required or if a rounded value will suffice. + * + * @return A {@link String} + */ + public String toString(boolean fExact) + { + Magnitude magnitude = Magnitude.BASE; + long nBits = m_cBits; + + // find the highest magnitude to represent the number appropriately + while ((magnitude.next() != null) && (nBits >= magnitude.next().getFactor()) + && ((fExact && (nBits % magnitude.next().getFactor()) % (magnitude.next().getFactor() / 4) == 0) ||!fExact)) + { + magnitude = magnitude.next(); + } + + long cMagnitudeUnits = nBits / magnitude.getFactor(); + long nRemainder = nBits % magnitude.getFactor(); + int cSignificantDigits = 3; + StringBuilder bldrString = new StringBuilder(); + + bldrString.append(cMagnitudeUnits); + + int cDigits = bldrString.length(); + int cRemainingDigits = cSignificantDigits - cDigits; + + if ((cRemainingDigits > 0) && (nRemainder > 0)) + { + int nSignificanceFactor = (int) Math.pow(10, cRemainingDigits); + long nDecimals = (long) Math.floor(nRemainder * (double) nSignificanceFactor / magnitude.getFactor()); + + if (nDecimals > 0) + { + bldrString.append("."); + + int cLeadingZeros = cRemainingDigits - (int)Math.log10(nDecimals) - 1; + for (int i = 0; i < cLeadingZeros; i++) + { + bldrString.append('0'); + } + + bldrString.append(nDecimals); + } + } + + bldrString.append(magnitude.getSuffix()); + bldrString.append("b/s"); + + return bldrString.toString(); + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() + { + return 31 + (int) (m_cBits ^ (m_cBits >>> 32)); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) + { + return (this == obj) || ((obj != null) && (obj instanceof Bandwidth) && ((Bandwidth) obj).m_cBits == m_cBits); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return toString(false); + } + + // ----- Magnitude Enumeration ------------------------------------------ + + /** + * A {@link Magnitude} of {@link Bandwidth}. + */ + public enum Magnitude + { + BASE(1L, ""), + KILO(1000L, "kilo"), + MEGA(1000000L, "mega"), + GIGA(1000000000L, "giga"), + TERA(1000000000000L, "tera"), + PETA(1000000000000000L, "peta"), + EXA(1000000000000000000L, "exa"); + + // ----- constructors ----------------------------------------------- + + /** + * Construct a {@link Magnitude} + * + * @param nFactor The factor of the {@link Magnitude} + * @param sDescription The description of the {@link Magnitude} + */ + Magnitude(long nFactor, String sDescription) + { + DESCRIPTION = sDescription.trim(); + SUFFIX = DESCRIPTION.isEmpty() + ? "" + : DESCRIPTION.substring(0, 1); + FACTOR = nFactor; + } + + // ----- Magnitude methods ------------------------------------------ + + /** + * Obtain the name of the {@link Magnitude}. For example, a kilo has the + * description "kilo". + * + * @return The {@link Magnitude}'s description + */ + public String getDescription() + { + return DESCRIPTION; + } + + /** + * Obtain the suffix of the {@link Magnitude}. + * + * @return A {@link String} + */ + public String getSuffix() + { + return SUFFIX; + } + + /** + * Obtain the factor of the {@link Magnitude}. + * + * @return The factor + */ + public long getFactor() + { + return FACTOR; + } + + /** + * Determine if the passed suffix is compatible with this {@link Magnitude}'s suffix, ignoring case. + * + * @param s The suffix to test + * + * @return true iff the passed string is compatible with the suffix of this {@link Magnitude}. + */ + public boolean isSuffix(String s) + { + return s.equalsIgnoreCase(SUFFIX); + } + + /** + * Obtain the next order of {@link Magnitude} (above this one). + * + * @return The next order of {@link Magnitude} above this one or null if this is + * the {@link #HIGHEST}. + */ + public Magnitude next() + { + if (this.equals(Magnitude.HIGHEST)) + { + return null; + } + else + { + return Magnitude.VALUES[this.ordinal() + 1]; + } + } + + /** + * Obtain the previous order of {@link Magnitude} (above this one). + * + * @return The previous order of {@link Magnitude} or null if this is + * the {@link #LOWEST}. + */ + public Magnitude previous() + { + if (this.equals(Magnitude.LOWEST)) + { + return null; + } + else + { + return Magnitude.VALUES[this.ordinal() - 1]; + } + } + + // ----- helpers ---------------------------------------------------- + + /** + * Determine the {@link Magnitude} given the specified suffix. + * + * @param sSuffix The proposed suffix + * + * @return A {@link Magnitude} with the specified suffix + */ + public static Magnitude fromSuffix(String sSuffix) + { + sSuffix = sSuffix.trim(); + + if (sSuffix.length() == 0) + { + return Magnitude.BASE; + } + else if (sSuffix.length() > 0) + { + for (Magnitude magnitude : Magnitude.VALUES) + { + if (magnitude.isSuffix(sSuffix)) + { + return magnitude; + } + } + } + + throw new IllegalArgumentException(String.format("Unknown %s suffix [%s]", Magnitude.class.getName(), sSuffix)); + } + + // ----- data members ----------------------------------------------- + + /** + * The description of this {@link Magnitude}. For example, a kilo has the name "kilo". + */ + private final String DESCRIPTION; + + /** + * The number of order of the magnitude. For example, a kilo is 1000. + */ + private final long FACTOR; + + /** + * The suffix that represents this {@link Magnitude}. For example, a kilobyte has the suffix "K". + */ + private final String SUFFIX; + + // ----- constants -------------------------------------------------- + + /** + * Cached copy of the values array to avoid garbage creation + */ + private static final Magnitude[] VALUES = Magnitude.values(); + + /** + * The lowest defined order of {@link Magnitude}. + */ + public final static Magnitude LOWEST = Magnitude.VALUES[0]; + + /** + * The highest defined order of {@link Magnitude}. + */ + public final static Magnitude HIGHEST = Magnitude.VALUES[Magnitude.VALUES.length - 1]; + } + + // ----- Rate Enumeration ----------------------------------------------- + + /** + * A {@link Rate} of a {@link Bandwidth} per second. + */ + public enum Rate + { + BITS(0, "b"), + BYTES(3, "B"); + + // ----- constructors ----------------------------------------------- + + /** + * Construct a {@link Rate}. + * + * @param cShift The binary (left) shift that the {@link Rate} requires to convert to a number of bits + * @param sSuffix The suffix for the {@link Rate} + */ + Rate(int cShift, String sSuffix) + { + SHIFT = cShift; + SUFFIX = sSuffix; + } + + // ----- Rate methods ----------------------------------------------- + + /** + * Determine the name of the {@link Rate}. + * + * @return "bits" or "bytes" + */ + public String getDescription() + { + return name().toLowerCase(); + } + + /** + * Obtain the suffix that identifies the {@link Rate}. + * + * @return "b" for "bits", or "B" for "bytes" + */ + public String getSuffix() + { + return SUFFIX; + } + + /** + * Convert the specified number of units of this {@link Rate} into a bits {@link Rate}. + * + * @param cUnits The number of units of this {@link Rate} + * + * @return The number of {@link Rate#BITS} units. + */ + public long toBits(long cUnits) + { + return cUnits << SHIFT; + } + + /** + * Convert the specified number of bits to units of this {@link Rate}. + * + * @param cBits The number of bits. + * + * @return The number of units of this {@link Rate}. + */ + public long fromBits(long cBits) + { + return cBits >> SHIFT; + } + + // ----- helpers ---------------------------------------------------- + + /** + * Convert a number of units of the specified {@link Rate} to another {@link Rate}. + * + * @param cUnits The number of units + * @param rateFrom The {@link Rate} to convert from + * @param rateTo The {@link Rate} to convert to + * + * @return the number of bits + */ + public static long convert(long cUnits, Rate rateFrom, Rate rateTo) + { + if (rateFrom.equals(rateTo)) + { + return cUnits; + } + else + { + return rateTo.fromBits(rateFrom.toBits(cUnits)); + } + } + + /** + * Determine the {@link Rate} given the specified suffix. + * + * @param sSuffix The proposed suffix + * + * @return A {@link Rate} with the specified suffix + */ + public static Rate fromSuffix(String sSuffix) + { + sSuffix = sSuffix.trim(); + + if (sSuffix.length() == 0) + { + return Rate.BITS; + } + else if (sSuffix.length() > 0) + { + for (Rate rate : Rate.VALUES) + { + if (rate.getSuffix().equals(sSuffix)) + { + return rate; + } + } + } + + throw new IllegalArgumentException(String.format("Unknown %s suffix [%s]", Rate.class.getName(), sSuffix)); + } + + // ----- data members ----------------------------------------------- + + /** + * Cached copy of the values array to avoid garbage creation + */ + private static final Rate[] VALUES = Rate.values(); + + /** + * The binary shift that the {@link Rate} requires to convert a number + * of units to or from the corresponding number of bytes. + */ + private final int SHIFT; + + /** + * The one-character suffix for the {@link Rate}. + */ + private final String SUFFIX; + } + + // ----- data members --------------------------------------------------- + + /** + * The number of {@link Rate#BITS} in the {@link Bandwidth}. + */ + private long m_cBits; + + // ----- constants ------------------------------------------------------ + + /** + * The pre-compiled regular expression {@link Pattern} to match + * a {@link Bandwidth} specified as a {@link String}. + */ + private static final Pattern REGEX_PATTERN = Pattern.compile("([0-9]+(?:\\.[0-9]+)?)([kKMmGgTtPpEe]?)/?([Bb]?)[Pp/]?[Ss]"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/CommonMonitor.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/CommonMonitor.java new file mode 100644 index 0000000000000..aba424eee1504 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/CommonMonitor.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +/** + * Common monitors allow for a low-cost means to reduce contention by + * spreading synchronization over a large number of monitors. An example + * usage would be to produce an "atomic array" without utilizing the + * Java 1.5 java.util.concurrent features. For instance to atomically change + * an element within an array which is being simultaneously updated by + * multiple threads: + *

+ * synchronized (getCommonMonitor(System.identityHashCode(aoShared) + i))
+ *     {
+ *     oOld = aoShared[i];
+ *     aoShared[i] = oNew;
+ *     }
+ * 
+ * With this approach many threads may concurrently access various array + * elements without having to synchronize on the array itself, and contend + * with each other. The use of common monitors also avoids the overhead of + * allocating a unique monitor per index. This example additionally makes + * use of the array's identity hash code to avoid frequent collisions against + * other atomic arrays for the same indices. + *

+ * As they are shared, these monitors will apply to any number of unrelated + * entities, and as such certain precautions must be employed when using + * them. + *

    + *
  • The holder of a common monitor MUST not synchronize on any other + * common monitor. Failure to adhere to this precaution will result in + * a deadlock. + *
  • Notifications on a common monitor MUST use notifyAll() rather then + * notify(), as there may be unrelated threads waiting for notification + * on the same monitor which could consume a single notification. Thus + * the only way to ensure that the desired thread does receive + * notification is to notify all threads waiting on the monitor. + *
  • Threads waiting for a notification must protect themselves against + * spurious style wake-ups. While this is a general, though often + * overlooked part of the normal use of notification, with common + * monitors it is far more likely that a thread will be notified due to + * an unrelated event. + *
  • A thread which is holding synchronization on a common monitor should + * avoid blocking operations as this could block unrelated threads which + * happen to be utilizing the same common monitor. + *
+ * The ideal number of common monitors in a JVM is one per concurrently + * executing thread. As this number is generally unknown the actual number + * of monitors is pre-sized based on a multiple of the number of processors + * available to the JVM. The value may also be manually specified via the + * com.oracle.common.util.CommonMonitor.monitors system property. + * + * @author mf 2007.07.05 + */ +public final class CommonMonitor + { + // ----- CommonMonitor interface ------------------------------------- + + /** + * Ensure all writes made prior to this call to be visible to any thread + * which calls the corresponding readBarrier method. + */ + public final void writeBarrier() + { + // read from a non-volatile to avoid a read barrier + int n = ++m_nonvolatile; + m_barrier = n == -1 ? 0 : n; // write barrier + } + + /** + * Ensure all reads made after this call will have visibility to any + * writes made prior to a corresponding call to writeBarrier on another + * thread. + */ + public final void readBarrier() + { + if (m_barrier == -1) // read barrier + { + // this check should ensure that the compiler does not optimize + // out the read + throw new IllegalStateException(); + } + } + + + // ----- static accessors -------------------------------------------- + + /** + * Return the common monitor associated with the specified integer value. + * + * @param i the common monitor identifier + * + * @return the associated monitor + */ + public static CommonMonitor getCommonMonitor(int i) + { + CommonMonitor[] aMonitors = MONITORS; + return aMonitors[(i & 0x7FFFFFFF) % aMonitors.length]; + } + + /** + * Return the common monitor associated with the specified long value. + * + * @param l the common monitor identifier + * + * @return the associated monitor + * + * @see #getCommonMonitor(int) + */ + public static CommonMonitor getCommonMonitor(long l) + { + CommonMonitor[] aMonitors = MONITORS; + return aMonitors[(int) ((l & 0x7FFFFFFFFFFFFFFFL) % aMonitors.length)]; + } + + /** + * Return the common monitor associated with the specified object based on + * its identity hashCode. + * + * @param o the object to obtain a common monitor for + * + * @return the associated monitor + * + * @see #getCommonMonitor(int) + */ + public static CommonMonitor getCommonMonitor(Object o) + { + return getCommonMonitor(System.identityHashCode(o)); + } + + + // ----- constants ------------------------------------------------------ + + /** + * An array of common monitors. + * + * @see #getCommonMonitor(int) + */ + private static final CommonMonitor[] MONITORS; + static + { + // The total overhead of a common monitor is 8 bytes for a 32 bit JVM + // or 12 bytes for a 64 bit JVM. Ideally the number of monitors will + // just exceed the number of active threads. Considering that under- + // allocating will result in increased contention, while over-allocating + // is not particularly expensive. The default is 512 monitors per CPU, or + // approximately 4-6KB per CPU. + String sMonitors = System.getProperty(CommonMonitor.class.getName() + ".monitors"); + int cMonitors = Runtime.getRuntime().availableProcessors() * 512; + if (sMonitors != null) + { + int c = Integer.parseInt(sMonitors); + if (c < cMonitors / 8) + { + // _trace not available yet + System.err.println("The specified number of " + c + + " common monitors is significantly lower then the recommended " + + cMonitors + " and may result in performance degradation."); + } + cMonitors = c; + } + + MONITORS = new CommonMonitor[cMonitors]; + for (int i = 0; i < cMonitors; ++i) + { + MONITORS[i] = new CommonMonitor(); + } + } + + // ----- data members ------------------------------------------------ + + protected int m_nonvolatile; + protected volatile int m_barrier; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/ConcurrentAssociationPile.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/ConcurrentAssociationPile.java new file mode 100644 index 0000000000000..4819cc65fa240 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/ConcurrentAssociationPile.java @@ -0,0 +1,718 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import com.oracle.coherence.common.base.Associated; +import com.oracle.coherence.common.base.ConcurrentNotifier; + +import com.oracle.coherence.common.collections.ConcurrentHashMap; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; + +/** + * A concurrent implementation of an AssociationPile. + * + * @param the pile type + * @param
the association type + * + * @author mf 2018.04.10 + */ +public class ConcurrentAssociationPile + extends ConcurrentNotifier + implements AssociationPile + { + @Override + public boolean add(T value) + { + f_cValues.increment(); + + A key = getAssociation(value); + if (key == null) + { + return addAvailable(value); + } + else if (key == ASSOCIATION_ALL) + { + return handleAddAll(value); + } + else + { + try (Sentry sentry = f_gateAll.enter()) + { + CloseableQueue> queue = m_lPosNextAll == Long.MAX_VALUE + ? f_mapQueueDeferred.putIfAbsent(key, EMPTY_QUEUE) + : EMPTY_QUEUE; + + return queue == null + ? addAvailable(value) // common path + : handleAssociatedAdd(key, value, queue); + } + } + } + + @Override + public T poll() + { + // nodes could be in either available queue, we have to check both (at least over time) or we could let one starve + // note, that polling from an empty SkipList is quite cheap. We could try to be extra fair by peeking into + // both and polling from the one with the older Node. This would be extra expense which isn't required for + // correctness, and SkipList also doesn't have a peek operation. + + boolean fPriority = (++m_cPolls & 1) == 0; + Node node = null; + T value; + + if (fPriority) + { + node = f_queueAvailablePriority.pollFirst(); + value = node == null ? f_queueAvailable.poll() : node.getValue(); + } + else + { + value = f_queueAvailable.poll(); + if (value == null) + { + node = f_queueAvailablePriority.pollFirst(); + value = node == null ? null : node.getValue(); + } + } + + if (value != null) + { + f_cValues.decrement(); + } + + return value; + } + + @Override + public void release(T value) + { + A key = getAssociation(value); + if (key == null) + { + // we don't have any cleanup work for unassociated + } + else if (key == ASSOCIATION_ALL) + { + handleReleaseAll(); + } + else // common path + { + // release of an association must either move the next associated node to available or remove the empty queue + try (Sentry sentry = f_gateAll.enter()) + { + Node node = f_mapQueueDeferred.remove(key, EMPTY_QUEUE) + ? null // common path + : pollOrRemoveAssociation(key); + + if (m_lPosNextAll < Long.MAX_VALUE) // must be checked even if node is null + { + handlePostReleaseWithPendingAll(node); + } + else if (node != null) + { + addAvailable(node); + } + // else; common path + } + catch (IllegalArgumentException e) + { + throw new IllegalArgumentException("while releasing " + value + " of " + value.getClass(), e); + } + } + } + + @Override + public int size() + { + return Math.max(0, f_cValues.intValue()); + } + + @Override + public boolean isAvailable() + { + return !f_queueAvailable.isEmpty() || !f_queueAvailablePriority.isEmpty(); + } + + // ---- MultiWaiterMultiNotifier interface ------------------------------ + + @Override + protected boolean isReady() + { + return isAvailable(); + } + + // ---- Object interface ------------------------------------------------ + + @Override + public String toString() + { + return getClass().getCanonicalName() + + " size=" + size() + + ", available=" + isAvailable() + + ", associations=" + f_mapQueueDeferred.size(); + } + + + // ---- ConcurrentAssociationPile methods ------------------------------- + + /** + * Return a Node for the specified value. + * + * @param value the value + * @param lPosition the value's position within the pile + * + * @return the node + */ + protected Node makeNode(T value, long lPosition) + { + return new SimpleNode<>(value, lPosition); + } + + /** + * Return the association for the specified value. + * + * @param value the value + * + * @return the association + */ + protected A getAssociation(T value) + { + return value instanceof Associated + ? ((Associated) value).getAssociatedKey() + : null; + } + + /** + * Add the specified node to the available set. + * + * @param node the available node + * + * @return true + */ + protected boolean addAvailable(Node node) + { + // adding to a SkipList while O(log(N)) still has quite a bit of bookkeeping overhead that we'd + // prefer to avoid where possible. Note we aren't required to be perfectly accurate here as + // the available queues never contain multiple nodes with the same association so there is only + // a fairness ordering to consider not a correctness ordering. + long lPos = node.getPosition(); + if (m_lPosAvailableLast - lPos > MAX_UNFAIRNESS_VARIANCE) + { + // this node is much older then the last add, push through the strict queue + f_queueAvailablePriority.add(node); + } + else + { + m_lPosAvailableLast = lPos; + f_queueAvailable.add(node.getValue()); + } + + signal(); // free if there are no threads waiting + + return true; + } + + /** + * Add a value to the available queue. + * + * @param value the value + * + * @return true + */ + protected boolean addAvailable(T value) + { + m_lPosAvailableLast = ++m_cAdds; + f_queueAvailable.add(value); + + signal(); // free if there are no threads waiting + + return true; + } + + /** + * Handle the add of an ASSOCIATION_ALL node. + * + * @param valueAll the ASSOCIATION_ALL associated value + * + * @return true + */ + protected boolean handleAddAll(T valueAll) + { + try (Sentry sentry = f_gateAll.close()) // block concurrent associated adds + { + long lPos = nextPosition(); // must occur under write lock to ensure ordering + + if (m_lPosNextAll == Long.MAX_VALUE) + { + // there are no pending ALLs + if (f_mapQueueDeferred.isEmpty()) + { + // there are also no pending associations + m_lPosNextAll = lPos; + addAvailable(valueAll); + } + else + { + // ensure all already available (or polled) associations get handled first, to be here means there are outstanding nodes + // pending release + m_lPosNextAll = lPos; + f_queueDeferredAlls.add(makeNode(valueAll, lPos)); + f_cAssociatedPendingRelease.set(f_mapQueueDeferred.size()); // there must be this many associations pending release (semi-expensive but very rare) + } + } + else // there are already ALL nodes, we can only come after them + { + f_queueDeferredAlls.add(makeNode(valueAll, lPos)); + } + } + + return true; + } + + /** + * Handle the release of an ASSOCIATION_ALL value. + */ + protected void handleReleaseAll() + { + // attempt to drain the ALL queue. It can be drained up until we find a new ALL, if we fully + // drain it without finding an ALL we can return to normal operations. + // We use a high level lock here to prevent endless churn on the queue where other threads add + // to it as we drain it, this ultimately helps us got back to our desired non-ALL state faster. + + try (Sentry sentry = f_gateAll.close()) + { + m_lPosNextAll = Long.MAX_VALUE; // assume there will be no more ALLs + + int cAssociatedPendingRelease = 0; + for (Node node = f_queueDeferredAlls.pollFirst(); node != null; node = f_queueDeferredAlls.pollFirst()) + { + if (getAssociation(node.getValue()) == ASSOCIATION_ALL) + { + m_lPosNextAll = node.getPosition(); + if (cAssociatedPendingRelease == 0) + { + addAvailable(node); + } + else + { + // we'd already made other associated nodes available so we'll have + // to wait on the ALL node, re-insert it (ordered insertion) + f_queueDeferredAlls.add(node); + f_cAssociatedPendingRelease.set(cAssociatedPendingRelease); + } + + break; // either way we're done once we've see a new ALL + } + + addAvailable(node); + ++cAssociatedPendingRelease; + // we can continue to add more until we hit an ASSOCATION_ALL + } + } + } + + /** + * Handle the add of an associated node. + * + * @param key the association + * @param value the value + * @param queue the associated queue (or null) + * + * @return true + */ + protected boolean handleAssociatedAdd(A key, T value, CloseableQueue> queue) + { + Node node = makeNode(value, nextPosition()); // inc under lock ensures it is never less than a concurrently added ALL + boolean fNoAll = m_lPosNextAll == Long.MAX_VALUE; // stable since we hold read lock + boolean fAdded = fNoAll && queue.add(node); + + while (!fAdded) + { + queue = f_mapQueueDeferred.putIfAbsent(key, EMPTY_QUEUE); + fAdded = queue == null + ? fNoAll + ? addAvailable(value) // common path; direct add to available + : f_queueDeferredAlls.add(node) + : queue == EMPTY_QUEUE + ? f_mapQueueDeferred.replace(key, EMPTY_QUEUE, new CloseableQueue<>(node)) // defer via promotion to inflated queue + : queue.add(node); // defer to existing real queue + } + + return true; + } + + /** + * Compute a position for an new element. + * + * @return the position + */ + protected long nextPosition() + { + // the position must be > the f_nPosCounter, and should be > m_cAdds; also take this as an opportunity to + // bring the two close together, but f_nPosCounter can never go backwards + long lPosCurr = f_nPosCounter.get(); + long lPosNext = Math.max(lPosCurr, m_cAdds) + 1; + + while (!f_nPosCounter.compareAndSet(lPosCurr, lPosNext)) + { + lPosCurr = f_nPosCounter.get(); + lPosNext = Math.max(lPosCurr, m_cAdds) + 1; + } + m_cAdds = lPosNext; + + return lPosNext; + } + + /** + * Handle the post release of an associated node while there is an ALL pending. + * + * @param nodeNext the next deferred node associated with the released node + */ + protected void handlePostReleaseWithPendingAll(Node nodeNext) + { + if (nodeNext == null || + (nodeNext.getPosition() > m_lPosNextAll && f_queueDeferredAlls.add(nodeNext))) + { + // the node either doesn't exist or comes after the pending ALL + // (in which case we've deferred it through the ALL queue above) + // decrement the pending count so we can make the ALL available + if (f_cAssociatedPendingRelease.decrementAndGet() == 0) + { + addAvailable(f_queueDeferredAlls.pollFirst()); + } + } + else + { + // this node is still in front of the pending ALL + addAvailable(nodeNext); + } + } + + /** + * Poll the next node for the specified association, or remove the association's deferred queue if it is empty. + * + * @param key the association to poll or remove + * + * @return the next node or null, which indicates the queue has also been closed and removed + */ + protected Node pollOrRemoveAssociation(A key) + { + CloseableQueue> queue = f_mapQueueDeferred.get(key); + + if (queue == null) + { + // Either the user error or we have an illegal state in the pile. This should not be ignored + // as either condition could just as easily cause pile corruption and allow two associated tasks + // to be concurrently pending release which would violate the contract of the pile. + throw new IllegalArgumentException("association " + key + " of " + key.getClass() + + " is not currently pending release in the pile"); + } + + Node node = queue.poll(); + if (node == null) + { + try (Sentry sentry = queue.f_gate.close()) + { + node = queue.poll(); + if (node == null) + { + queue.close(); + f_mapQueueDeferred.remove(key); + } + } + } + + return node; + } + + + // ---- inner interface: Node ------------------------------------------- + + /** + * Node is a thin wrapper around the pile's value object. + * + * For use cases where a the added value type is well known it may implement Node itself, and avoid + * creating Node garbage objects, though in such cases it is critical that such values are never exist + * in two piles at the same time. + * + * @param the value type + */ + protected interface Node + extends Comparable> + { + /** + * Return the node's position within the pile. + * + * @return the position + */ + public long getPosition(); + + /** + * Return the nodes value. + * + * @return the value + */ + public T getValue(); + + @Override + default int compareTo(Node o) + { + return Long.compare(getPosition(), o.getPosition()); + } + } + + // ---- inner class: SimpleNode ----------------------------------------- + + /** + * A simple implementation of the Node interface. + * + * @param the value type + */ + protected static class SimpleNode + implements Node + { + /** + * Construct a SimpleNode with the specified value and position. + * + * @param value the value + * @param lPosition the position + */ + public SimpleNode(T value, long lPosition) + { + f_value = value; + m_lPosition = lPosition; + } + + @Override + public long getPosition() + { + return m_lPosition; + } + + @Override + public T getValue() + { + return f_value; + } + + // ----- data members ------------------------------------------- + + /** + * The value. + */ + protected final T f_value; + + /** + * The position. + */ + protected long m_lPosition; + } + + // ----- inner class: CloseableQueue ------------------------------------ + + /** + * A Queue implementation which is also closeable and supports a read write lock. + * + * @param the element type + */ + protected static class CloseableQueue + extends ConcurrentLinkedQueue + implements AutoCloseable + { + public CloseableQueue() + { + } + + public CloseableQueue(E initial) + { + this(); + super.add(initial); // super to avoid needless read lock + } + + /** + * {@inheritDoc} + * + * @return true if added; false if the queue was closed + */ + @Override + public boolean add(E e) + { + try (Sentry sentry = f_gate.enter()) + { + return isOpen() && super.add(e); + } + } + + // ---- Closeable interface ----------------------------------------- + + /** + * Close the queue preventing futher additions. Note the caller should hold the write lock. + */ + @Override + public void close() + { + m_fClosed = true; + } + + /** + * Return true iff the queue has not been closed. + * + * @return true iff the queue has not been closed. + */ + public boolean isOpen() + { + return !m_fClosed; + } + + // ---- Object interface ---------------------------------------- + + @Override + public boolean equals(Object obj) + { + return this == obj; + } + + @Override + public int hashCode() + { + return System.identityHashCode(this); + } + + // ---- data members -------------------------------------------- + + /** + * True if the queue has been closed. + */ + protected boolean m_fClosed; + + /** + * The gate protecting closing the queue. + */ + protected final ThreadGate.NonReentrant f_gate = new ThreadGate.NonReentrant(); + } + + // ----- data members --------------------------------------------------- + + /** + * The monotonically increasing position counter, used for potentially contending associated adds. + */ + protected final AtomicLong f_nPosCounter = new AtomicLong(); + + /** + * (Dirty) number of adds to the pile, used to estimate the position of "uncontended" adds. + */ + protected long m_cAdds; + + /** + * The size of the pile. + */ + protected final LongAdder f_cValues = new LongAdder(); + + /** + * The number of values with non-null associations which are either pending release, i.e. available or polled. + * + * This is only maintained when there is a pending ALL node. + */ + protected final AtomicLong f_cAssociatedPendingRelease = new AtomicLong(); + + /** + * A "queue" (ordered set) of nodes available to be polled. A SkipList is superior to a tree based set here + * not just because it is concurrent, but also because unlike a tree based set there is no rebalancing + * to do, which is especially important as most adds to this queue occur in sort order which would trigger + * constant rebalancing in a tree based map. + */ + protected final ConcurrentSkipListSet> f_queueAvailablePriority = new ConcurrentSkipListSet<>(); + + /** + * Another queue of available values to be polled. Unlike f_queueAvailablePriority, this queue is ordered + * by the time nodes were made available rather then their position. See {@link #addAvailable} and {@link #poll} + * to see how the two available queues interact. + */ + protected final ConcurrentLinkedQueue f_queueAvailable = new ConcurrentLinkedQueue<>(); + + /** + * (Dirty) Count of the number of times poll has been called. + */ + protected int m_cPolls; + + /** + * (Dirty) The estimated position of the last Node added to the available queue. + */ + protected long m_lPosAvailableLast; + + /** + * A map of associations to queues of their currently deferred nodes. + * + * CloseableQueue is just insertion ordered (rather then strict position ordering). Normally insertion and positional + * ordering are the same, the only time when may not be is when two nodes with the same association are concurrently + * added, and then there is a race to determine the queue order. In such a case there is no correct ordering + * anyway, and insertion ordering is cheaper to maintain. + */ + protected final ConcurrentHashMap>> f_mapQueueDeferred = new ConcurrentHashMap<>(); + + /** + * The "queue" (ordered set) of nodes with values entangled with {@link #ASSOCIATION_ALL}, this includes any + * younger association heads which become ready while there is an ASSOCIATION_ALL in the pile. + * + * A SkipList rather then Queue is used her as nodes may not be added in their position order. This can happen + * when there are multiple ALLs as well as associations from f_mapQueue which sit between those two in age. + */ + protected final ConcurrentSkipListSet> f_queueDeferredAlls = new ConcurrentSkipListSet<>(); + + /** + * The position of the next ASSOCIATION_ALL node, or Long.MAX_VALUE if there is none in the pile. + */ + protected long m_lPosNextAll = Long.MAX_VALUE; + + /** + * ThreadGate governing access to the ASSOCIATION_ALL related portions of the pile. + */ + protected ThreadGate.NonReentrant f_gateAll = new ThreadGate.NonReentrant(); + + // ----- constants ------------------------------------------------------ + + /** + * The maximum unorderedness of polls. + */ + protected static final int MAX_UNFAIRNESS_VARIANCE = Integer.parseInt( + System.getProperty(ConcurrentAssociationPile.class.getCanonicalName() + ".maxVariance", + String.valueOf(Runtime.getRuntime().availableProcessors() * 4))); + + /** + * A type-safe permanently empty marker queue. + */ + protected final CloseableQueue> EMPTY_QUEUE = EMPTY_QUEUE_UNTYPED; + + /** + * A permanently empty (and closed) marker queue. + */ + protected final static CloseableQueue EMPTY_QUEUE_UNTYPED = new CloseableQueue() + { + @Override + public boolean add(Object o) + { + return false; + } + + @Override + public int size() + { + return 0; + } + + @Override + public boolean isOpen() + { + return false; + } + }; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/DaemonThreadFactory.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/DaemonThreadFactory.java new file mode 100644 index 0000000000000..5c267cb2a7d1d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/DaemonThreadFactory.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.oracle.coherence.common.util; + + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.ThreadFactory; + + +/** + * DaemonThreadFactory is a ThreadFactory which produces daemon threads. + * + * @author mf 2010.05.12 + */ +public class DaemonThreadFactory + implements ThreadFactory + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new DaemonThreadFactory. + */ + public DaemonThreadFactory() + { + this(null); + } + + /** + * Construct a new DaemonThreadFactory. + * + * @param sPrefix the prefix for unnamed threads + */ + public DaemonThreadFactory(String sPrefix) + { + m_sNamePrefix = sPrefix; + } + + + // ----- ThreadFactory methods ------------------------------------------ + + /** + * {@inheritDoc} + */ + public Thread newThread(Runnable r) + { + String sPrefix = m_sNamePrefix; + Thread thread = sPrefix == null + ? new Thread(r) + : new Thread(r, sPrefix + m_cNameSuffix.incrementAndGet()); + thread.setDaemon(true); + return thread; + } + + + // ----- data members --------------------------------------------------- + + /** + * The prefix to use for un-named threads produced by the factory. + */ + protected final String m_sNamePrefix; + + /** + * The thread name counter. + */ + protected final AtomicInteger m_cNameSuffix = new AtomicInteger(); + + + // ----- constants ------------------------------------------------------ + + /** + * A reusable DaemonThreadFactory instance. + */ + public static final DaemonThreadFactory INSTANCE = + new DaemonThreadFactory("DaemonThread-"); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Duration.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Duration.java new file mode 100644 index 0000000000000..0402419c76641 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Duration.java @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link Duration} represents an amount of time, with nanosecond accuracy. + *

+ * This implementation is inspired (and copied mostly) from the earlier work by Cameron Purdy. + * + * @author bko 2011.07.19 + */ +public class Duration + { + /** + * Construct a {@link Duration} given another {@link Duration}. + * + * @param d the {@link Duration} + */ + public Duration(Duration d) + { + assert d != null; + m_cNanos = d.m_cNanos; + } + + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link Duration} give a specified number of nano seconds. + * + * @param cNanos the number of nano seconds in the {@link Duration} + */ + public Duration(long cNanos) + { + assert cNanos >= 0; + m_cNanos = cNanos; + } + + /** + * Construct a {@link Duration} by parsing the specified {@link String}. + *

+ * The format of the {@link String} consists of one or more numbers, each with a specific {@link Magnitude} + * separated by white space. + *

+ * Note 1: Numbers may contain decimal places. + *

+ * Note 2: Each number must be followed by a specific {@link Magnitude}. For a more relaxed {@link String}-based + * constructor, including the ability to specify a default {@link Magnitude}, use + * {@link #Duration(String, Magnitude)} instead. + *

+ * For example: The following are valid {@link Duration}s. + * "10s", "0ns", "1hr 10m", "5us", "100ms", "12m", "1.5h", "20m 10.5s" + * + * @param s the string containing the {@link Duration} + */ + public Duration(String s) + { + this(s, null); + } + + /** + * Construct a {@link Duration} given a specified amount of a {@link Magnitude}. + *

+ * As the amount is a double precision value, the resulting {@link Duration} + * will be rounded to the closest nanosecond. + * + * @param nAmount the amount of the {@link Magnitude} + * @param magnitude the {@link Magnitude} + */ + public Duration(double nAmount, Magnitude magnitude) + { + assert nAmount >= 0.0; + m_cNanos = Math.round(nAmount * magnitude.getFactor()); + } + + /** + * Construct a {@link Duration} given a specified amount of a {@link Magnitude}. + * + * @param nAmount the amount of the {@link Magnitude} + * @param magnitude the {@link Magnitude} + */ + public Duration(int nAmount, Magnitude magnitude) + { + assert nAmount >= 0; + m_cNanos = nAmount * magnitude.getFactor(); + } + + /** + * Construct a {@link Duration} by parsing the specified {@link String}. + *

+ * The format of the {@link String} is either a number (without a specified {@link Magnitude}) or a {@link String} + * containing one or more numbers, each with a specific {@link Magnitude} separated by white space. + *

+ * Note: numbers may contain decimal places. + *

+ * For example: The following are valid {@link Duration}s. + * "10s", "0ns", "1hr 10m", "0", "5us", "100ms", "12m", "1.5h", "20m 10.5s" + * + * @param s the string containing the {@link Duration} + * @param m the default {@link Magnitude} to use if the specified {@link String} does not specify a + * {@link Magnitude}. when null a {@link Magnitude} must be specified in the {@link String}. + * when not null the {@link String} may only contain a single number. + */ + public Duration(String s, Magnitude m) + { + s = (s == null) ? null : s.trim(); + + if ((s == null) || s.isEmpty()) + { + throw new IllegalArgumentException("An empty or null string was provided. Expected a duration size"); + } + + // when a default magnitude was specified, attempt to match just the number + boolean fUsedDefault = false; + + if (m != null) + { + Matcher matcher = REGEX_NUMBER.matcher(s); + + if (matcher.matches()) + { + String sAmount = matcher.group(1); + double nAmount = Double.valueOf(sAmount); + + m_cNanos = Math.round(nAmount * m.getFactor()); + fUsedDefault = true; + } + } + + // when a default magnitude wasn't used, attempt to match using explicit magnitudes + if (!fUsedDefault && !s.equals("0")) + { + Matcher matcher = REGEX_PATTERN.matcher(s); + + if (!matcher.matches()) + { + throw new IllegalArgumentException(String.format("The specified %s [%s] is invalid.", + this.getClass().getName(), s)); + } + + m_cNanos = 0; + + int i = 1; + + while (i < matcher.groupCount()) + { + String sAmount = matcher.group(i + 1); + + if (sAmount != null) + { + // determine the amount of the magnitude + double nAmount = Double.valueOf(sAmount); + + // determine the magnitude + String sSuffix = matcher.group(i + 2); + Magnitude magnitude = Magnitude.fromSuffix(sSuffix); + + m_cNanos += Math.round(nAmount * magnitude.getFactor()); + } + + i += 3; + } + } + } + + // ----- Duration methods ----------------------------------------------- + + /** + * Obtains the number of nano seconds in the {@link Duration}. + * + * @return The number of nano seconds + */ + public long getNanos() + { + return m_cNanos; + } + + /** + * Obtains the {@link Duration} in the specified {@link Magnitude} (rounded down). + * + * @param magnitude the required {@link Magnitude} + * + * @return The number of units of the specified {@link Magnitude}. + */ + public long as(Magnitude magnitude) + { + return m_cNanos / magnitude.getFactor(); + } + + /** + * Obtains a {@link String} representation of the {@link Duration} using the most appropriate {@link Magnitude} + * to simplify the representation. + *

+ * Note: Using {@link #toString()} will result in a non-exact representation. + * + * @param fExact indicates an exact value is required or if a rounded value will suffice. + * + * @return A {@link String} + */ + public String toString(boolean fExact) + { + StringBuilder sbResult = new StringBuilder(); + long cRemainingNanos = m_cNanos; + int cLimit = 2; + + for (Magnitude magnitude = Magnitude.HIGHEST; cRemainingNanos > 0 && (fExact || cLimit > 0); magnitude = magnitude.previous()) + { + long cMagnitudeUnits = cRemainingNanos / magnitude.getFactor(); + + if (cMagnitudeUnits > 0) + { + cRemainingNanos -= cMagnitudeUnits * magnitude.getFactor(); + + if (fExact || magnitude.ordinal() > Magnitude.SECOND.ordinal()) + { + sbResult.append(cMagnitudeUnits); + } + else // non-exact and seconds or less, express as fraction + { + long cNanosPerMagnitudeFractionUnit = magnitude.getFactor() / 1000; + long cMagnitudeFractionUnits = (cNanosPerMagnitudeFractionUnit > 0) + ? cRemainingNanos / cNanosPerMagnitudeFractionUnit : 0; + long cMagnitudeFractionNanos = cMagnitudeFractionUnits * cNanosPerMagnitudeFractionUnit; + + double flRem = cMagnitudeFractionNanos * 1000 / magnitude.getFactor() / 1000.0; + + if (flRem >= 0.01 && (fExact || cLimit > 1)) + { + sbResult.append(String.format("%.2f", cMagnitudeUnits + flRem)); + } + else + { + sbResult.append(cMagnitudeUnits); + } + cRemainingNanos = 0; + } + + sbResult.append(magnitude.getSuffix()); + --cLimit; + } + else if (sbResult.length() > 0) + { + --cLimit; + } + } + + if (sbResult.length() == 0) + { + return "0ns"; + } + else + { + return sbResult.toString(); + } + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() + { + return 31 + (int) (m_cNanos ^ (m_cNanos >>> 32)); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) + { + return (this == obj) || ((obj != null) && (obj instanceof Duration) && ((Duration) obj).m_cNanos == m_cNanos); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return toString(false); + } + + // ----- Magnitude Enumeration ------------------------------------------ + + /** + * The {@link Magnitude} of the {@link Duration}. + */ + public enum Magnitude + { + NANO(1L, "ns"), + MICRO(1000L, "us", "\u00B5s", "\u039Cs", "\u03BCs"), + MILLI(1000000L, "ms"), + SECOND(1000000000L, "s"), + MINUTE(60 * 1000000000L, "m"), + HOUR(60 * 60 * 1000000000L, "h"), + DAY(24 * 60 * 60 * 1000000000L, "d"); + + // ----- constructors ----------------------------------------------- + + /** + * Construct a {@link Magnitude}. + * + * @param nFactor the factor which this Magnitude represents, relative to the number of nanoseconds + * @param sSuffixes the suffix (case-insensitive) of the magnitude + */ + Magnitude(long nFactor, String... sSuffixes) + { + FACTOR = nFactor; + SUFFIXES = sSuffixes; + } + + // ----- Magnitude methods ------------------------------------------ + + /** + * Determine the factor of the {@link Magnitude} relative to the number of + * nanoseconds. For example, "MILLI" has a factor of 1000000. + * + * @return the factor of the {@link Magnitude} + */ + public long getFactor() + { + return FACTOR; + } + + /** + * Obtain the default for the {@link Magnitude}. For example, "MILLI" has the suffix + * "ms". + * + * @return the suffix of the {@link Magnitude}. + */ + public String getSuffix() + { + return SUFFIXES[0]; + } + + /** + * Determine if the passed suffix is compatible with this {@link Magnitude}'s suffix, ignoring case. + * + * @param s the suffix to test + * + * @return true iff the passed string is compatible with the suffix of this {@link Magnitude}. + */ + public boolean isSuffix(String s) + { + for (String sSuffix : SUFFIXES) + { + if (sSuffix.equalsIgnoreCase(s)) + { + return true; + } + } + + return false; + } + + /** + * Obtain the next order of {@link Magnitude} (above this one). + * + * @return the next order of {@link Magnitude} above this one or null if this is + * the {@link #HIGHEST}. + */ + public Magnitude next() + { + if (this.equals(Magnitude.HIGHEST)) + { + return null; + } + else + { + return Magnitude.VALUES[this.ordinal() + 1]; + } + } + + /** + * Obtain the previous order of {@link Magnitude} (above this one). + * + * @return the previous order of {@link Magnitude} or null if this is + * the {@link #LOWEST}. + */ + public Magnitude previous() + { + if (this.equals(Magnitude.LOWEST)) + { + return null; + } + else + { + return Magnitude.VALUES[this.ordinal() - 1]; + } + } + + // ----- helpers ---------------------------------------------------- + + /** + * Determine the {@link Magnitude} given the specified suffix. + * + * @param sSuffix the proposed suffix + * + * @return a {@link Magnitude} with the specified suffix + */ + public static Magnitude fromSuffix(String sSuffix) + { + sSuffix = sSuffix.trim(); + + if (sSuffix.length() == 0) + { + return Magnitude.NANO; + } + else if (sSuffix.length() > 0) + { + for (Magnitude magnitude : Magnitude.VALUES) + { + if (magnitude.isSuffix(sSuffix)) + { + return magnitude; + } + } + } + + throw new IllegalArgumentException(String.format("Unknown %s suffix [%s]", Magnitude.class.getName(), + sSuffix)); + } + + // ----- constants -------------------------------------------------- + + /** + * Cached copy of the VALUES array to avoid garbage creation + */ + private static final Magnitude[] VALUES = Magnitude.values(); + + /** + * The lowest defined order of {@link Magnitude}. + */ + public final static Magnitude LOWEST = Magnitude.VALUES[0]; + + /** + * The highest defined order of {@link Magnitude}. + */ + public final static Magnitude HIGHEST = Magnitude.VALUES[Magnitude.VALUES.length - 1]; + + // ----- data members ----------------------------------------------- + + /** + * The number of nanoseconds in a single unit of this magnitude. For + * example, a minute has 60 billion nanoseconds. + */ + public final long FACTOR; + + /** + * The suffixes that for the {@link Magnitude}. For example, "MILLI" has + * the suffix "ms". + */ + public final String[] SUFFIXES; + } + + // ----- constants ------------------------------------------------------ + + /** + * The pre-compiled regular expression {@link Pattern}s to match + * a {@link Duration} specified as a {@link String}. + */ + private static final String NUMBER = "(\\d+(?:\\.\\d+)?)"; + private static final String PATTERN_MINUTE = "\\s*(" + NUMBER + "\\s*([Mm]))?"; + private static final String PATTERN_HOUR = "\\s*(" + NUMBER + "\\s*([Hh]))?"; + private static final String PATTERN_DAY = "\\s*(" + NUMBER + "\\s*([Dd]))?"; + private static final String PATTERN_SECOND = "\\s*(" + NUMBER + "\\s*([Ss]))?"; + private static final String PATTERN_NANO = "\\s*(" + NUMBER + "\\s*([Nn][Ss]))?"; + private static final String PATTERN_MILLI = "\\s*(" + NUMBER + "\\s*([Mm][Ss]))?"; + private static final String PATTERN_MICRO = "\\s*(" + NUMBER + "\\s*([Uu\u00B5Mm\\u039C\\u03BC][Ss]))?"; + private static final Pattern REGEX_NUMBER = Pattern.compile(NUMBER); + private static final Pattern REGEX_PATTERN = Pattern.compile(PATTERN_DAY + PATTERN_HOUR + PATTERN_MINUTE + + PATTERN_SECOND + PATTERN_MILLI + PATTERN_MICRO + PATTERN_NANO); + + // ----- data members --------------------------------------------------- + + /** + * The number of nanos in the {@link Duration}. + */ + private long m_cNanos; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Gate.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Gate.java new file mode 100644 index 0000000000000..282bf099f8e0c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Gate.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + + +/** +* The Gate interface acts as an abstraction for {@link ThreadGate}. +* +* @author coh 2010-08-13 +* +* @since Coherence 3.7 +*/ +public interface Gate + { + /** + * Wait to close the gate. + * + * @return an AutoCloseable which can be used with a try-with-resource block to perform the corresponding {@link #open}. + */ + public Sentry close(); + + /** + * Close the gate. A thread uses this method to obtain exclusive access to + * the resource represented by the gate. Each invocation of this method must + * ultimately have a corresponding invocation of the {@link #open} method. + * + * @param cMillis maximum number of milliseconds to wait; + * pass -1 to wait indefinitely or 0 to return immediately + * + * @return true iff entry into the gate was successfully closed by the + * calling thread and no other threads remain in the gate + */ + public boolean close(long cMillis); + + /** + * Re-open the closed gate. This method can be called only if the calling + * thread successfully closed the gate. + * + * @throws IllegalMonitorStateException if the gate is not closed or + * was closed by a different thread + */ + public void open(); + + /** + * Wait to enter the gate. + * + * @return an AutoCloseable which can be used with a try-with-resource block to perform the corresponding {@link #exit}. + */ + public Sentry enter(); + + /** + * Enter the gate. A thread uses this method to obtain non-exclusive + * access to the resource represented by the gate. Each invocation + * of this method must ultimately have a corresponding invocation of the + * {@link #exit} method. + * + * @param cMillis maximum number of milliseconds to wait; + * pass -1 to wait indefinitely or 0 to return immediately + * + * @return true iff the calling thread successfully entered the gate + */ + public boolean enter(long cMillis); + + /** + * Exit the gate. A thread must invoke this method corresponding to each + * invocation of the {@link #enter} method. + * + * @throws IllegalMonitorStateException if the gate is not entered by the + * current thread + */ + public void exit(); + + /** + * Bar entry to the thread gate by other threads, but do not wait for the + * gate to close. When all other threads have exited, the status of the + * thread gate will be closeable by the thread which barred entry. + * Threads that have already entered the gate at the time of this method + * call should be allowed to succeed in additional #enter calls. + *

+ * Each successful invocation of this method must ultimately have a + * corresponding invocation of the open method (assuming the thread gate + * is not destroyed) even if the calling thread does not subsequently + * close the gate. + * + *


+    * gate.barEntry(-1);
+    * try
+    *     {
+    *     // processing that does not require the gate to be closed
+    *     // ...
+    *     }
+    * finally
+    *     {
+    *     gate.close(-1);
+    *     try
+    *         {
+    *         // processing that does require the gate to be closed
+    *         // ...
+    *         }
+    *     finally
+    *         {
+    *         gate.open(); // matches gate.close()
+    *         }
+    *     gate.open(); // matches gate.barEntry()
+    *     }
+    * 
+ * + * @param cMillis maximum number of milliseconds to wait; + * pass -1 for forever or 0 for no wait + * + * @return true iff entry into the thread gate was successfully barred by + * the calling thread + */ + public boolean barEntry(long cMillis); + + /** + * Determine if the calling thread has closed the gate and continues + * to hold exclusive access. + * + * @return true iff the calling thread holds exclusive access to the gate + */ + public boolean isClosedByCurrentThread(); + + /** + * Determines if the current thread has entered the gate and not yet exited. + * + * @return true if the current thread has entered the gate + */ + public boolean isEnteredByCurrentThread(); + + /** + * Determine if any thread has closed the gate and continues to hold exclusive access. + * + * @return true iff there is a thread that holds exclusive access to the gate + */ + public boolean isClosed(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/MemorySize.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/MemorySize.java new file mode 100644 index 0000000000000..1190e36d95d53 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/MemorySize.java @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link MemorySize} represents an amount of memory, with byte accuracy. + *

+ * Measurements are based on the base two standard (using octets) and not base ten as outlined by the IEC. + *

+ * eg: In this implementation 1 kilobyte = 1024 bytes, not 1000 bytes. + * + * @author cp, bko 2011.07.11 + */ +public class MemorySize + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link MemorySize} give a specified number of bytes. + * + * @param cBytes the number of bytes in the memory size + */ + public MemorySize(long cBytes) + { + assert cBytes >= 0; + m_cBytes = cBytes; + } + + /** + * Constructs a {@link MemorySize} based on another {@link MemorySize}. + * + * @param m the {@link MemorySize} (not null) + */ + public MemorySize(MemorySize m) + { + assert m != null; + m_cBytes = m.m_cBytes; + } + + /** + * Construct a {@link MemorySize} by parsing the specified {@link String}. + *

+ * The format of the {@link String} is a number followed by a {@link Magnitude}. + *

+ * Note: numbers may contain decimal places. + *

+ * For example: The following are valid {@link MemorySize}s. + * "10m", "0k", "1b", "1.75gb" + * + * @param s the string containing the {@link MemorySize} + */ + public MemorySize(String s) + { + this(s, null); + } + + /** + * Construct a {@link MemorySize} given a specified amount of a {@link Magnitude}. + *

+ * As the amount is a double precision value, the resulting {@link MemorySize} + * will be rounded to the closest byte. + * + * @param nAmount the amount of the {@link Magnitude} + * @param magnitude the {@link Magnitude} + */ + public MemorySize(double nAmount, Magnitude magnitude) + { + assert nAmount >= 0.0; + m_cBytes = Math.round(nAmount * magnitude.getByteCount()); + } + + /** + * Construct a {@link MemorySize} given a specified amount of a {@link Magnitude}. + * + * @param nAmount the amount of the {@link Magnitude} + * @param magnitude the {@link Magnitude} + */ + public MemorySize(int nAmount, Magnitude magnitude) + { + assert nAmount >= 0; + m_cBytes = nAmount * magnitude.getByteCount(); + } + + /** + * Construct a {@link MemorySize} by parsing the specified {@link String}. + *

+ * The format of the {@link String} is a number possibly followed by a specific {@link Magnitude}. + *

+ * Note: numbers may contain decimal places. + *

+ * For example: The following are valid {@link MemorySize}s. + * "10m", "0k", "1b", "0", "1.75gb" + * + * @param s the string containing the {@link MemorySize} + * @param m the default {@link Magnitude} to use if the specified {@link String} does not specify a + * {@link Magnitude}. when null a {@link Magnitude} specified in the {@link String} is used + * and if not present, {@link Magnitude#BYTES} is assumed + */ + public MemorySize(String s, Magnitude m) + { + s = (s == null) ? null : s.trim(); + + if ((s == null) || s.isEmpty()) + { + throw new IllegalArgumentException("An empty or null string was provided. Expected a memory size"); + } + + if (!s.equals("0")) + { + Matcher matcher = REGEX_PATTERN.matcher(s); + + if (!matcher.matches()) + { + throw new IllegalArgumentException(String.format("The specified %s [%s] is invalid.", + this.getClass().getName(), s)); + } + + // determine the desired magnitude from the suffix (using group 2) or use the specified default + String sSuffix = matcher.group(2); + Magnitude magnitude = sSuffix == null || sSuffix.trim().isEmpty() ? m : Magnitude.fromSuffix(sSuffix); + + // when there's no magnitude we default to bytes + if (magnitude == null) + { + magnitude = Magnitude.BYTES; + } + + // determine the amount (using group 1) + double nAmount = Double.valueOf(matcher.group(1)); + + m_cBytes = Math.round(nAmount * magnitude.getByteCount()); + } + } + + // ----- MemorySize methods --------------------------------------------- + + /** + * Obtain the {@link MemorySize} as a value in the specified {@link Magnitude}. + * + * @param magnitude the {@link Magnitude} + * + * @return the number of units of the specified {@link Magnitude} that make up the {@link MemorySize} + */ + public double as(Magnitude magnitude) + { + return ((double) m_cBytes) / magnitude.getByteCount(); + } + + /** + * Obtain the number of bytes represented by the {@link MemorySize}. + * + * @return the number of bytes. + */ + public long getByteCount() + { + return m_cBytes; + } + + /** + * Obtains a {@link String} representation of the {@link MemorySize} using the most appropriate {@link Magnitude} + * to simplify the representation. + *

+ * Note: Using {@link #toString()} will result in a non-exact representation. + * + * @param fExact indicates an exact value is required or if an approximate value + * (with three significant digits) will suffice + * + * @return a {@link String} + */ + public String toString(boolean fExact) + { + Magnitude magnitude = Magnitude.BYTES; + long nBytes = m_cBytes; + + // find the highest magnitude to represent the number of bytes appropriately + while ((magnitude.next() != null) && (nBytes >= magnitude.next().getByteCount()) + && ((fExact && (nBytes % magnitude.next().getByteCount()) % (magnitude.next().getByteCount() / 4) == 0) + || !fExact)) + { + magnitude = magnitude.next(); + } + + long cMagnitudeUnits = nBytes / magnitude.getByteCount(); + long nRemainder = nBytes % magnitude.getByteCount(); + int cSignificantDigits = 3; + StringBuilder bldrString = new StringBuilder(); + + bldrString.append(cMagnitudeUnits); + + int cDigits = bldrString.length(); + int cRemainingDigits = cSignificantDigits - cDigits; + + if ((cRemainingDigits > 0) && (nRemainder > 0)) + { + int nSignificanceFactor = (int) Math.pow(10, cRemainingDigits); + long nDecimals = (long) Math.floor(nRemainder * (double) nSignificanceFactor / magnitude.getByteCount()); + + if (nDecimals > 0) + { + bldrString.append("."); + + int cLeadingZeros = cRemainingDigits - (int)Math.log10(nDecimals) - 1; + for (int i = 0; i < cLeadingZeros; i++) + { + bldrString.append('0'); + } + + bldrString.append(nDecimals); + } + } + + bldrString.append(magnitude.getSuffix()); + + return bldrString.toString(); + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() + { + return 31 + (int) (m_cBytes ^ (m_cBytes >>> 32)); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) + { + return (this == obj) + || ((obj != null) && (obj instanceof MemorySize) && ((MemorySize) obj).m_cBytes == m_cBytes); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return toString(false); + } + + // ----- Magnitude Enumeration ------------------------------------------ + + /** + * The {@link Magnitude} of the {@link MemorySize}. + */ + public enum Magnitude + { + BYTES(0, "B", "bytes"), + KB(10, "KB", "kilobytes"), + MB(20, "MB", "megabytes"), + GB(30, "GB", "gigabytes"), + TB(40, "TB", "terabytes"), + PB(50, "PB", "petabytes"), + EB(60, "EB", "exabytes"); + + // ----- constructors ----------------------------------------------- + + /** + * Construct a {@link Magnitude} + * + * @param cShift the number of bits to shift a count of {@link Magnitude} + * units to the left in order to calculate a byte count + * @param sSuffix the String suffix that represents this {@link Magnitude} + * @param sDescription the description of the {@link Magnitude} + */ + Magnitude(int cShift, String sSuffix, String sDescription) + { + SHIFT_COUNT = cShift; + SUFFIX = sSuffix.trim(); + SUFFIX_CHAR = (sSuffix.length() > 0) ? Character.toUpperCase(sSuffix.charAt(0)) : 0; + DESCRIPTION = sDescription; + BYTE_COUNT = 1L << SHIFT_COUNT; + BIT_MASK = BYTE_COUNT - 1; + } + + // ----- Magnitude methods ------------------------------------------ + + /** + * Determine the number of bytes in a single unit of this {@link Magnitude} + * For example, a kilobyte has 1024 bytes. + * + * @return the number of bytes in a single unit of this {@link Magnitude} + */ + public long getByteCount() + { + return BYTE_COUNT; + } + + /** + * Obtain the name of the {@link Magnitude} For example, a kilobyte has the + * description "kilobyte". + * + * @return the {@link Magnitude}'s description + */ + public String getDescription() + { + return DESCRIPTION; + } + + /** + * Obtain the bit mask that when applied will return the fractional + * (right-most) bits that are below this {@link Magnitude} unit. For example, + * a kilobyte has a mask that includes the least significant 10 bits. + * + * @return the mask highlighting the {@link Magnitude}'s fractional bits + */ + public long getResidualBitMask() + { + return BIT_MASK; + } + + /** + * Obtain the suffix of the {@link Magnitude}. + * + * @return a {@link String} + */ + public String getSuffix() + { + return SUFFIX; + } + + /** + * Determine if the passed suffix is compatible with this {@link Magnitude}'s suffix, ignoring case. + * + * @param s the suffix to test + * + * @return true iff the passed string is compatible with the suffix of this {@link Magnitude}. + */ + public boolean isSuffix(String s) + { + return s.equalsIgnoreCase(SUFFIX) || (Character.toUpperCase(s.charAt(0)) == SUFFIX_CHAR); + } + + /** + * Obtain the next order of {@link Magnitude} (above this one). + * + * @return the next order of {@link Magnitude} above this one or null if this is + * the {@link #HIGHEST}. + */ + public Magnitude next() + { + if (this.equals(Magnitude.HIGHEST)) + { + return null; + } + else + { + return Magnitude.VALUES[this.ordinal() + 1]; + } + } + + /** + * Obtain the previous order of {@link Magnitude} (above this one). + * + * @return the previous order of {@link Magnitude} or null if this is + * the {@link #LOWEST}. + */ + public Magnitude previous() + { + if (this.equals(Magnitude.LOWEST)) + { + return null; + } + else + { + return Magnitude.VALUES[this.ordinal() - 1]; + } + } + + // ----- helpers ---------------------------------------------------- + + /** + * Determine the {@link Magnitude} given the specified suffix. + * + * @param sSuffix the proposed suffix + * + * @return a {@link Magnitude} with the specified suffix + */ + public static Magnitude fromSuffix(String sSuffix) + { + sSuffix = sSuffix.trim(); + + if (sSuffix.length() == 0) + { + return Magnitude.BYTES; + } + else if (sSuffix.length() > 0) + { + for (Magnitude magnitude : Magnitude.VALUES) + { + if (magnitude.isSuffix(sSuffix)) + { + return magnitude; + } + } + } + + throw new IllegalArgumentException(String.format("Unknown %s suffix [%s]", Magnitude.class.getName(), + sSuffix)); + } + + // ----- constants -------------------------------------------------- + + /** + * Cached copy of the values array to avoid garbage creation + */ + private static final Magnitude[] VALUES = Magnitude.values(); + + /** + * The lowest defined order of {@link Magnitude}. + */ + public final static Magnitude LOWEST = VALUES[0]; + + /** + * The highest defined order of {@link Magnitude}. + */ + public final static Magnitude HIGHEST = VALUES[VALUES.length - 1]; + + // ----- data members ----------------------------------------------- + + /** + * The bit mask that highlights all of the fractional (right-most) + * bits that are below this {@link Magnitude} unit. For example, a kilobyte + * has a mask that includes the least significant 10 bits. + */ + private final long BIT_MASK; + + /** + * The number of bytes in a single unit of this magnitude. For + * example, a kilobyte has 1024 bytes. + */ + private final long BYTE_COUNT; + + /** + * The description of this {@link Magnitude}. For example, a kilobyte has the name + * "kilobyte". + */ + private final String DESCRIPTION; + + /** + * The number of bits that a size value would have to be shifted to + * the right in order to eliminate any fraction of a {@link Magnitude} unit + * (for example, anything less than a megabyte) and to reduce a size + * value to a count of {@link Magnitude} units (for example, the number of + * megabytes); or the number of bits that a number of {@link Magnitude} units + * (such as a number of megabytes) would have to be shifted to the + * left in order to convert it to a size value (the number of bytes). + */ + private final int SHIFT_COUNT; + + /** + * The suffix that represents this {@link Magnitude}. For example, a + * kilobyte has the suffix "KB". + */ + private final String SUFFIX; + + /** + * The single character abbreviation of the {@link #SUFFIX} (in upper-case). + */ + private final char SUFFIX_CHAR; + } + + // ----- constants ------------------------------------------------------ + + /** + * The pre-compiled regular expression {@link Pattern} to match + * a {@link MemorySize} specified as a {@link String}. + */ + private static final Pattern REGEX_PATTERN = Pattern.compile("([0-9]+(?:\\.[0-9]+)?)([kKMmGgTtPpEe]?[Bb]?)"); + + // ----- data members --------------------------------------------------- + + /** + * The number of bytes in the {@link MemorySize}. + */ + private long m_cBytes; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/NullLock.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/NullLock.java new file mode 100644 index 0000000000000..ee8b418febfb8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/NullLock.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import java.util.Date; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; + + +/** + * NullLock is a lock no-op Lock implementation. + * + * @author mf 2014.10.02 + */ +public class NullLock + extends AutoLock + { + /** + * Construct a NullLock with no resource. + */ + public NullLock() + { + this(null); + } + + /** + * Construct a NullLock with the specified resource. + * + * @param resource the resource + */ + public NullLock(R resource) + { + super(null, resource); + } + + @Override + public Sentry acquire() + { + return f_sentry; + } + + @Override + public void lock() + { + } + + @Override + public void lockInterruptibly() + throws InterruptedException + { + } + + @Override + public boolean tryLock() + { + return true; + } + + @Override + public boolean tryLock(long time, TimeUnit unit) + throws InterruptedException + { + return true; + } + + @Override + public void unlock() + { + } + + @Override + public Condition newCondition() + { + return new Condition() + { + @Override + public void await() + throws InterruptedException + { + throw new UnsupportedOperationException(); + } + + @Override + public void awaitUninterruptibly() + { + throw new UnsupportedOperationException(); + } + + @Override + public long awaitNanos(long nanosTimeout) + throws InterruptedException + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean await(long time, TimeUnit unit) + throws InterruptedException + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean awaitUntil(Date deadline) + throws InterruptedException + { + throw new UnsupportedOperationException(); + } + + @Override + public void signal() + { + } + + @Override + public void signalAll() + { + } + }; + } + + /** + * Singleton instance. + */ + public static final NullLock INSTANCE = new NullLock(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Options.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Options.java new file mode 100644 index 0000000000000..38b845e134a13 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Options.java @@ -0,0 +1,666 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Stack; +import java.util.stream.Collectors; + +/** + * An immutable collection of zero or more values, typically called an options, + * internally arranged as a map, keyed by the concrete type of each option in + * the collection. + * + * @param the base type of the options in the collection + * + * @author bko 2015.07.24 + */ +public class Options + { + + // ----- constructors --------------------------------------------------- + + /** + * Constructs an empty {@link Options} collection. + * + * @param clsType the {@link Class} of the base type of the options + * in the collection + */ + private Options(Class clsType) + { + m_mapOptions = new LinkedHashMap<>(); + m_clsType = clsType; + } + + /** + * Constructs an {@link Options} collection based on an array of option + * values. + * + * + * @param clsType the {@link Class} of the base type of the options + * in the collection + * @param aOptions the array of options to add to the collection + */ + private Options(Class clsType, T[] aOptions) + { + m_mapOptions = new LinkedHashMap<>(); + m_clsType = clsType; + + addAll(aOptions); + } + + // ----- Options methods ------------------------------------------------ + + /** + * Obtains the option for a specified concrete type from the collection. + * + *

Should the option not exist in the collection, an attempt is made + * to determine a suitable default based on the use of the {@link Default} + * annotation in the specified class, firstly by looking for and evaluating + * the annotated "public static U getter()" method, failing that, looking for + * and evaluating the annotated "public static U value = ...;" field, failing + * that, looking for an evaluating the annotated public no args constructor + * and finally, failing that, looking for an annotated field on an enum + * (assuming the class is an enum). Failing these approaches, + * null is returned.

+ * + * @param clzOption the concrete type of option to obtain + * @param the type of value + * + * @return the option of the specified type or if undefined, the + * suitable default value (or null if one can't be + * determined) + */ + public U get(Class clzOption) + { + return get(clzOption, getDefaultFor(clzOption)); + } + + /** + * Obtains the option of a specified concrete type from the collection. + *

+ * Should the type of option not exist, the specified default is returned. + * + * @param clzOption the type of option to obtain + * @param optDefault the option to return if the specified type is not defined + * @param the type of value + * + * @return the option of the specified type or + * the default if it's not defined + */ + public U get(Class clzOption, U optDefault) + { + if (clzOption == null) + { + return null; + } + else + { + T option = m_mapOptions.get(clzOption); + + if (option == null) + { + return optDefault; + } + else + { + return (U) option; + } + } + } + + /** + * Determines if an option of the specified concrete type is in the + * collection. + * + * @param clzOption the class of option + * @param the type of option + * + * @return true if the class of option is in the {@link Options} + * false otherwise + */ + public boolean contains(Class clzOption) + { + return get(clzOption) != null; + } + + /** + * Determines if the specified option (and type) is in the {@link Options}. + * + * @param option the option + * + * @return true if the options is defined, + * false otherwise + */ + public boolean contains(T option) + { + if (option == null) + { + return false; + } + + Class clzOption = getClassOf(option); + Object value = get(clzOption); + + return value != null && value.equals(option); + } + + /** + * Obtains an {@link Iterable} over all of the options in the collection + * that are an instance of the specified class. + * + * @param clz the required class + * @param the type of option + * + * @return the options of the required class + */ + public Iterable getInstancesOf(Class clz) + { + return m_mapOptions.values() + .stream() + .filter(clz::isInstance) + .map(value -> (O) value) + .collect(Collectors.toCollection(LinkedList::new)); + } + + /** + * Obtains the current collection of options as an array. + * + * @return an array of options + */ + public T[] asArray() + { + T[] aOptions = (T[]) new Object[m_mapOptions.size()]; + int i = 0; + + for (T option : m_mapOptions.values()) + { + aOptions[i++] = option; + } + + return aOptions; + } + + // ----- Object methods ------------------------------------------------- + + @Override + public String toString() + { + StringBuilder bldrResult = new StringBuilder(); + + bldrResult.append("Options{"); + + boolean fFirst = true; + + for (T option : m_mapOptions.values()) + { + if (fFirst) + { + fFirst = false; + } + else + { + bldrResult.append(", "); + } + + bldrResult.append(option); + } + + bldrResult.append("}"); + + return bldrResult.toString(); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Constructs an {@link Options} collection given an array of options + * + * @param clsType the {@link Class} of the base type of the options + * in the collection + * @param aOptions the array of options + * + * @param the type of options + * + * @return an {@link Options} collection + */ + @SafeVarargs + public static Options from(Class clsType, T... aOptions) + { + return aOptions == null || aOptions.length == 0 + ? empty() + : new Options<>(clsType, aOptions); + } + + /** + * Constructs an empty {@link Options} collection + * + * @param the type of options + * + * @return an empty {@link Options} collection + */ + public static Options empty() + { + return EMPTY; + } + + // ----- internal methods ----------------------------------------------- + + /** + * Adds an option to the collection, replacing an + * existing option of the same concrete type if one exists. + * + * @param option the option to add + * + * @return the {@link Options} to permit fluent-style method calls + */ + private Options add(T option) + { + Class clz = getClassOf(option); + + m_mapOptions.put(clz, option); + + return this; + } + + /** + * Adds an array of options to the collection, replacing + * existing options of the same concrete type where they exist. + * + * @param aOptions the options to add + * + * @return the {@link Options} to permit fluent-style method calls + */ + private Options addAll(T[] aOptions) + { + if (aOptions != null) + { + for (T option : aOptions) + { + add(option); + } + } + + return this; + } + + /** + * Adds all of the options in the specified {@link Options} + * to this collection, replacing existing options of the same concrete + * type where they exist. + * + * @param options the {@link Options} to add + * + * @return the {@link Options} to permit fluent-style method calls + */ + private Options addAll(Options options) + { + for (T option : options.asArray()) + { + add(option); + } + + return this; + } + + /** + * Obtains the concrete type of an option. + * + * @param option the option + * + * @return the concrete {@link Class} that directly extends / implements + * the value interface + * or null if the value is null + */ + private Class getClassOf(T option) + { + return option == null ? null : getClassOf(option.getClass()); + } + + /** + * Obtains the concrete type that directly implements / extends the {@link #m_clsType} + * option {@link Class}. + * + * @param classOfOption the class that somehow implements or extends {@link #m_clsType} + * + * @return the concrete {@link Class} that directly extends / implements the {@link #m_clsType} + * class or null if the specified {@link Class} doesn't implement or extend + * the {@link #m_clsType} class + */ + + private Class getClassOf(Class classOfOption) + { + + if (m_clsType.equals(classOfOption)) + { + return (Class) classOfOption; + } + + // the hierarchy of classes we've visited + // (so that we can traverse it later to find non-abstract classes) + Stack> hierarchy = new Stack<>(); + + while (classOfOption != null) + { + // remember the current class + hierarchy.push(classOfOption); + + for (Class interfaceClass : classOfOption.getInterfaces()) + { + if (m_clsType.equals(interfaceClass)) + { + // when the option class is directly implemented by a class, + // we return the first non-abstract class in the hierarchy. + while (classOfOption != null && Modifier.isAbstract(classOfOption.getModifiers()) && !classOfOption.isInterface()) + { + classOfOption = hierarchy.isEmpty() ? null : hierarchy.pop(); + } + + return (Class) (classOfOption == null ? null + : classOfOption.isSynthetic() ? interfaceClass : classOfOption); + } + else if (m_clsType.isAssignableFrom(interfaceClass)) + { + // ensure that we have a concrete class in our hierarchy + while (classOfOption != null && Modifier.isAbstract(classOfOption.getModifiers()) + && !classOfOption.isInterface()) + { + classOfOption = hierarchy.isEmpty() ? null : hierarchy.pop(); + } + + if (classOfOption == null) + { + // when the hierarchy is entirely abstract, we can't determine a concrete Option type + return null; + } + else + { + // when the option is a super class of an interface, + // we return the interface that's directly extending it. + + // TODO: we should search to find the interface that is directly + // extending the option type and not just assume that the interfaceClass + // is directly implementing it + return (Class) interfaceClass; + } + } + } + + classOfOption = classOfOption.getSuperclass(); + } + + return null; + } + + + /** + * Attempts to determine a "default" value for a given class. + * + *

Aan attempt is made to determine a suitable default based on the use + * of the {@link Default} annotation in the specified class, firstly by + * looking for and evaluating the annotated "public static U getter()" + * method, failing that, looking for and evaluating the annotated + * "public static U value = ...;" field, failing that, looking for an + * evaluating the annotated public no args constructor and finally, failing + * that, looking for an annotated field on an enum + * (assuming the class is an enum). Failing these approaches, + * null is returned.

+ * + * @param clzOption the class + * @param the type of value + * + * @return a default value or null if a default can't be + * determined + */ + protected U getDefaultFor(Class clzOption) + { + if (clzOption == null) + { + return null; + } + else + { + for (Method method : clzOption.getMethods()) + { + int modifiers = method.getModifiers(); + + if (method.getAnnotation(Default.class) != null && + method.getParameterCount() == 0 && + Modifier.isStatic(modifiers) && + Modifier.isPublic(modifiers) && + clzOption.isAssignableFrom(method.getReturnType())) + { + try + { + return (U) method.invoke(null); + } + catch (Exception e) + { + //carry on... perhaps we can use another approach? + } + } + } + } + + for (Field field : clzOption.getFields()) + { + int modifiers = field.getModifiers(); + + if (field.getAnnotation(Default.class) != null && + Modifier.isStatic(modifiers) && + Modifier.isPublic(modifiers) && + clzOption.isAssignableFrom(field.getType())) + { + try + { + return (U) field.get(null); + } + catch (Exception e) + { + // carry on... perhaps we can use another approach? + } + } + } + + try { + Constructor constructor = clzOption.getConstructor(); + + int modifiers = constructor.getModifiers(); + + if (constructor.getAnnotation(Default.class) != null && + Modifier.isPublic(modifiers)) + { + try + { + return (U) constructor.newInstance(); + } + catch (Exception e) + { + //carry on... perhaps we can use another approach? + } + } + } + catch (NoSuchMethodException e) + { + // carry on... there's no no-args constructor + } + + // couldn't find a default so let's return null + return null; + } + + // ----- data members --------------------------------------------------- + + /** + * The map of the options, keyed by their concrete class. + */ + private LinkedHashMap, T> m_mapOptions; + + private Class m_clsType; + + // ----- constants ------------------------------------------------------ + + /** + * A constant to represent an empty {@link Options} collection. + */ + private final static Options EMPTY = new EmptyOptions(); + + // ----- internal EmptyOptions class ------------------------------------ + + /** + * An optimized {@link Options} implementation for representing empty + * {@link Options}. + * + * @param the type of the {@link Options} + */ + private static final class EmptyOptions extends Options + { + + // ----- constructors ----------------------------------------------- + + /** + * Constructs an {@link EmptyOptions} + */ + public EmptyOptions() + { + super(null); + } + + // ----- Options methods -------------------------------------------- + + @Override + public U get(Class clzOption) + { + return getDefaultFor(clzOption); + } + + @Override + public U get(Class clzOption, U optDefault) + { + return optDefault; + } + + @Override + public boolean contains(Class clzOption) + { + return false; + } + + @Override + public boolean contains(T option) + { + return false; + } + + @Override + public Iterable getInstancesOf(Class clz) + { + return Collections.EMPTY_SET; + } + + @Override + public T[] asArray() + { + return (T[]) EMPTY; + } + + // ----- Object methods --------------------------------------------- + + @Override + public String toString() + { + return "EmptyOptions{}"; + } + + // ----- constants -------------------------------------------------- + + /** + * The empty array of options. + */ + private static final Object[] EMPTY = {}; + } + + // ----- Default annotation --------------------------------------------- + + /** + * Defines how an {@link Options} collection may automatically determine a + * suitable default value for a specific class of option at runtime + * when the said option does not exist in an {@link Options} collection. + * + * For example, the {@link Default} annotation can be used to specify that + * a public static no-args method can be used to determine a default value. + *

+     * public class Color {
+     *     ...
+     *     @Options.Default
+     *     public static Color getDefault() {
+     *         ...
+     *     }
+     *     ...
+     * }
+     * 
+ * + * Similarly, the {@link Default} annotation can be used to specify a + * public static field to use for determining a default value. + *

+     * public class Color {
+     *     ...
+     *     @Options.Default
+     *     public static Color BLUE = ...;
+     *     ...
+     * }
+     * 
+ * + * Alternatively, the {@link Default} annotation can be used to specify that + * the public no-args constructor for a public class may be used for + * constructing a default value. + *

+     * public class Color {
+     *     ...
+     *     @Options.Default
+     *     public Color() {
+     *         ...
+     *     }
+     *     ...
+     * }
+     * 
+ * + * Lastly when used with an enum, the {@link Default} annotation + * can be used to specify the default enum constant. + *

+     * public enum Color {
+     *     RED,
+     *
+     *     GREEN,
+     *
+     *     @Options.Default
+     *     BLUE;   // blue is the default color
+     * }
+     * 
+ * + * @see Options#get(Class) + */ + @Documented + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD}) + public @interface Default + { + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/SafeClock.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/SafeClock.java new file mode 100644 index 0000000000000..f689e5da11666 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/SafeClock.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import java.util.concurrent.atomic.AtomicBoolean; + +import java.security.AccessController; +import java.security.PrivilegedAction; + + +/** + * SafeClock maintains a "safe" time in milliseconds. + *

+ * Unlike the {@link System#currentTimeMillis()} this clock guarantees that + * the time never "goes back". More specifically, when queried twice on the + * same thread, the second query will never return a value that is less then + * the value returned by the first. + *

+ * If it is detected that the system clock moved backward, an attempt will be + * made to gradually compensate the safe clock (by slowing it down), so in the + * long run the safe time is the same as the system time. + *

+ * The SafeClock supports the concept of "clock jitter", which is a small + * time interval that the system clock could fluctuate by without a + * corresponding passage of wall time. + *

+ * In most cases the {@link SafeClock#INSTANCE} singleton should be used + * rather then creating new SafeClock instances. + * + * @author mf 2009.12.09 + */ +public class SafeClock + extends AtomicBoolean // embedded "lock" which will be on the same cache line as the clock's data members + { + /** + * Create a new SafeClock with the default maximum expected jitter as + * specified by the {@link #DEFAULT_JITTER_THRESHOLD} constant. + */ + public SafeClock() + { + this(System.currentTimeMillis()); + } + + /** + * Create a new SafeClock with the default maximum expected jitter as + * specified by the {@link #DEFAULT_JITTER_THRESHOLD} constant. + * + * @param ldtUnsafe the current unsafe time + */ + public SafeClock(long ldtUnsafe) + { + this(ldtUnsafe, DEFAULT_JITTER_THRESHOLD); + } + + /** + * Create a new SafeClock with the specified jitter threshold. + * + * @param ldtUnsafe the current unsafe time + * @param lJitter the maximum expected jitter in the underlying system + * clock + */ + public SafeClock(long ldtUnsafe, long lJitter) + { + m_ldtLastSafe = m_ldtLastUnsafe = ldtUnsafe; + m_lJitter = lJitter; + } + + /** + * Returns a "safe" current time in milliseconds. + * + * @return the difference, measured in milliseconds, between the + * corrected current time and midnight, January 1, 1970 UTC. + */ + public final long getSafeTimeMillis() + { + return getSafeTimeMillis(System.currentTimeMillis()); + } + + /** + * Returns a "safe" current time in milliseconds. + * + * @param ldtUnsafe the current unsafe time + * + * @return the difference, measured in milliseconds, between the + * corrected current time and midnight, January 1, 1970 UTC. + */ + public final long getSafeTimeMillis(long ldtUnsafe) + { + // optimization for heavy concurrent load: if no time has passed, or + // time jumped back within the expected jitter just return the last + // time and avoid CAS contention; keep short to encourage hot-spotting + long lDelta = ldtUnsafe - m_ldtLastUnsafe; + + return lDelta == 0 || (lDelta < 0 && lDelta >= -m_lJitter) + ? m_ldtLastSafe // common case during heavy load + : updateSafeTimeMillis(ldtUnsafe); + } + + /** + * Returns the last "safe" time as computed by a previous call to the + * {@link #getSafeTimeMillis} method. + *

+ * Note: Since the underlying field is non-volatile, the returned value + * is only guaranteed to be no less than the last value returned by + * getSafeTimeMillis() call on the same thread. + * + * @return the last "safe" time in milliseconds + */ + public final long getLastSafeTimeMillis() + { + return m_ldtLastSafe; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Updates and returns a "safe" current time in milliseconds based on the + * "unsafe" time. + * + * @param ldtUnsafe the unsafe current time in milliseconds + * + * @return the corrected safe time + */ + protected long updateSafeTimeMillis(long ldtUnsafe) + { + if (compareAndSet(false, true)) + { + try + { + long lJitter = m_lJitter; + long ldtLastSafe = m_ldtLastSafe; + long lDelta = ldtUnsafe - m_ldtLastUnsafe; + long ldtNewSafe = ldtLastSafe; + + if (lDelta > 0) + { + // unsafe progressed + if (ldtUnsafe >= ldtLastSafe) + { + // common case; unsafe is at or ahead of safe; sync clocks + ldtNewSafe = ldtUnsafe; + } + else if (lDelta > lJitter && ldtLastSafe - ldtUnsafe <= lJitter) + { + // unsafe is behind safe and jumped; the jump brought it + // very close (within jitter) to where it was before the + // corresponding regression; this appears to be jitter, hold + // safe and avoid recording anything about this bogus jump as + // that could artificially push safe into the future + return ldtLastSafe; + } + else + { + // unsafe is behind safe and progressed; progress safe slowly + // at half the measured delta or every other ms if delta is 1ms + // allowing unsafe to eventually catch up + ldtNewSafe += lDelta == 1 ? ldtUnsafe % 2 : lDelta / 2; + } + } + else if (lDelta >= -lJitter) + { + // unsafe made an insignificant (within jitter) regression; or + // didn't move at all; hold safe and avoid recording anything about + // this bogus jump as that could artificially push safe into the future + // Note: the same cases are handled in getSafeTimeMillis() but based + // on synchronization ordering it may not be detected until here + return ldtLastSafe; + } + + // except in the case of jitter we update our clocks + m_ldtLastUnsafe = ldtUnsafe; + return m_ldtLastSafe = ldtNewSafe; + } + finally + { + set(false); // unlock + } + } + else + { + // some other thread has locked the clock we have a few options + // - block until they complete, but who likes global contention + // - spin until they complete, but then we just waste CPU, and for what gain? + // - pretend like time has not advanced, no worse then the above and we get to do useful work + return m_ldtLastSafe; // note since we've attempted the CAS this is as good as a volatile read + } + } + + // ----- data members --------------------------------------------------- + + /** + * The last known safe time value. + */ + protected long m_ldtLastSafe; + + /** + * The last recorded unsafe time value. + */ + protected long m_ldtLastUnsafe; + + /** + * The maximum expected jitter exposed by the underlying unsafe clock. + */ + protected final long m_lJitter; + + // ----- constants ------------------------------------------------------ + + /** + * SafeClock singleton. + */ + public static final SafeClock INSTANCE = new SafeClock(); + + /** + * The default jitter threshold. + */ + public static final long DEFAULT_JITTER_THRESHOLD = Long.valueOf(AccessController.doPrivileged( + new PrivilegedAction() + { + public String run() + { + return System.getProperty(SafeClock.class.getName() + ".jitter", "16"); + } + })); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Sentry.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Sentry.java new file mode 100644 index 0000000000000..84b62bbd39400 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Sentry.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +/** + * AutoCloseable which provides access to a resource, and releases a lock upon being closed. + * + * @see AutoLock#acquire() + * + * @author mf 2014.10.02 + */ +public interface Sentry + extends AutoCloseable + { + @Override + void close(); + + /** + * Return the resource associated with the sentry. + *

+ * This resource is only valid until the sentry is closed. + * + * @return the resource. + */ + public R getResource(); + } + + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/SimpleAssociationPile.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/SimpleAssociationPile.java new file mode 100644 index 0000000000000..4f5b47179edf7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/SimpleAssociationPile.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import com.oracle.coherence.common.base.Associated; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Set; + +/** + * Simple thread-safe {@link AssociationPile} implementation based on a + * LinkedList. + * + * @author gg, jh 2014.03.27 + */ +public class SimpleAssociationPile + implements AssociationPile + { + + // ----- constructors --------------------------------------------------- + + /** + * Default constructor. + */ + public SimpleAssociationPile() + { + } + + // ----- AssociationPile interface -------------------------------------- + + @Override + public synchronized boolean add(E element) + { + f_list.add(element); + + m_fAvailable = true; + + return true; + } + + @Override + public synchronized E poll() + { + if (f_list.isEmpty()) + { + m_fAvailable = false; + return null; + } + + E elFirst = f_list.getFirst(); + + Contention contention = lockAssociation(elFirst, Contention.NONE); + if (contention == Contention.NONE) + { + f_list.removeFirst(); + if (f_list.isEmpty()) + { + m_fAvailable = false; + } + return elFirst; + } + + Iterator iter = f_list.iterator(); + iter.next(); // already looked at the first item + + while (iter.hasNext()) + { + E el = iter.next(); + + contention = lockAssociation(el, contention); + if (contention == Contention.NONE) + { + iter.remove(); + return el; + } + } + + return null; + } + + @Override + public synchronized void release(E element) + { + unlockAssociation(element); + } + + @Override + public synchronized int size() + { + return f_list.size(); + } + + @Override + public boolean isAvailable() + { + return m_fAvailable; + } + + // ----- helpers -------------------------------------------------------- + + /** + * Lock the specified element's association + * + * @param element the element with a potential association to lock + * @param contention the maximum contention level that has been previously + * reported by this pile + * + * @return {@link Contention#NONE} if the given element does not have an + * association or if it's association was successfully locked; + * {@link Contention#SINGLE} if the element is associated with + * an element that was polled, but hasn't been released; + * {@link Contention#ALL} if the element is associated with + * {@link AssociationPile#ASSOCIATION_ALL} and there are elements + * with a non-null association element that were polled, but + * haven't been released; + */ + private Contention lockAssociation(E element, Contention contention) + { + Object oAssoc = element instanceof Associated ? + ((Associated) element).getAssociatedKey() : null; + if (oAssoc == null) + { + return Contention.NONE; + } + + switch (contention) + { + case ALL: + return contention; + + default: + if (oAssoc == ASSOCIATION_ALL) + { + if (!f_setLocked.isEmpty()) + { + // beyond this point only non-associated elements are eligible + return Contention.ALL; + } + + m_fAllLocked = true; + return Contention.NONE; + } + else + { + return !m_fAllLocked && f_setLocked.add(oAssoc) ? + Contention.NONE : Contention.SINGLE; + } + } + } + + /** + * Unlock the specified element's association + * + * @param element the element with a potential association to unlock + * + * @return true if the given element does not have an association or if + * it's association was successfully unlocked; false otherwise + */ + private void unlockAssociation(E element) + { + Object oAssoc = element instanceof Associated ? + ((Associated) element).getAssociatedKey() : null; + + if (oAssoc != null) + { + if (oAssoc == ASSOCIATION_ALL) + { + if (m_fAllLocked) + { + m_fAllLocked = false; + } + } + else + { + f_setLocked.remove(oAssoc); + } + } + } + + + // ----- data members --------------------------------------------------- + + /** + * Results of the {@link #lockAssociation} method. + */ + public enum Contention + { + NONE, // non-contended + SINGLE, // contended due to a single association + ALL, // contended due to ASSOCIATION_ALL + } + + /** + * The queue of elements. + */ + private final LinkedList f_list = new LinkedList(); + + /** + * The set of "locked" associations. + */ + private final Set f_setLocked = new HashSet(); + + /** + * The flag that indicates that ALL_ASSOCIATION is "locked". + */ + private volatile boolean m_fAllLocked; + + /** + * The availability flag. + */ + private volatile boolean m_fAvailable; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/ThreadGate.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/ThreadGate.java new file mode 100644 index 0000000000000..a292446129b1f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/ThreadGate.java @@ -0,0 +1,1405 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + + +import com.oracle.coherence.common.base.Blocking; +import com.oracle.coherence.common.base.IdentityHolder; +import com.oracle.coherence.common.base.MutableLong; +import com.oracle.coherence.common.collections.ConcurrentHashMap; +import com.oracle.coherence.common.collections.InflatableMap; +import java.util.concurrent.atomic.AtomicLong; + + +/** +* Use this class in cases that large numbers of threads can operate +* concurrently with an additional requirement that all threads be blocked for +* certain operations. The algorithm is based on a gate concept, allowing +* threads in (enter) and out (exit), but occasionally shutting the gate (close) +* such that other threads cannot enter and exit. However, since threads may +* "be inside", the gate cannot fully close until they leave (exit). Once all +* threads are out, the gate is closed, and can be re-opened (open) or +* permanently closed (destroy). +* +* Each call to enter requires a corresponding call to exit, similar to the JVM +* implementation of the "synchronized" keyword that places a monitorenter op +* that the beginning of the synchronized portion and protects the synchronized +* portion with a try..finally construct that ensures the execution of a +* monitorexit op. For example, the following would ensure proper clean-up +* using a ThreadGate: +*

+* gate.enter();
+* try
+*     {
+*     ...
+*     }
+* finally
+*     {
+*     gate.exit();
+*     }
+* 
+* or simply: +*
+* try (Sentry sentry = gate.enter())
+*     {
+*     ...
+*     }
+* 
+* Similarly, each call to close() should be matched with a call to open(), +* unless the gate is being destroyed: +*
+* gate.close();
+* try
+*     {
+*     ...
+*     }
+* finally
+*     {
+*     gate.open();
+*     }
+* 
+* or simply: +*
+* try (Sentry sentry = gate.close())
+*     {
+*     ...
+*     }
+* 
+* and to permanently close, i.e. destroy the gate: +*
+* gate.close();
+* gate.destroy();
+* 
+* The enter/exit calls can be nested; the same thread can invoke enter multiple +* times as long as exit is invoked a corresponding number of times. The +* close/open calls work in the same manner. Lastly, the thread that closes the +* gate may continue to enter/exit the gate even when it is closed since that +* thread has exclusive control of the gate. +*

+* This Gate implementation also supports non-thread-based lock contenders. When non-thread-based +* contenders are used the contender may internally be synchronized on during gate operations. +* While any object can be used as a non-thread contender, {@link ThreadGate.LiteContender} instances +* are preferable. +*

+* For performance critical cases which don't require reentrancy consider the {@link NonReentrant} variant. +* +* @author cp 2003.05.26; mf 2007.04.27; coh 2010.08.13; mf 2017.03.21 +* @since Coherence 2.2 +* +* @param the type of resource protected by this gate +*/ +public class ThreadGate + implements Gate + { + /** + * Default constructor. + */ + public ThreadGate() + { + this (null); + } + + /** + * Construct a gate protecting the specified resource. + * + * @param resource the resource, or null + */ + public ThreadGate(R resource) + { + f_resource = resource; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean barEntry(long cMillis) + { + return barEntryInternal(Thread.currentThread(), cMillis); // thread needed even if non-reentrant + } + + /** + * Bar entry, using the specified (potentially non-thread based) lock contender. + * + * @param oContender the lock contender + * @param cMillis the timeout + * + * @return true iff barred + */ + public boolean barEntry(Object oContender, long cMillis) + { + synchronized (oContender) + { + return barEntryInternal(oContender, cMillis); + } + } + + /** + * Internal version of bar-entry. + * + * @param oContender the lock contender + * @param cMillis the timeout + * + * @return true iff barred + */ + protected boolean barEntryInternal(Object oContender, long cMillis) + { + if (oContender == getCloser()) + { + // we've already closed or are closing the gate + setCloseCount(getCloseCount() + 1); + return true; + } + + synchronized (this) + { + while (true) + { + if (getCloser() == null) + { + // transition to CLOSING state + if (updateStatus(GATE_CLOSING) == GATE_DESTROYED) + { + // oops gate was destroyed while we were waiting + updateStatus(GATE_DESTROYED); + throw new IllegalStateException("ThreadGate.close:" + + " ThreadGate has been destroyed."); + } + + setCloser(oContender); + setCloseCount(1); + return true; + } + + // gate is already closed or closing, wait for notification + cMillis = doWait(cMillis); + if (cMillis == 0) + { + return false; + } + } + } + } + + /** + * Wait to close the gate. + * + * @return an AutoCloseable which can be used with a try-with-resource block to perform the corresponding {@link #open}. + */ + public Sentry close() + { + close(-1); + return f_openSentry; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean close(long cMillis) + { + return closeInternal(Thread.currentThread(), cMillis); // thread needed even if non-reentrant + } + + /** + * Close the gate, using the specified (potentially non-thread based) lock contender. + * + * @param oContender the lock contender + * @param cMillis the timeout + * + * @return true iff barred + */ + public boolean close(Object oContender, long cMillis) + { + synchronized (oContender) + { + return closeInternal(oContender, cMillis); + } + } + + /** + * Internal version of close. + * + * @param oContender the lock contender + * @param cMillis the timeout + * + * @return true iff barred + */ + protected boolean closeInternal(Object oContender, long cMillis) + { + if (oContender == getCloser() && getStatus() == GATE_CLOSED) + { + // we've already closed the gate + setCloseCount(getCloseCount() + 1); + return true; + } + + AtomicLong atomicState = f_atomicState; + long cEnterThis = Math.min(1L, getEnterCount(oContender)); // cEnterThis is this just contenders contribution to the active count, which is at most 1 + long lStatusReq = EMPTY_GATE_OPEN | cEnterThis; + long lStatusEnd = EMPTY_GATE_CLOSED | cEnterThis; + boolean fReenter = false; + boolean fReopen = false; + + synchronized (this) + { + try + { + if (oContender == getCloser()) + { + lStatusReq = EMPTY_GATE_CLOSING; + + // if we've also "entered" we need to temporarily + // decrement the counter so that the last thread to + // exit the gate will know to notify us + if (cEnterThis > 0) + { + fReenter = true; + atomicState.addAndGet(-cEnterThis); + } + } + + while (true) + { + if (atomicState.compareAndSet(lStatusReq, lStatusEnd)) + { + // we've closed the gate + setCloseCount(getCloseCount() + 1); + setCloser(oContender); // in case we bypassed GATE_CLOSING + fReenter = fReopen = false; + return true; + } + else if (getCloser() == null) + { + // transition to CLOSING state + if (updateStatus(GATE_CLOSING) == GATE_DESTROYED) + { + // oops gate was destroyed while we were waiting + updateStatus(GATE_DESTROYED); + throw new IllegalStateException("ThreadGate.close: ThreadGate has been destroyed."); + } + + setCloser(oContender); + lStatusReq = EMPTY_GATE_CLOSING; + fReopen = true; // reopen if we fail + + // if we've also "entered" we need to temporarily + // decrement the counter so that the last thread to + // exit the gate will know to notify us + if (cEnterThis > 0) + { + fReenter = true; + atomicState.addAndGet(-cEnterThis); + } + + // as we've just transitioned to CLOSING we must + // retest the active count since exiting threads only + // notify if they when in the state is CLOSING, thus + // we can't go to doWait without retesting + continue; + } + + // gate is closed or closing, wait for notification + cMillis = doWait(cMillis); + if (cMillis == 0) + { + return false; + } + } + } + finally + { + // if we transitioned to closing but didn't make it to + // closed; re-open the gate + if (fReenter) + { + atomicState.addAndGet(cEnterThis); // undo temporary decrement + } + + if (fReopen) + { + setCloser(null); + updateStatus(GATE_OPEN); + notifyAll(); + } + } + } + } + + /** + * Destroy the thread gate. This method can only be invoked if the gate is + * already closed. + */ + public void destroy() + { + destroyInternal(Thread.currentThread()); + } + + /** + * Destroy the thread gate. This method can only be invoked if the gate is + * already closed. + * + * @param oContender the contender destroying the gate + */ + public void destroy(Object oContender) + { + synchronized (oContender) + { + destroyInternal(oContender); + } + } + + /** + * Internal version of destroy. + * + * @param oContender the contender destroying the gate + */ + protected synchronized void destroyInternal(Object oContender) + { + switch (getStatus()) + { + case GATE_CLOSED: + { + if (oContender != getCloser()) + { + throw new IllegalStateException( + "ThreadGate.destroy: Gate was not closed by " + oContender + "; " + + this); + } + + updateStatus(GATE_DESTROYED); + setCloser(null); + notifyAll(); + } + break; + + case GATE_DESTROYED: + // the gate has already been destroyed + break; + + default: + throw new IllegalStateException( + "ThreadGate.destroy: Gate is not closed! " + this); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean enter(long cMillis) + { + // optimized common-path; i.e. already entered or open gate which hasn't become full. + if (adjustThreadLocalEnters(1) > 1) + { + return true; // already entered + } + + AtomicLong atomicState = f_atomicState; + for (long lStatus = atomicState.get(); lStatus < FULL_GATE_OPEN; lStatus = atomicState.get()) + { + if (atomicState.compareAndSet(lStatus, lStatus + 1)) + { + // atomic set succeeded confirming that the gate + // remained open and that we made it in + return true; + } + } + // otherwise; fall through + + adjustThreadLocalEnters(-1); // undo our increment from above + return enterInternal(Thread.currentThread(), cMillis); + } + + /** + * Wait to enter the gate. + * + * @return an AutoCloseable which can be used with a try-with-resource block to perform the corresponding {@link #exit}. + */ + public Sentry enter() + { + enter(-1L); + return f_exitSentry; + } + + /** + * Enter the gate, using the specified (potentially non-thread based) lock contender. + * + * @param oContender the lock contender + * @param cMillis the timeout + * + * @return true iff barred + */ + public boolean enter(Object oContender, long cMillis) + { + synchronized (oContender) + { + return enterInternal(oContender, cMillis); + } + } + + /** + * Internal version of enter. + * + * @param oContender the lock contender + * @param cMillis the timeout + * + * @return true iff barred + */ + protected boolean enterInternal(Object oContender, long cMillis) + { + AtomicLong atomicState = f_atomicState; + + // increment local enter count and check if we are already entered + if (incrementEnterCount(oContender) > 1L) + { + // we'd already entered; all we needed to do was increment our contender enter count + return true; + } + else if (oContender == getCloser()) + { + // we've either closed the gate or are closing the gate. in either case we must be allowed to + // enter. Since our local enter count was not > 1 it must just be 1, and thus we need to increment the + // active count. + if ((atomicState.get() & ACTIVE_COUNT_MASK) == ACTIVE_COUNT_MASK) + { + // the gate has been entered more times then we can track, i.e. 2^60 + decrementEnterCount(oContender); + throw new IllegalStateException("The ThreadGate is full."); + } + + // We don't need to worry about concurrent overflow since no others can increment now, but they + // can still concurrently decrement and thus we must use the atomic increment operation rather + // then just a blind set call. + atomicState.incrementAndGet(); + return true; + } + + boolean fSuccess = false; + try + { + while (true) + { + long lStatus = atomicState.get(); + switch ((int) (lStatus >>> STATUS_OFFSET)) + { + case GATE_OPEN: + if ((lStatus & ACTIVE_COUNT_MASK) == ACTIVE_COUNT_MASK) + { + // the gate has been entered more times then we can track, i.e. 2^60 + throw new IllegalStateException("The ThreadGate is full."); + } + else if (atomicState.compareAndSet(lStatus, lStatus + 1)) + { + // atomic set succeeded confirming that the gate + // remained open and that we made it in + return fSuccess = true; + } + // we failed to atomically enter an open gate, which + // can happen if either the gate closed just as we entered + // or if another thread entered at the same time + break; // retry + + case GATE_CLOSING: + case GATE_CLOSED: + // we know that we were not already in the gate, and are + // not the one closing the gate; wait for it to open + synchronized (this) + { + long nStatus = getStatus(); + if (nStatus == GATE_CLOSING || nStatus == GATE_CLOSED) + { + // wait for the gate to open + cMillis = doWait(cMillis); + if (cMillis == 0L) + { + return false; + } + } + } + break; // retry + + case GATE_DESTROYED: + throw new IllegalStateException("ThreadGate.enter: ThreadGate has been destroyed."); + + default: + throw new IllegalStateException("ThreadGate.enter: ThreadGate has an invalid status. " + this); + } + } + } + finally + { + if (!fSuccess) + { + decrementEnterCount(oContender); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void exit() + { + exitInternal(Thread.currentThread()); + } + + /** + * Exit the gate, using the specified (potentially non-thread based) lock contender. + * + * @param oContender the lock contender + */ + public void exit(Object oContender) + { + synchronized (oContender) + { + exitInternal(oContender); + } + } + /** + * Internal version of exit. + * + * @param oContender the lock contender + */ + protected void exitInternal(Object oContender) + { + long cEnterThis = decrementEnterCount(oContender); + if (cEnterThis == 0) + { + // we've fully exited + if (f_atomicState.decrementAndGet() == EMPTY_GATE_CLOSING) + { + // we were the last to exit, and the gate is in the CLOSING state + // notify everyone, to ensure that we notify the closing thread + synchronized (this) + { + notifyAll(); + } + } + } + else if (cEnterThis < 0) + { + // Note: decrementEnterCount will not store a value less then 0, so we don't need to do a correction + throw new IllegalMonitorStateException("ThreadGate.exit: (" + oContender + ") has already exited! " + this); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void open() + { + openInternal(Thread.currentThread()); + } + + /** + * Open the gate, using the specified (potentially non-thread based) lock contender. + * + * @param oContender the lock contender + */ + public void open(Object oContender) + { + synchronized (oContender) + { + openInternal(oContender); + } + } + + /** + * Internal version of open. + * + * @param oContender the lock contender + */ + protected void openInternal(Object oContender) + { + if (oContender == getCloser()) + { + int cClosed = getCloseCount() - 1; + if (cClosed >= 0) + { + setCloseCount(cClosed); + if (cClosed == 0) + { + // we've opened the gate + synchronized (this) + { + updateStatus(GATE_OPEN); + setCloser(null); + notifyAll(); + } + } + return; + } + } + + throw new IllegalMonitorStateException( + "ThreadGate.open: Gate was not closed by " + oContender + ";" + this); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isClosedByCurrentThread() + { + return isClosedByInternal(Thread.currentThread()); + } + + /** + * Return true if the gate is closed by the specified contender. + * + * @param oContender the contender + * + * @return true if the gate is closed by the specified contender + */ + public boolean isClosedBy(Object oContender) + { + synchronized (oContender) + { + return isClosedByInternal(oContender); + } + } + + /** + * Return true if the gate is closed by the specified contender. + * + * @param oContender the contender + * + * @return true if the gate is closed by the specified contender. + */ + protected boolean isClosedByInternal(Object oContender) + { + return oContender == getCloser() && getStatus() == GATE_CLOSED; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnteredByCurrentThread() + { + return isEnteredByInternal(Thread.currentThread()); + } + + /** + * Determines if the specified non-thread contender has entered the gate and not yet exited. + * + * + * @param oContender the contender + * + * @return true if the specified contender has entered the gate + */ + public boolean isEnteredBy(Object oContender) + { + synchronized (oContender) + { + return isClosedByInternal(oContender); + } + } + + /** + * Determines if the specified non-thread contender has entered the gate and not yet exited. + * + * @param oContender the contender + * + * @return true if the specified contender has entered the gate + */ + protected boolean isEnteredByInternal(Object oContender) + { + return getEnterCount(oContender) > 0; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isClosed() + { + return getStatus() == GATE_CLOSED; + } + + // ----- internal helpers ----------------------------------------------- + + /** + * Wait up to the specified number of milliseconds for notification. + * Caller must be synchronized. + * + * @param cMillis the wait time + * + * @return the remaining wait time in milliseconds + */ + protected long doWait(long cMillis) + { + if (cMillis == 0) + { + return 0; + } + + long lTime = SafeClock.INSTANCE.getSafeTimeMillis(); + try + { + Blocking.wait(this, Math.max(0, cMillis)); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + onInterruptedException(e); + } + + return cMillis < 0 ? cMillis : + Math.max(0, cMillis - (SafeClock.INSTANCE.getSafeTimeMillis() - lTime)); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Return the number of entered contenders. + * + * @return the number of entered contenders. + */ + public int getActiveCount() + { + return (int) (f_atomicState.get() & ACTIVE_COUNT_MASK); + } + + /** + * Return the number of unmatched completed close/barEntry calls. + * + * @return the number of unmatched completed close/barEntry calls. + */ + public int getCloseCount() + { + return m_cClose; + } + + /** + * Specify the number of unmatched completed close/barEntry calls. + * + * The caller must have the gate closed/closing. + * + * @param cClose the close count + */ + protected void setCloseCount(int cClose) + { + m_cClose = cClose; + } + + /** + * Return the thread that is closing the gates. + * + * @return the thread that is closing the gates. + */ + protected Object getCloser() + { + return m_closer; + } + + /** + * Specify the thread that is closing the gates. + * + * The caller must be synchronized on the ThreadGate. + * + * @param oCloser the closer + */ + protected void setCloser(Object oCloser) + { + m_closer = oCloser; + } + + /** + * Return the current thread gate status. + * + * @return the current thread gate status. + */ + public int getStatus() + { + return (int) (f_atomicState.get() >>> STATUS_OFFSET); + } + + /** + * Update the current thread gate status, without changing the active count. + * + * The caller must hold synchronization on the ThreadGate. + * + * @param nStatus the new status + * + * @return the old status + */ + protected int updateStatus(int nStatus) + { + AtomicLong atomicState = f_atomicState; + long lStatus = ((long) nStatus) << STATUS_OFFSET; + while (true) + { + long lCurr = atomicState.get(); + long lNew = lStatus | (lCurr & ACTIVE_COUNT_MASK); + if (atomicState.compareAndSet(lCurr, lNew)) + { + return (int) (lCurr >>> STATUS_OFFSET); + } + } + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Provide a human-readable representation of this ThreadGate. + */ + @Override + public synchronized String toString() + { + String sState; + switch (getStatus()) + { + case GATE_OPEN: + sState = "GATE_OPEN"; + break; + case GATE_CLOSING: + sState = "GATE_CLOSING"; + break; + case GATE_CLOSED: + sState = "GATE_CLOSED"; + break; + case GATE_DESTROYED: + sState = "GATE_DESTROYED"; + break; + default: + sState = "INVALID"; + break; + } + + return "ThreadGate{State=" + sState + + ", ActiveCount=" + getActiveCount() + + ", CloseCount=" + getCloseCount() + + ", Closer= " + getCloser() + + '}'; + + } + + /** + * Handle an InterruptedException + * + * @param e the exception + */ + protected void onInterruptedException(InterruptedException e) + { + throw new RuntimeException(toString(), e); + } + + /** + * Helper method for creating a MutableLong for the EnterMap if one isn't already present. + * + * @param oHolder the contender holder + * + * @return the counter + */ + protected static MutableLong makeCounterFromHolder(IdentityHolder oHolder) + { + return makeCounter(oHolder.get()); + } + + /** + * Helper method for creating a MutableLong for the EnterMap if one isn't already present. + * + * @param oContender the contender + * + * @return the counter + */ + protected static MutableLong makeCounter(Object oContender) + { + if (oContender instanceof Thread) + { + // to be here we know that oContender is not the calling thread, we don't support this since + // we use TLO to maintain the thread based contender count + throw new IllegalArgumentException("thread based contenders may only be the current thread"); + } + + return new MutableLong(); + } + + /** + * Return the enter count for the specified contender + * + * @param oContender the contender + * + * @return the count + */ + protected long getEnterCount(Object oContender) + { + return oContender == Thread.currentThread() + ? adjustThreadLocalEnters(0) // most common case + : oContender instanceof LiteContender + ? ((LiteContender) oContender).getCount(this) + : oContender == f_atomicState // marker from non-reentrant path + ? 0 + : ensureEnterCountMap().getOrDefault(new IdentityHolder<>(oContender), + ThreadGate.makeCounter(oContender)).get(); + } + + /** + * Increment and return the enter count for the specified contender + * + * @param oContender the contender + * + * @return the new count + */ + protected long incrementEnterCount(Object oContender) + { + return oContender == Thread.currentThread() + ? adjustThreadLocalEnters(1) // most common case + : oContender instanceof LiteContender + ? ((LiteContender) oContender).incrementAndGet(this) + : oContender == f_atomicState // marker from non-reentrant path + ? 0 + : ensureEnterCountMap().computeIfAbsent(new IdentityHolder<>(oContender), + ThreadGate::makeCounterFromHolder).incrementAndGet(); + } + + /** + * Decrement and return the enter count for the specified contender + * + * @param oContender the contender + * + * @return the new count + */ + protected long decrementEnterCount(Object oContender) + { + return oContender == Thread.currentThread() + ? adjustThreadLocalEnters(-1) // most common case + : oContender instanceof LiteContender + ? ((LiteContender) oContender).decrementAndGet(this) + : oContender == f_atomicState // marker from non-reentrant path + ? 0 + : decrementEnterCountComplex(oContender); + } + + /** + * Complex (non-common case) implementation of contender decrement. + * + * @param oContender the contender + * + * @return the new count + */ + protected long decrementEnterCountComplex(Object oContender) + { + ConcurrentHashMap, MutableLong> map = ensureEnterCountMap(); + IdentityHolder oHolder = new IdentityHolder<>(oContender); + MutableLong cEnter = map.get(oHolder); + + if (cEnter == null) + { + if (oContender instanceof Thread) + { + // the specified thread may be entered, but we don't support exiting from another thread + throw new IllegalArgumentException("thread-based contenders may only be the current thread"); + } + return -1L; + } + + long c = cEnter.decrementAndGet(); + if (c <= 0) + { + // Avoid leaving garbage counters around, we may never hear from this oContender again + // ThreadGate prevents concurrent access for the same oContender, so we don't have to worry + // about a concurrent increment for the same contender. + map.remove(oHolder, cEnter); + } + + return c; + } + + /** + * Return the map which tracks the enter count for non-thread based contenders. + * + * @return the map + */ + protected ConcurrentHashMap, MutableLong> ensureEnterCountMap() + { + ConcurrentHashMap, MutableLong> map = m_mapContenderEnters; + if (map == null) + { + synchronized (this) + { + map = m_mapContenderEnters; + if (map == null) + { + map = m_mapContenderEnters = new ConcurrentHashMap<>(); + } + } + } + + return map; + } + + /** + * Increment the thread-local enter count. + * + * @param c the increment amount + * + * @return the new enter count + */ + protected int adjustThreadLocalEnters(int c) + { + int[] ai = f_tlcEnters.get(); + if (ai == null) + { + ai = new int[1]; + f_tlcEnters.set(ai); + } + + int i = ai[0] + c; + if (i >= 0) + { + ai[0] = i; + } + + return i; + } + + // ----- inner class: LiteContender ------------------------------------- + + /** + * An optimized implementation of a object to use for non-thread-based gate contenders. + *

+ * Using an object of any other type as the contender is allowable but may produce considerably more + * garbage then if a LiteContender is used. + * + * @author mf 2017.03.21 + */ + public static class LiteContender + extends InflatableMap // extend only to keep the total number of allocations to a minimum + { + /** + * Increment the contender's count for the specified gate. + * + * @param gate the gate + * + * @return the new count + */ + protected long incrementAndGet(ThreadGate gate) + { + return gate == m_gatePrimary + ? ++m_cEntersPrimary // common path + : incrementComplex(gate); + } + + /** + * Increment the contender's count for the specified non-primary gate. + * + * @param gate the gate + * + * @return the new count + */ + protected long incrementComplex(ThreadGate gate) + { + MutableLong cEnters = get(gate); + if (cEnters == null) + { + if (m_gatePrimary == null) + { + m_gatePrimary = gate; + return m_cEntersPrimary = 1; + } + + put(gate, cEnters = new MutableLong()); + } + + return cEnters.incrementAndGet(); + } + + /** + * Decrement the contender's count for the specified gate. + * + * @param gate the gate + * + * @return the new count + */ + protected long decrementAndGet(ThreadGate gate) + { + if (gate == m_gatePrimary) + { + long cEnters = --m_cEntersPrimary; + if (cEnters <= 0) + { + m_gatePrimary = null; + m_cEntersPrimary = 0; + } + + return cEnters; // common path + } + + MutableLong cEnters = get(gate); + return cEnters == null ? -1 : cEnters.decrementAndGet(); + } + + /** + * Return the contender's count for the specified gate. + * + * @param gate the gate + * + * @return the current count + */ + protected long getCount(ThreadGate gate) + { + if (gate == m_gatePrimary) + { + return m_cEntersPrimary; // common path + } + + MutableLong cEnters = get(gate); + return cEnters == null ? 0 : cEnters.get(); + } + + /** + * The primary (usually first) gate this LiteContender is used with. This avoids the creating of the underlying + * LiteMap.Entry and MutableLong on the common path, resulting LiteContender being the only object created. + */ + private ThreadGate m_gatePrimary; + + /** + * The enter count for the primary gate. + */ + private long m_cEntersPrimary; + } + + + // ----- inner class: NonReentrant -------------------------------------- + + /** + * A non-reentrant version of a ThreadGate. The non-reentrant version does not support lock + * promotion, but is much faster then the reentrant version, especially when the contenders are + * not LiteContenders (including Threads). + * @param the resource type + */ + public static class NonReentrant + extends ThreadGate + { + // performance tests against the reentrant gate have shown it to be about 3x faster then the reentrant + // version; and about 30% faster then locking/unlocking the read lock of Java's ReentrantReadWriteLock. + + /** + * Construct a NonReentrant gate. + */ + public NonReentrant() + { + this(null); + } + + /** + * Construct a NonReentrant gate protecting the specified resource. + * + * @param resource the resource + */ + public NonReentrant(R resource) + { + super(resource); + } + + @Override + public boolean enter(long cMillis) + { + // optimized common-path for non-reentrant enter; i.e. open gate which hasn't become full. + AtomicLong atomicState = f_atomicState; + for (long lStatus = atomicState.get(); lStatus < FULL_GATE_OPEN; lStatus = atomicState.get()) + { + if (atomicState.compareAndSet(lStatus, lStatus + 1L)) + { + // atomic set succeeded confirming that the gate + // remained open and that we made it in + return true; + } + } + // otherwise; fall through + + return enterInternal(f_atomicState /*marker*/, cMillis); + } + + @Override + public boolean enter(Object oContender, long cMillis) + { + return enter(cMillis); // contender doesn't matter for non-reentrant + } + + @Override + public void exit() + { + exitInternal(f_atomicState); + } + + @Override + public void exit(Object oContender) + { + exitInternal(f_atomicState); // contender doesn't matter for non-reentrant + } + + @Override + protected void exitInternal(Object oContender) + { + // unlike re-entrant case we need to do extra checks to ensure we don't corrupt the state + long lStatus = f_atomicState.decrementAndGet(); + if (lStatus == EMPTY_GATE_CLOSING) + { + // we were the last to exit, and the gate is in the CLOSING state + // notify everyone, to ensure that we notify the closing thread + synchronized (this) + { + notifyAll(); + } + } + else if (lStatus < 0) + { + throw new IllegalStateException(); + } + // else; common path + } + } + + // ----- constants ------------------------------------------------------ + + /** + * GATE_OPEN: Threads may enter and exit the gates. + */ + public static final int GATE_OPEN = 0; + + /** + * GATE_CLOSING: A thread is waiting to be the only thread inside the + * gates; other threads can only exit. + */ + public static final int GATE_CLOSING = 1; + + /** + * GATE_CLOSED: A single thread is inside the gates; other threads cannot + * enter. + */ + public static final int GATE_CLOSED = 2; + + /** + * GATE_DESTROYED: Life-cycle is complete; the object is no longer usable. + */ + public static final int GATE_DESTROYED = 3; + + /** + * The bit offset at which the GATE_* status is stored within f_atomicState. + */ + private static final int STATUS_OFFSET = 60; + + /** + * The bit mask covering the portion of f_atomicState used to store the + * number contenders currently entered. + */ + private static final long ACTIVE_COUNT_MASK = -1L >>> (64 - STATUS_OFFSET); + + /** + * EMPTY_GATE_OPEN: Threads may enter, exit, or close the gates. + */ + private static final long EMPTY_GATE_OPEN = ((long) GATE_OPEN << STATUS_OFFSET); + + /** + * FULL_GATE_OPEN indicates a state at which no more enters are allowable. + */ + private static final long FULL_GATE_OPEN = EMPTY_GATE_OPEN | ACTIVE_COUNT_MASK; + + /** + * EMPTY_GATE_CLOSING: Closing thread may close the gates, all entered threads + * have exited. + */ + private static final long EMPTY_GATE_CLOSING = ((long) GATE_CLOSING << STATUS_OFFSET); + + /** + * EMPTY_GATE_CLOSED: Gates are closed, with no threads inside. + */ + private static final long EMPTY_GATE_CLOSED = ((long) GATE_CLOSED << STATUS_OFFSET); + + + // ----- data members --------------------------------------------------- + + /** + * The protected resource. + */ + private final R f_resource; + + /** + * The state of the ThreadGate, including: + *

+    * bits  0 - 59 store the number of entered contenders
+    * bits 60 - 61 store the state GATE_* value
+    * bit  62 - 63 always zero
+    * 
+ */ + protected final AtomicLong f_atomicState = new AtomicLong(); + + /** + * Number of unmatched completed close/barEntry calls. + */ + private int m_cClose; + + /** + * The closer (usually a thread) that is closing the gates. + */ + private volatile transient Object m_closer; + + /** + * Count of how many unmatched enter calls per thread. + * + * This rather odd ThreadLocal allows us to avoid having any non JDK class stored in the ThreadLocal and as + * such allows our Class to be collected when needed. We never remove the ThreadLocal, and allow it to be + * cleaned up by the ThreadLocal machinery itself, i.e. either the Thread terminates or the ThreadLocalMap + * which is key'd by weak reference will clean up this entry when space is needed. This is all done to avoid + * the need to do a tlc.remove() each time the count reaches zero, as that more the doubles the cost of + * entering the gate. + */ + private final ThreadLocal f_tlcEnters = new ThreadLocal<>(); + + /** + * When using not using thread-based or LiteContender contenders, this map will be created and + * will hold the non-zero counts for only those contenders. In most cases this map will never be created. + * + * The map is key'd by IdentityHolders to ensure that key equality is based on identity equality of the + * contender rather then state equality. + */ + private volatile ConcurrentHashMap, MutableLong> m_mapContenderEnters; + + /** + * Sentry to return from {@link #enter} that will {@link #exit} when the sentry is closed. + */ + protected final Sentry f_exitSentry = new Sentry() + { + @Override + public R getResource() + { + return f_resource; + } + + @Override + public void close() + { + exit(); + } + }; + + /** + * Sentry to return from {@link #close} that will {@link #open} when the sentry is closed. + */ + protected final Sentry f_openSentry = new Sentry() + { + @Override + public R getResource() + { + return f_resource; + } + + @Override + public void close() + { + open(); + } + }; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Threads.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Threads.java new file mode 100644 index 0000000000000..3f270083d8411 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Threads.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import java.lang.management.LockInfo; +import java.lang.management.ManagementFactory; +import java.lang.management.MonitorInfo; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Thread related helpers. + * + * @author mf 2016.08.17 + */ +public final class Threads + { + /** + * Get the full thread dump. + * + * @return a string containing the thread dump + */ + public static String getThreadDump() + { + return getThreadDump(DUMP_LOCKS); + } + + /** + * Get the full thread dump. + * + * @param fLocks true if lock related information should be gathered. + * + * @return a string containing the thread dump + */ + public static String getThreadDump(boolean fLocks) + { + return getThreadDump(fLocks ? LockAnalysis.FULL : LockAnalysis.NONE); + } + /** + * Get the full thread dump. + * + * @param locks the lock analysis mode + * + * @return a string containing the thread dump + */ + public static String getThreadDump(LockAnalysis locks) + { + String sMsg; + boolean fLocks; + switch (locks) + { + case FULL: + fLocks = true; + sMsg = ""; + break; + case OWNERSHIP: + fLocks = true; + sMsg = "(excluding deadlock analysis)"; + break; + default: + fLocks = false; + sMsg = "(excluding locks and deadlock analysis)"; + break; + } + + long ldtStart = System.currentTimeMillis(); + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + Set setDeadLocked = locks == LockAnalysis.FULL ? collectDeadlockedIds(bean) : null; + + StringBuilder sbAll = new StringBuilder("Full Thread Dump: ").append(sMsg); + StringBuilder sbThreads = new StringBuilder(); + StringBuilder sbDeadlocks = new StringBuilder(); + StringBuilder sbTemp = new StringBuilder(); + ThreadInfo[] ainfo = bean.dumpAllThreads(fLocks && bean.isObjectMonitorUsageSupported(), + fLocks && bean.isSynchronizerUsageSupported()); + + for (ThreadInfo info : ainfo) + { + boolean fDeadlocked = locks == LockAnalysis.FULL && setDeadLocked.contains(info.getThreadId()); + + sbTemp.setLength(0); + collectThreadHeader(info, sbTemp, fDeadlocked); + collectStackTrace(info, sbTemp); + collectLockedSyncs(info, sbTemp); + + sbThreads.append(sbTemp); + if (fDeadlocked) + { + sbDeadlocks.append(sbTemp); + } + } + + long cMillis = System.currentTimeMillis() - ldtStart; + if (cMillis > 1000) + { + sbAll.append(" took ").append(new Duration(cMillis, Duration.Magnitude.MILLI)); + } + + sbAll.append("\n") + .append(sbThreads); + + if (locks == LockAnalysis.FULL && !setDeadLocked.isEmpty()) + { + sbAll.append("\n Found following deadlocked threads:\n"); + sbAll.append(sbDeadlocks); + } + + return sbAll.toString(); + } + + /** + * Collect the deadlocked thread ids. + * + * @param bean the ThreadMXBean + * + * @return a set of thread ids + */ + protected static Set collectDeadlockedIds(ThreadMXBean bean) + { + long[] alThreadId = bean.isSynchronizerUsageSupported() ? + bean.findDeadlockedThreads() : + bean.findMonitorDeadlockedThreads(); + + if (alThreadId == null || alThreadId.length == 0) + { + return Collections.emptySet(); + } + + Set setIds = new HashSet(); + + ThreadInfo[] ainfo = bean.getThreadInfo(alThreadId, Integer.MAX_VALUE); + for (ThreadInfo info : ainfo) + { + if (info != null) + { + setIds.add(info.getThreadId()); + } + } + return setIds; + } + + /** + * Collect the header information for a given thread. + * + * @param infoThread the ThreadInfo that the thread is associated with + * @param sbTrace the StringBuilder to append the info to + * @param fDeadlocked true iff the thread is in deadlock + */ + protected static void collectThreadHeader(ThreadInfo infoThread, StringBuilder sbTrace, boolean fDeadlocked) + { + sbTrace.append("\n\"") + .append(infoThread.getThreadName()) + .append("\" id=") + .append(infoThread.getThreadId()) + .append(" State:") + .append(fDeadlocked ? "DEADLOCKED" : infoThread.getThreadState()); + + if (infoThread.isSuspended()) + { + sbTrace.append(" (suspended)"); + } + if (infoThread.isInNative()) + { + sbTrace.append(" (in native)"); + } + sbTrace.append("\n"); + } + + /** + * Collect locked synchronizers for a given thread. + * + * @param infoThread the ThreadInfo that the thread is associated with + * @param sbTrace the StringBuilder to append the info to + */ + protected static void collectLockedSyncs(ThreadInfo infoThread, StringBuilder sbTrace) + { + LockInfo[] aLock = infoThread.getLockedSynchronizers(); + if (aLock.length > 0) + { + sbTrace.append("\n\tLocked synchronizers:\n"); + for (LockInfo info : aLock) + { + sbTrace.append("\t").append(info).append("\n"); + } + } + } + + /** + * Collect stack trace for a given thread. + * + * @param infoThread the ThreadInfo that the thread is associated with + * @param sbTrace the StringBuilder to append the info to + */ + protected static void collectStackTrace(ThreadInfo infoThread, StringBuilder sbTrace) + { + StackTraceElement[] aStackElement = infoThread.getStackTrace(); + MonitorInfo[] aMonitor = infoThread.getLockedMonitors(); + LockInfo infoLock = infoThread.getLockInfo(); + + for (int iDepth = 0, c = aStackElement.length; iDepth < c; iDepth++) + { + sbTrace.append("\tat ") + .append(aStackElement[iDepth]) + .append("\n"); + + if (iDepth == 0 && infoLock != null) + { + String sOwner = infoThread.getLockOwnerName(); + + sbTrace.append("\t- ") + .append(sOwner != null ? "waiting to lock " : "waiting on ") + .append(infoLock); + + if (sOwner != null) + { + sbTrace.append(" owned by:\"") + .append(sOwner) + .append("\" id=") + .append(infoThread.getLockOwnerId()); + } + + sbTrace.append("\n"); + } + + for (MonitorInfo info : aMonitor) + { + if (info.getLockedStackDepth() == iDepth) + { + sbTrace.append("\t- locked ") + .append(info) + .append("\n"); + } + } + } + } + + /** + * Enum for various forms of lock analysis. + */ + public enum LockAnalysis + { + /** + * Perform no lock analysis. + */ + NONE, + + /** + * List the locks held by a thread. + */ + OWNERSHIP, + + /** + * List the locks held by a thread and perform deadlock detection. + */ + FULL + } + + // ----- constants ------------------------------------------------------ + + /** + * If true then {@link #getThreadDump() thread dumps} will include lock and synchronizer information. Such + * dumps can be more expensive to obtain, and can ultimately pause the JVM. + */ + private static final LockAnalysis DUMP_LOCKS ; + + static + { + String sLock = System.getProperty(Threads.class.getName() + ".dumpLocks", LockAnalysis.OWNERSHIP.name()); + if ("true".equalsIgnoreCase(sLock)) + { + DUMP_LOCKS = LockAnalysis.FULL; + } + else if ("false".equalsIgnoreCase(sLock)) + { + DUMP_LOCKS = LockAnalysis.NONE; + } + else + { + DUMP_LOCKS = LockAnalysis.valueOf(sLock); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Timers.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Timers.java new file mode 100644 index 0000000000000..e3d4ac8a88dfb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/Timers.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util; + +import com.oracle.coherence.common.base.Disposable; +import com.oracle.coherence.common.internal.util.Timer; +import com.oracle.coherence.common.internal.util.TimerTask; + +import java.util.concurrent.atomic.AtomicLong; + +import java.util.Set; + +/** + * Timer related helpers. + * + * @author mf 2014.07.16 + */ +public class Timers + { + /** + * Schedule a short non-blocking task for future execution. + * + * @param task the task to run + * @param cMillis the delay before execution + * + * @return a Disposable object which can be used to cancel the scheduled task + */ + public static Disposable scheduleNonBlockingTask(final Runnable task, long cMillis) + { + return scheduleNonBlockingTask(task, cMillis, null); + } + + /** + * Schedule a short non-blocking task for future execution. + * + * @param task the task to run + * @param cMillis the delay before execution + * @param setPending optional Set to add the returned Disposables to upon scheduling (i.e. now), + * and to delete from upon running/canceling. Note, modifications to the set will + * be performed while synchronized on the set. + * + * @return a Disposable object which can be used to cancel the scheduled task + */ + public static Disposable scheduleNonBlockingTask(final Runnable task, long cMillis, Set setPending) + { + final AtomicLong cTasks = s_cNonBlockingTimerTasks; + + long cExpected; + do + { + cExpected = cTasks.get(); + if (cExpected == 0) + { + // protect transition from 0, we may need to create the timer + Class clz = Timers.class; + synchronized (clz) + { + cExpected = cTasks.get(); + if (cExpected == 0 && s_timerNonBlocking == null) + { + Timer timer = s_timerNonBlocking = new Timer("NonBlockingTimer", /*fDaemon*/ true); + + // schedule a repeating task to shutdown the thread if it becomes idle (COH-6531) + timer.scheduleAtFixedRate(new TimerTask() + { + @Override + public void run() + { + if (cTasks.get() == 0) + { + synchronized (clz) // the capture of clz also ensures that the Timers class can't be GC'd, thus preserving it's static state + { + if (cTasks.get() == 0) + { + s_timerNonBlocking.cancel(); + s_timerNonBlocking = null; + } + } + } + } + }, /*delay*/ 5000, /*interval*/ 5000); + } + // else; timer was still active; just revive it by setting the count + + cTasks.incrementAndGet(); // under sync ensures that the above shutdown task won't see 0 + break; + } + } + // else we have at least one task try to CAS to increment from non-zero + } + while (!cTasks.compareAndSet(cExpected, cExpected + 1)); + + // we've ensured our task is in cTasks, thus the Timer cannot be shutdown and s_timer cannot be null + TimerTask taskWrapper = new PendingTask(task, cTasks, setPending); + + if (setPending != null) + { + synchronized (setPending) + { + setPending.add(taskWrapper); + } + } + + s_timerNonBlocking.schedule(taskWrapper, cMillis); + + return taskWrapper; + } + + // ----- inner class: PendingTask --------------------------------------- + + /** + * PendingTask is a TimerTask which allows for "safe" cancellation. + */ + protected static class PendingTask + extends TimerTask + { + public PendingTask(Runnable task, AtomicLong cTasks, Set set) + { + m_task = task; + m_cTasks = cTasks; + m_set = set; + } + + public void run() + { + m_cTasks.decrementAndGet(); + Set set = m_set; + if (set != null) + { + synchronized (set) + { + set.remove(this); + } + } + + Runnable task = m_task; + if (task != null) + { + task.run(); + } + } + + @Override + public boolean cancel() + { + Set set = m_set; + if (set != null) + { + synchronized (set) + { + set.remove(this); + } + m_set = null; + } + + m_task = null; // release the task so avoid retaining garbage + if (super.cancel()) + { + m_cTasks.decrementAndGet(); + return true; + } + return false; + } + + public String toString() + { + return "TimerTask{" + m_task + "}"; + } + + /** + * The task to run, or null if cancelled. + */ + protected Runnable m_task; + + /** + * The number of pending tasks, this is to be decremented once the task is run (even if the task is null). + */ + protected AtomicLong m_cTasks; + + /** + * An optional set of pending tasks, this task is to be removed from the set once run or cancelled. + */ + protected Set m_set; + } + + // ----- data members ---------------------------------------------------- + + /** + * Shared timer thread. + */ + private static Timer s_timerNonBlocking; + + /** + * The number of currently scheduled timer tasks. + */ + private static final AtomicLong s_cNonBlockingTimerTasks = new AtomicLong(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/logging/SimpleThreadAwareFormatter.java b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/logging/SimpleThreadAwareFormatter.java new file mode 100644 index 0000000000000..d1d2e94fe931e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/logging/SimpleThreadAwareFormatter.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.common.util.logging; + +import java.util.logging.LogRecord; +import java.util.logging.SimpleFormatter; + + +/** + * A Java logging formatter which includes the name of the thread which issued the log message. + *

+ * More precisely it includes the name of the thread which formats the message, and thus + * in the case of an asynchronous Handler this may not be the thread which logged the message. But + * For simple Handlers such as the {@link java.util.logging.ConsoleHandler} or {@link java.util.logging.FileHandler} + * this formatter will be able to include the name of the thread which has issued the log message. + *

+ * + * @author mf 2013.09.23 + */ +public class SimpleThreadAwareFormatter + extends SimpleFormatter + { + @Override + public String format(LogRecord record) + { + String sTxt = super.format(record); + int ofLine = sTxt.indexOf(System.lineSeparator()); + + return sTxt.substring(0, ofLine) + " " + Thread.currentThread().getName() + sTxt.substring(ofLine); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/package.html new file mode 100644 index 0000000000000..f8fd804c127ca --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/common/util/package.html @@ -0,0 +1,5 @@ + +The util package provides a number of common utilities classes. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/AsyncPersistenceException.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/AsyncPersistenceException.java new file mode 100644 index 0000000000000..7d9959e4325ec --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/AsyncPersistenceException.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +/** + * This exception indicates that an asynchronous persistence operation failed. + *

+ * Note that this exception isn't explicitly thrown; rather, it will be + * passed to the collector specified during the start of an asynchronous + * transaction. Additionally, the receipt for the collector can be obtained + * via the {@link #getReceipt()} method. + * + * @author jh 2013.07.17 + */ +public class AsyncPersistenceException + extends PersistenceException + { + /** + * Create a new AsyncPersistenceException. + */ + public AsyncPersistenceException() + { + super(); + } + + /** + * Create a new AsyncPersistenceException with the specified detail + * message. + * + * @param sMessage a detail message + */ + public AsyncPersistenceException(String sMessage) + { + super(sMessage); + } + + /** + * Create a new AsyncPersistenceException with the specified detail + * message and cause. + * + * @param sMessage a detail message + * @param eCause the cause + */ + public AsyncPersistenceException(String sMessage, Throwable eCause) + { + super(sMessage, eCause); + } + + /** + * Create a new AsyncPersistenceException with the specified cause. + * + * @param eCause the cause + */ + public AsyncPersistenceException(Throwable eCause) + { + super(eCause); + } + + // ----- accessors ------------------------------------------------------ + + /** + * Return the receipt associated with this exception. + * + * @return the receipt + */ + public Object getReceipt() + { + Object oReceipt = m_oReceipt; + return oReceipt == NO_RECEIPT ? null : oReceipt; + } + + /** + * Associate the specified receipt with this exception. + *

+ * This method should only be called once. Once a receipt has been + * associated with this exception, this method will have no effect. + * + * @param oReceipt the receipt + * + * @return this exception + */ + public synchronized AsyncPersistenceException initReceipt(Object oReceipt) + { + if (m_oReceipt == NO_RECEIPT) + { + m_oReceipt = oReceipt; + } + return this; + } + + // ----- constants ------------------------------------------------------ + + /** + * Constant used to indicate that a receipt hasn't been associated with + * an AsyncPersistenceException. + */ + private static final Object NO_RECEIPT = new Object(); + + // ----- data members---------------------------------------------------- + + /** + * The receipt associated with this exception. + */ + private volatile Object m_oReceipt = NO_RECEIPT; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/ConcurrentAccessException.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/ConcurrentAccessException.java new file mode 100644 index 0000000000000..65ad682439aab --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/ConcurrentAccessException.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +/** + * This exception is thrown when a persistence operation fails due to another + * process having exclusive access to a persistent resource. + * + * @author jh 2012.08.29 + */ +public class ConcurrentAccessException + extends PersistenceException + { + /** + * Create a new ConcurrentAccessException. + */ + public ConcurrentAccessException() + { + super(); + } + + /** + * Create a new ConcurrentAccessException with the specified detail + * message. + * + * @param sMessage a detail message + */ + public ConcurrentAccessException(String sMessage) + { + super(sMessage); + } + + /** + * Create a new ConcurrentAccessException with the specified detail + * message and cause. + * + * @param sMessage a detail message + * @param eCause the cause + */ + public ConcurrentAccessException(String sMessage, Throwable eCause) + { + super(sMessage, eCause); + } + + /** + * Create a new ConcurrentAccessException with the specified cause. + * + * @param eCause the cause + */ + public ConcurrentAccessException(Throwable eCause) + { + super(eCause); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/FatalAccessException.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/FatalAccessException.java new file mode 100644 index 0000000000000..8d66856609e72 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/FatalAccessException.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +/** + * This exception is thrown when a persistence operation fails due to a + * non-recoverable issue with a persistent resource. + * + * @author jh 2012.08.29 + */ +public class FatalAccessException + extends PersistenceException + { + /** + * Create a new FatalAccessException. + */ + public FatalAccessException() + { + super(); + } + + /** + * Create a new FatalAccessException with the specified detail message. + * + * @param sMessage a detail message + */ + public FatalAccessException(String sMessage) + { + super(sMessage); + } + + /** + * Create a new FatalAccessException with the specified detail message + * and cause. + * + * @param sMessage a detail message + * @param eCause the cause + */ + public FatalAccessException(String sMessage, Throwable eCause) + { + super(sMessage, eCause); + } + + /** + * Create a new FatalAccessException with the specified cause. + * + * @param eCause the cause + */ + public FatalAccessException(Throwable eCause) + { + super(eCause); + } + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/OfflinePersistenceInfo.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/OfflinePersistenceInfo.java new file mode 100644 index 0000000000000..2a5dae11fc32c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/OfflinePersistenceInfo.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +/** + * A data structure encapsulating information derived from an implementation of + * {@link PersistenceTools}. + * + * @since 12.2.1 + * @author hr/tam 2014.10.11 + */ +public class OfflinePersistenceInfo + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an instance to store offline information about a persistent + * snapshot or archived snapshot. + * + * @param cPartitions the partition count as defined in metadata + * @param sStorageFormat the storage format + * @param fArchive true if the snapshot is archived + * @param asGUIDs the set of GUIDs + * @param nStorageVersion the storage version + * @param nImplVersion the implementation version + * @param sServiceVersion the service version + * + * @deprecated use {@link #OfflinePersistenceInfo(int, String ,boolean, String[], int, int, int)} instead + */ + public OfflinePersistenceInfo(int cPartitions, String sStorageFormat, boolean fArchive, String[] asGUIDs, + int nStorageVersion, int nImplVersion, String sServiceVersion) + { + this(cPartitions, sStorageFormat, fArchive, asGUIDs, nStorageVersion, + nImplVersion, Integer.parseInt(sServiceVersion)); + } + + /** + * Construct an instance to store offline information about a persistent + * snapshot or archived snapshot. + * + * @param cPartitions the partition count as defined in metadata + * @param sStorageFormat the storage format + * @param fArchive true if the snapshot is archived + * @param asGUIDs the set of GUIDs + * @param nStorageVersion the storage version + * @param nImplVersion the implementation version + * @param nPersistenceVersion the persistence version + */ + public OfflinePersistenceInfo(int cPartitions, String sStorageFormat, boolean fArchive, String[] asGUIDs, + int nStorageVersion, int nImplVersion, int nPersistenceVersion) + { + f_cPartitions = cPartitions; + f_sStorageFormat = sStorageFormat; + f_fArchive = fArchive; + f_asGUIDs = asGUIDs == null ? new String[] {} : asGUIDs; + f_nStorageVersion = nStorageVersion; + f_nImplVersion = nImplVersion; + f_nPersistenceVersion = nPersistenceVersion; + } + + // ----- accessors ------------------------------------------------------ + + /** + * Return the partition count as defined in the store metadata. + * + * @return the partition count as defined in the store metadata + */ + public int getPartitionCount() + { + return f_cPartitions; + } + + /** + * Return the storage format of the snapshot. + * + * @return the storage format + */ + public String getStorageFormat() + { + return f_sStorageFormat; + } + + /** + * Return the GUIDs that the related snapshot or archive is aware of. + * + * @return the GUIDs that the related snapshot or archive is aware of + */ + public String[] getGUIDs() + { + return f_asGUIDs; + } + + /** + * Return true if all parts of the snapshot are present. + * + * @return true if all parts of the snapshot are present + */ + public boolean isComplete() + { + return f_asGUIDs.length == f_cPartitions; + } + + /** + * Return true if the snapshot is an archived snapshot. + * + * @return true if the snapshot is an archived snapshot + */ + public boolean isArchived() + { + return f_fArchive; + } + + /** + * Return the storage version. + * + * @return the storage version + */ + public int getStorageVersion() + { + return f_nStorageVersion; + } + + /** + * Return the implementation version. + * + * @return the implementation version + */ + public int getImplVersion() + { + return f_nImplVersion; + } + + /** + * Return the service version. + * + * @return the service version + * + * @deprecated use {@link #getPersistenceVersion()} instead + */ + public String getServiceVersion() + { + return String.valueOf(f_nPersistenceVersion); + } + + /** + * Return the persistence version. + * + * @return the persistence version + */ + public int getPersistenceVersion() + { + return f_nPersistenceVersion; + } + + // ----- data members --------------------------------------------------- + + /** + * The partition count as defined in the store metadata. + */ + private final int f_cPartitions; + + /** + * The storage type. + */ + private final String f_sStorageFormat; + + /** + * The GUIDs in the snapshot. + */ + private String[] f_asGUIDs; + + /** + * Indicates if the snapshot is archived or not. + */ + private final boolean f_fArchive; + + /** + * Implementation version. + */ + private final int f_nImplVersion; + + /** + * Storage version. + */ + private final int f_nStorageVersion; + + /** + * Persistence version. + */ + private final int f_nPersistenceVersion; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceEnvironment.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceEnvironment.java new file mode 100644 index 0000000000000..774ada4fb3d0a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceEnvironment.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +/** + * A PersistenceEnvironment is responsible for managing a singleton + * PersistenceManager and provides facilities for creating, opening, and + * deleting persistent copies or "snapshots" of a PersistenceManager. + * + * @param the type of a raw, environment specific object representation + * + * @author jh/rl/hr 2013.05.09 + */ +public interface PersistenceEnvironment + { + /** + * Open and return the singleton active PersistenceManager. + * + * @return the singleton active PersistenceManager or null if an active + * PersistenceManager has not been configured + */ + public PersistenceManager openActive(); + + /** + * Open a PersistenceManager used to access the snapshot with the + * specified identifier. + * + * @param sSnapshot  the snapshot identifier + * + * @throws IllegalArgumentException if a snapshot with the given + * identifier does not exist + * + * @return a PersistenceManager representing the snapshot + */ + public PersistenceManager openSnapshot(String sSnapshot); + + /** + * Create a PersistenceManager used to manage the snapshot with the + * specified identifier. + * + * @param sSnapshot  the snapshot identifier + * + * @return a PersistenceManager representing the snapshot + */ + public default PersistenceManager createSnapshot(String sSnapshot) + { + return createSnapshot(sSnapshot, null); + } + + /** + * Create a PersistenceManager used to manage the snapshot with the + * specified identifier. + * + * @param sSnapshot  the snapshot identifier + * @param manager the optional PersistenceManager to create a snapshot + * of; if null, an empty snapshot will be created + * + * @return a PersistenceManager representing the snapshot + */ + public PersistenceManager createSnapshot(String sSnapshot, PersistenceManager manager); + + /** + * Remove the persistent artifacts associated with the snapshot with the + * specified identifier. + * + * @param sSnapshot  the snapshot identifier + * + * @return true if the snapshot was successfully deleted, false otherwise + */ + public boolean removeSnapshot(String sSnapshot); + + /** + * Return the identifiers of the snapshots known to this environment. + * + * @return a list of the known snapshot identifiers + */ + public String[] listSnapshots(); + + /** + * Release all resources held by this environment. Note that the behavior + * of all other methods on this environment is undefined after this + * method is called. + */ + public void release(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceException.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceException.java new file mode 100644 index 0000000000000..3498bca54b1db --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceException.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +/** + * PersistenceException is the superclass of all exception + * classes in the com.oracle.coherence.persistence package. + * + * @author jh 2012.08.29 + */ +public class PersistenceException + extends RuntimeException + { + /** + * Create a new PersistenceException. + */ + public PersistenceException() + { + super(); + } + + /** + * Create a new PersistenceException with the specified detail message. + * + * @param sMessage a detail message + */ + public PersistenceException(String sMessage) + { + super(sMessage); + } + + /** + * Create a new PersistenceException with the specified detail message + * and cause. + * + * @param sMessage a detail message + * @param eCause the cause + */ + public PersistenceException(String sMessage, Throwable eCause) + { + super(sMessage, eCause); + } + + /** + * Create a new PersistenceException with the specified cause. + * + * @param eCause the cause + */ + public PersistenceException(Throwable eCause) + { + super(eCause); + } + + /** + * Create a new PersistenceException with the specified detail message + * and cause. + * + * @param sMessage a detail message + * @param eCause the cause + */ + public PersistenceException(String sMessage, PersistenceException eCause) + { + super(sMessage, eCause); + + initPersistenceEnvironment(eCause.getPersistenceEnvironment()). + initPersistenceManager(eCause.getPersistenceManager()). + initPersistentStore(eCause.getPersistentStore()); + } + + /** + * Create a new PersistenceException with the specified cause. + * + * @param eCause the cause + */ + public PersistenceException(PersistenceException eCause) + { + super(eCause); + + initPersistenceEnvironment(eCause.getPersistenceEnvironment()). + initPersistenceManager(eCause.getPersistenceManager()). + initPersistentStore(eCause.getPersistentStore()); + } + + // ----- accessors ------------------------------------------------------ + + /** + * Return the PersistenceEnvironment associated with this exception. + * + * @return the environment + */ + public PersistenceEnvironment getPersistenceEnvironment() + { + PersistenceEnvironment[] aEnv = m_aEnv; + return aEnv == null ? null : aEnv[0]; + } + + /** + * Associate the specified PersistenceEnvironment with this exception. + *

+ * This method should only be called once. Once a PersistenceEnvironment + * has been associated with this exception, this method will have no + * effect. + * + * @param env the environment + * + * @return this exception + */ + public synchronized PersistenceException initPersistenceEnvironment(PersistenceEnvironment env) + { + if (m_aEnv == null) + { + m_aEnv = new PersistenceEnvironment[] {env}; + } + return this; + } + + /** + * Return the PersistenceManager associated with this exception. + * + * @return the manager + */ + public PersistenceManager getPersistenceManager() + { + PersistenceManager[] aManager = m_aManager; + return aManager == null ? null : aManager[0]; + } + + /** + * Associate the specified PersistenceManager with this exception. + *

+ * This method should only be called once. Once a PersistenceManager has + * been associated with this exception, this method will have no effect. + * + * @param manager the manager + * + * @return this exception + */ + public synchronized PersistenceException initPersistenceManager(PersistenceManager manager) + { + if (m_aManager == null) + { + m_aManager = new PersistenceManager[] {manager}; + } + return this; + } + + /** + * Return the PersistentStore associated with this exception. + * + * @return the store + */ + public PersistentStore getPersistentStore() + { + PersistentStore[] aStore = m_aStore; + return aStore == null ? null : m_aStore[0]; + } + + /** + * Associate the specified PersistentStore with this exception. + *

+ * This method should only be called once. Once a PersistentStore has + * been associated with this exception, this method will have no effect. + * + * @param store the store + * + * @return this exception + */ + public synchronized PersistenceException initPersistentStore(PersistentStore store) + { + if (m_aStore == null) + { + m_aStore = new PersistentStore[] {store}; + } + return this; + } + + // ----- data members --------------------------------------------------- + + /** + * The PersistenceEnvironment associated with this exception. + */ + private volatile PersistenceEnvironment[] m_aEnv; + + /** + * The PersistenceManager associated with this exception. + */ + private volatile PersistenceManager[] m_aManager; + + /** + * The PersistentStore associated with this exception. + */ + private volatile PersistentStore[] m_aStore; + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceManager.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceManager.java new file mode 100644 index 0000000000000..92ca31fbab074 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceManager.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +import com.oracle.coherence.common.base.Collector; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A PersistenceManager is responsible for managing the collection, location, + * and lifecycle of persistent key-value stores. + * + * @param the type of a raw, environment specific object representation + * + * @author rhl/gg/jh/mf/hr 2012.06.12 + */ +public interface PersistenceManager + { + /** + * Return the name of this manager. + * + * @return the name of this manager + */ + public String getName(); + + /** + * Create a {@link PersistentStore} associated with the specified + * identifier. + *

+ * Creation of a store suggests its registration but has no usage + * until it transitions into a state of open. The implementation may + * choose to forgo any resource allocation until the caller + * {@link #open opens} the same identifier. + * + * @param sId a unique identifier of the store + * + * @return a persistent store + */ + public PersistentStore createStore(String sId); + + /** + * Open or create a {@link PersistentStore} associated with the specified + * identifier and based on the provided {@link PersistentStore store}. + *

+ * Upon a new store being created the provided store should be used as the + * basis for the new store such that the extents and associated data is + * available in the returned store. This provides an opportunity for an + * implementation to optimize initializing the new store based upon knowledge + * of the storage mechanics. + * + * @param sId a unique identifier for the store + * @param storeFrom the PersistenceStore the new store should be based upon + * + * @return a PersistentStore associated with the specified identifier + * + * @throws ConcurrentAccessException if the specified store is + * unavailable for exclusive access + * + * @throws FatalAccessException if the specified store could not be + * opened due to a non-recoverable issue with the store + * + * @throws PersistenceException if the specified store could not be + * opened due to a potentially recoverable issue with the store + * + * @throws IllegalArgumentException if the specified identifier is invalid + */ + public PersistentStore open(String sId, PersistentStore storeFrom); + + /** + * Open or create a {@link PersistentStore} associated with the specified + * identifier and based on the provided {@link PersistentStore store}. + *

+ * Upon a new store being created the provided store should be used as the + * basis for the new store such that the extents and associated data is + * available in the returned store. This provides an opportunity for an + * implementation to optimize initializing the new store based upon knowledge + * of the storage mechanics. + *

+ * Providing a {@link Collector} allows the open operation to be performed + * asynchronously. This may be desirable when the calling thread can not + * be blocked on IO operations that are required when creating a new store + * based on an old store ({@code storeFrom}). Open is only non-blocking when + * both an old store and a Collector are provided. Upon completion + * of an asynchronous open request the provided Collector is called with + * either a String (GUID) or an AsyncPersistenceException, thus notifying + * the collector of success of failure respectively. + *

+ * Note: the behavior of a returned store that has not been opened is undefined. + * + * @param sId a unique identifier for the store + * @param storeFrom the PersistenceStore the new store should be based upon + * @param collector the Collector to notify once the store has been opened + * or failed to open; the collector will either receive + * a String (GUID) or an AsyncPersistenceException + * + * @return a PersistentStore associated with the specified identifier + * + * @throws ConcurrentAccessException if the specified store is + * unavailable for exclusive access + * + * @throws FatalAccessException if the specified store could not be + * opened due to a non-recoverable issue with the store + * + * @throws PersistenceException if the specified store could not be + * opened due to a potentially recoverable issue with the store + * + * @throws IllegalArgumentException if the specified identifier is invalid + */ + public PersistentStore open(String sId, PersistentStore storeFrom, Collector collector); + + /** + * Close the associated PersistentStore and release exclusive access to + * the associated resources. + * + * @param sId a unique identifier of the store to close + * + * @throws IllegalArgumentException if the specified identifier is invalid + */ + public void close(String sId); + + /** + * Remove the PersistentStore associated with the specified identifier. + * + * @param sId a unique identifier of the store to remove + * @param fSafe if true, remove the store by moving it to a restorable + * location (if possible) rather than deleting it + * + * @return true if the store was successfully removed, false otherwise + * + * @throws IllegalArgumentException if the specified identifier is invalid + */ + public boolean delete(String sId, boolean fSafe); + + /** + * Return the identifiers of the PersistentStores known to this manager. + * + * @return a list of the known store identifiers + */ + public String[] list(); + + /** + * Return the identifiers of PersistentStores that are currently open. + * + * @return a list of the open store identifiers + */ + public String[] listOpen(); + + /** + * Read the PersistenceStore associated with the specified identifier + * from the given input stream, making it available to this manager. + * + * @param sId a unique identifier of the store to read + * @param in the stream to read from + * + * @throws IOException if an error occurred while reading from the stream + * + * @throws PersistenceException if an error occurred while writing to the + * specified store + * + * @throws IllegalArgumentException if the specified identifier is invalid + */ + public void read(String sId, InputStream in) throws IOException; + + /** + * Write the PersistentStore associated with the specified identifier to + * the given output stream. + * + * @param sId a unique identifier of the store to write + * @param out the stream to write to + * + * @throws IOException if an error occurred while writing to the stream + * + * @throws PersistenceException if an error occurred while reading from + * the specified store + * + * @throws IllegalArgumentException if the specified identifier is invalid + */ + public void write(String sId, OutputStream out) throws IOException; + + /** + * Release all resources held by this manager. Note that the behavior + * of all other methods on this manager is undefined after this method + * is called. + */ + public void release(); + + /** + * Return an instance of {@link PersistenceTools} allowing offline operations + * to be performed against the associated PersistenceManager and appropriate + * {@link PersistentStore}. + * + * @return a PersistenceTools implementation + */ + public PersistenceTools getPersistenceTools(); + } diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceStatistics.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceStatistics.java new file mode 100644 index 0000000000000..11db2146fbb67 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceStatistics.java @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * PersistenceStatistics provides statistics in relation to the entries and + * metadata persisted to allow recovery of Coherence caches. These statistics + * are accumulated from either actively persisted data, snapshots or archived + * snapshots. Fundamentally these statistics provide a means to validate the + * integrity of the persisted data but also provide an insight into the data + * and metadata stored. + *

+ * The usage of this data structure is intended to pivot around cache names, + * thus to output the byte size of all caches the following would typically be + * executed: + *


+ *     PersistenceStatistics stats = ...;
+ *     for (String sCacheName : stats)
+ *         {
+ *         long cb = stats.getCacheBytes(sCacheName);
+ *         System.out.printf("%s has %d bytes\n", sCacheName, cb);
+ *         }
+ * 
+ * + * @author tam/hr 2014.11.18 + * @since 12.2.1 + */ +public class PersistenceStatistics + implements Iterable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new instance to store persistence statistics. + */ + public PersistenceStatistics() + { + } + + // ----- Iterable interface --------------------------------------------- + + @Override + public Iterator iterator() + { + return f_mapStats.keySet().iterator(); + } + + // ----- accessors ------------------------------------------------------ + + /** + * Increment the size (count of entries) for a cache. + * + * @param sCacheName the cache to increment + */ + public void incrementSize(String sCacheName) + { + ensureCacheStats(sCacheName).incrementSize(); + } + + /** + * Add number of raw bytes stored for a cache. + * + * @param sCacheName the cache name to add bytes to + * @param cBytes the number of bytes to add + */ + public void addToBytes(String sCacheName, long cBytes) + { + ensureCacheStats(sCacheName).addToBytes(cBytes); + } + + /** + * Increment the index count for a cache. + * + * @param sCacheName the cache to increment for + */ + public void incrementIndexes(String sCacheName) + { + ensureCacheStats(sCacheName).incrementIndexes(); + } + + /** + * Increment the trigger count for a cache. + * + * @param sCacheName the cache to increment for + */ + public void incrementTriggers(String sCacheName) + { + ensureCacheStats(sCacheName).incrementTriggers(); + } + + /** + * Increment the lock count for a cache. + * + * @param sCacheName the cache to increment for + */ + public void incrementLocks(String sCacheName) + { + ensureCacheStats(sCacheName).incrementTriggers(); + } + + /** + * Increment the listener count for a cache. + * + * @param sCacheName the cache to increment + */ + public void incrementListeners(String sCacheName) + { + ensureCacheStats(sCacheName).incrementListeners(); + } + + /** + * Return the size (count) of number of entries. + * + * @param sCacheName the cache to return the size + * + * @return the size (count) of number of entries + */ + public long getCacheSize(String sCacheName) + { + CachePersistenceStatistics stats = f_mapStats.get(sCacheName); + + return stats == null ? -1 : stats.getSize(); + } + + /** + * Return the number of raw bytes for a given cache. + * + * @param sCacheName the cache name to return bytes + * + * @return the number of raw bytes for a given cache + */ + public long getCacheBytes(String sCacheName) + { + CachePersistenceStatistics stats = f_mapStats.get(sCacheName); + + return stats == null ? -1 : stats.getBytes(); + } + + /** + * Return the number of indexes for a given cache. + * + * @param sCacheName the cache name to return indexes + * + * @return the number of indexes for a given cache + */ + public int getIndexCount(String sCacheName) + { + CachePersistenceStatistics stats = f_mapStats.get(sCacheName); + + return stats == null ? -1 : stats.getIndexCount(); + } + + /** + * Return the number of triggers for a given cache. + * + * @param sCacheName the cache name to return triggers + * + * @return the number of triggers for a given cache + */ + public int getTriggerCount(String sCacheName) + { + CachePersistenceStatistics stats = f_mapStats.get(sCacheName); + + return stats == null ? -1 : stats.getTriggerCount(); + } + + /** + * Return the number of locks for a given cache. + * + * @param sCacheName the cache name to return locks + * + * @return the number of locks for a given cache + */ + public int getLockCount(String sCacheName) + { + CachePersistenceStatistics stats = f_mapStats.get(sCacheName); + + return stats == null ? -1 : stats.getLockCount(); + } + + /** + * Return the number of listeners for a given cache. + * + * @param sCacheName the cache name to return listeners + * + * @return the number of listeners for a given cache + */ + public int getListenerCount(String sCacheName) + { + CachePersistenceStatistics stats = f_mapStats.get(sCacheName); + + return stats == null ? -1 : stats.getListenerCount(); + } + + // ----- helpers -------------------------------------------------------- + + /** + * Ensure a CachePersistenceStatistics object exists for a cache. + * + * @param sCacheName the cache name relating to the CachePersistenceStatistics + * + * @return a CachePersistenceStatistics object for the given cache + */ + protected CachePersistenceStatistics ensureCacheStats(String sCacheName) + { + CachePersistenceStatistics stats = f_mapStats.get(sCacheName); + + if (stats == null) + { + f_mapStats.put(sCacheName, stats = new CachePersistenceStatistics(sCacheName)); + } + return stats; + } + + // ----- inner class: CachePersistenceStatistics ----------------------- + + /** + * A holder for statics pertaining to an individual cache. + */ + protected class CachePersistenceStatistics + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a new instance for a given cache. + * + * @param sCacheName the cache name to create stats + */ + protected CachePersistenceStatistics(String sCacheName) + { + f_sCacheName = sCacheName; + } + + // ----- CachePersistenceStatistics methods ------------------------- + + /** + * Increment the size (count) of entries. + */ + public void incrementSize() + { + m_cSize++; + } + + /** + * Add the number of bytes to the count of bytes. + * + * @param cBytes the number of bytes to add + */ + public void addToBytes(long cBytes) + { + m_cBytes += cBytes; + } + + /** + * Increment the number of triggers. + */ + public void incrementTriggers() + { + m_cTriggers++; + } + + /** + * Increment the number of indexes. + */ + public void incrementIndexes() + { + m_cIndexes++; + } + + /** + * Increment the number of locks. + */ + public void incrementLocks() + { + m_cLocks++; + } + + /** + * Increment the number of listeners. + */ + public void incrementListeners() + { + m_cListeners++; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the cache name. + * + * @return the cache name + */ + public String getCacheName() + { + return f_sCacheName; + } + + /** + * Return the size (count) of number of entries. + * + * @return he size (count) of number of entries + */ + public long getSize() + { + return m_cSize; + } + + /** + * Return the number of bytes stored for the cache. + * + * @return the number of bytes stored for the cache + */ + public long getBytes() + { + return m_cBytes; + } + + /** + * Return the number of indexes. + * + * @return the number of indexes + */ + public int getIndexCount() + { + return m_cIndexes; + } + + /** + * Return the number of triggers. + * + * @return the number of triggers + */ + public int getTriggerCount() + { + return m_cTriggers; + } + + /** + * Return the number of locks. + * + * @return the number of locks + */ + public int getLockCount() + { + return m_cLocks; + } + + /** + * Return the number of listeners. + * + * @return the number of listeners + */ + public int getListenerCount() + { + return m_cListeners; + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder("CachePersistenceStatistics(cacheName="); + sb.append(f_sCacheName) + .append(", size=") + .append(m_cSize) + .append(", bytes=") + .append(m_cBytes) + .append(", indexes=") + .append(m_cIndexes) + .append(", triggers=") + .append(m_cTriggers) + .append(", locks=") + .append(m_cLocks) + .append(", listeners=") + .append(m_cListeners) + .append(")"); + + return sb.toString(); + } + + // ----- data members ---------------------------------------------- + + /** + * The cache name to store statistics about. + */ + private final String f_sCacheName; + + /** + * The size (count) of entries in the cache. + */ + protected long m_cSize = 0L; + + /** + * The number of bytes stores for both key and values. + */ + protected long m_cBytes = 0L; + + /** + * The number of triggers stored. + */ + protected int m_cTriggers = 0; + + /** + * The number of indexes stored. + */ + protected int m_cIndexes = 0; + + /** + * The number of locks stored. + */ + protected int m_cLocks = 0; + + /** + * The number of listeners stored. + */ + protected int m_cListeners = 0; + } + + // ----- data members -------------------------------------------------- + + /** + * The map of CachePersistenceStatistics for all caches. + */ + private final Map f_mapStats = new LinkedHashMap<>();; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceTools.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceTools.java new file mode 100644 index 0000000000000..d5654d339a002 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistenceTools.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +/** + * PersistenceTools provides an ability to submit operations under the context + * of a {@link PersistenceManager}, hence a reference to PersistenceTools is + * retrieved from {@link PersistenceManager#getPersistenceTools()}. The PersistenceManager + * typically will refer to offline stores such as a snapshot, archived snapshot, + * or an unlinked active store (disconnected from running cache server). + *

+ * The intent of this interface is to describe some offline operations that can + * be performed against {@link PersistentStore}s to validate their integrity + * and accumulate offline information. One of the primary benefits is to verify + * recovery will be successful. There are other operations that can be beneficial, + * including retrieving statistics, correction and compaction. + * + * @since 12.2.1 + * @author hr/tam 2014.10.11 + */ +public interface PersistenceTools + { + /** + * Return summary information regarding the available {@link PersistentStore}s + * under the context of the {@link PersistenceManager} (snapshot or archived + * snapshot). + * + * @return summary information about the specific snapshot + */ + public OfflinePersistenceInfo getPersistenceInfo(); + + /** + * Validate the available {@link PersistentStore}s under the context of the + * associated {@link PersistenceManager} (snapshot or archived snapshot). + * + * @throws RuntimeException if the snapshot is invalid + */ + public void validate(); + + /** + * Return a {@link PersistenceStatistics} object representing the available + * {@link PersistentStore}s under the context of the associated {@link + * PersistenceManager} (snapshot or archived snapshot). + * + * @return a PersistenceStatistics object representing a snapshot + */ + public PersistenceStatistics getStatistics(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistentStore.java b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistentStore.java new file mode 100644 index 0000000000000..0700358dca3fa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/PersistentStore.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.oracle.coherence.persistence; + +import com.oracle.coherence.common.base.Collector; + +/** + * PersistentStore represents a persistence facility to store and recover + * key/value pairs. Each key-value pair is namespaced by a numeric "extent" + * identifier and may be stored or removed in atomic units. + *

+ * A PersistentStore implementation should be optimized for random writes and + * sequential reads (e.g. iteration), as it is generally assumed to only be + * read from during recovery operations. Additionally, all operations with + * the exception of key and entry iteration may be called concurrently and + * therefore must be thread-safe. + * + * @param the type of a raw, environment specific object representation + * + * @author rhl/gg/jh/mf/hr 2012.06.12 + */ +public interface PersistentStore + { + /** + * Return the identifier of this store. + * + * @return the identifier that was used to open this store + */ + public String getId(); + + // ---- extent lifecycle ------------------------------------------------ + + /** + * Ensure that an extent with the given identifier exists in the + * persistent store, returning true iff the extent was created. + * + * @param lExtentId the identifier of the extent to ensure + * + * @return true iff the specified extent did not previously exist + */ + public boolean ensureExtent(long lExtentId); + + /** + * Delete the specified extent from the persistent store, ensuring that + * any key-value mappings associated with the extent are no longer valid. + *

+ * Removal of the key-value mappings associated with the extent from the + * underlying storage is the responsibility of the implementation, and + * may (for example) be performed immediately, asynchronously, or + * deferred until space is required. + * + * @param lExtentId the identifier of the extent to delete + */ + public void deleteExtent(long lExtentId); + + /** + * Truncate the specified extent from the persistent store, ensuring that + * any key-value mappings associated with the extent are removed. + *

+ * Removal of the key-value mappings associated with the extent from the + * underlying storage is the responsibility of the implementation, and + * may (for example) be performed immediately, asynchronously, or + * deferred until space is required. + * + * @param lExtentId the identifier of the extent to truncate + */ + public void truncateExtent(long lExtentId); + + /** + * Move the specified extent from the old extent id to the new extent id. + *

+ * Upon control being returned the implementation guarantees that any data + * data that used to reside against the old extent id is accessible from + * new extent id using the {@link #load(long, Object) load} API. In addition, + * calls to {@link #store(long, Object, Object, Object) store} are permitted + * immediately after control is returned. + * + * @param lOldExtentId the old extent identifier + * @param lNewExtentId the new extent identifier + */ + public void moveExtent(long lOldExtentId, long lNewExtentId); + + /** + * Return a list of the extent identifiers in the underlying store. + * + * @return a list of the extent identifiers in the underlying store + */ + public long[] extents(); + + // ----- store operations ----------------------------------------------- + + /** + * Return the value associated with the specified key, or null if the key + * does not have an associated value in the underlying store. + * + * @param lExtentId the extent identifier for the key + * @param key key whose associated value is to be returned + * + * @return the value associated with the specified key, or null + * if no value is available for that key + * + * @throws IllegalArgumentException if the specified extent does not exist + * or the key is invalid + */ + public R load(long lExtentId, R key); + + /** + * Store the specified value under the specific key in the underlying + * store. This method is intended to support both key-value pair creation + * and value update for a specific key. + * + * @param lExtentId the extent identifier for the key-value pair + * @param key key to store the value under + * @param value value to be stored + * @param oToken optional token that represents a set of mutating + * operations to be committed as an atomic unit; if + * null, the given key-value pair will be committed to + * the store automatically by this method + * + * @throws IllegalArgumentException if the specified extent does not exist, + * or if the key, value or token is invalid + */ + public void store(long lExtentId, R key, R value, Object oToken); + + /** + * Remove the specified key from the underlying store if present. + * + * @param lExtentId the extent identifier for the key + * @param key key whose mapping is to be removed + * @param oToken optional token that represents a set of mutating + * operations to be committed as an atomic unit; if + * null, the removal of the given key will be committed + * to the store automatically by this method + * + * @throws IllegalArgumentException if the specified extent does not exist, + * of if the key or the token is invalid + */ + public void erase(long lExtentId, R key, Object oToken); + + /** + * Iterate the key-value pairs in the persistent store, applying the + * specified visitor to each key-value pair. + * + * @param visitor the visitor to apply + */ + public void iterate(Visitor visitor); + + // ----- transaction demarcation ---------------------------------------- + + /** + * Begin a new sequence of mutating operations that should be committed + * to the store as an atomic unit. The returned token should be passed to + * all mutating operations that should be part of the atomic unit. Once + * the sequence of operations have been performed, they must either be + * {@link #commit(Object) committed} to the store or the atomic unit must + * be {@link #abort(Object) aborted}. + * + * @return a token that represents the atomic unit + */ + public Object begin(); + + /** + * Begin a new sequence of mutating operations that should be committed + * to the store asynchronously as an atomic unit. The returned token + * should be passed to all mutating operations that should be part of the + * atomic unit. Once the sequence of operations have been performed, they + * must either be {@link #commit(Object) committed} to the store or the + * atomic unit must be {@link #abort(Object) aborted}. + *

+ * If a collector is passed to this method, the specified receipt will be + * added to it when the unit is committed. If the operation is {@link #abort + * aborted} or an error occurs during the commit, an {@link + * AsyncPersistenceException} that wraps the cause and + * specified receipt will be added. Finally, the collector will be flushed. + * + * @param collector an optional collector + * @param oReceipt a receipt to be added to the collector (if any) when + * the unit is committed + * + * @return a token representing the atomic unit that will be committed + * asynchronously + */ + public Object begin(Collector collector, Object oReceipt); + + /** + * Commit a sequence of mutating operations represented by the given + * token as an atomic unit. + * + * @param oToken a token that represents the atomic unit to commit + * + * @throws IllegalArgumentException if the token is invalid + */ + public void commit(Object oToken); + + /** + * Abort an atomic sequence of mutating operations. + * + * @param oToken a token that represents the atomic unit to abort + * + * @throws IllegalArgumentException if the token is invalid + */ + public void abort(Object oToken); + + // ----- inner interface: Visitor --------------------------------------- + + /** + * The Visitor interface allows the "iteration" of the contents of a + * persistent store in the style of the + * Visitor Pattern. + * + * @param the type of a raw, environment specific object representation + */ + public interface Visitor + { + /** + * Apply the visitor to the specified extent-scoped key-value pair. + * + * @param lExtentId the extent identifier + * @param key the key + * @param value the value + * + * @return false to terminate the iteration + */ + public boolean visit(long lExtentId, R key, R value); + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/package.html b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/package.html new file mode 100644 index 0000000000000..ac35759ee6a07 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/oracle/coherence/persistence/package.html @@ -0,0 +1,5 @@ + +Defines the Persistence Framework interfaces and exception classes. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/application/ContainerHelper.java b/prj/coherence-core/src/main/java/com/tangosol/application/ContainerHelper.java new file mode 100644 index 0000000000000..6e99807147006 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/application/ContainerHelper.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.application; + + +import com.tangosol.net.Service; + +import com.tangosol.util.MapListener; + + +/** + * Helper methods for Container aware logic. + * + * @author gg 2005.10.24 + * @since Coherence 12.2.1 + */ +public class ContainerHelper + { + /** + * Initialize the thread context for the given Service. + * + * @param service a Service instance + */ + public static void initializeThreadContext(Service service) + { + } + + /** + * Wrap the specified MapListener in such a way that the returned listener + * would dispatch events using the caller's context rather than context + * associated with the specified Service. + * + * @param service a Service instance + * @param listener the listener to wrap + * + * @return the corresponding context aware listener + */ + public static MapListener getContextAwareListener(Service service, MapListener listener) + { + return listener; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/CacheConfig.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/CacheConfig.java new file mode 100644 index 0000000000000..a22a7198b85c9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/CacheConfig.java @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.scheme.CachingScheme; +import com.tangosol.coherence.config.scheme.NamedTopicScheme; +import com.tangosol.coherence.config.scheme.ServiceScheme; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.Base; +import com.tangosol.util.ResourceRegistry; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + + +/** + * A {@link CacheConfig} is the top-level container for Coherence Cache + * Configuration, that of which is used at runtime to establish caches and + * services. + * + * @author pfm 2011.12.2 + * @since Coherence 12.1.2 + */ +public class CacheConfig + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a {@link CacheConfig}. + */ + public CacheConfig(ParameterResolver defaultParameterResolver) + { + // by default cache configurations aren't scoped + m_sScopeName = ""; + + // by default the registries are empty + m_registrySchemeRegistry = new ServiceSchemeRegistry(); + m_listEventInterceptorBuilders = new LinkedList<>(); + + // remember the default parameter resolver for the configuration + m_parameterResolver = defaultParameterResolver; + } + + // ----- CacheConfig methods ------------------------------------------- + + /** + * Obtain the scope name of the {@link CacheConfig}. + */ + public String getScopeName() + { + return m_sScopeName; + } + + /** + * Set the scope name of this {@link CacheConfig} (which will be trimmed) + * + * @param sScopeName the scope name + */ + @Injectable("scope-name") + @Deprecated + public void setScopeName(String sScopeName) + { + // the top level "scope-name" is deprecated as of 12.1.3; + // we maintain this only for backward compatibility + m_sScopeName = sScopeName.trim(); + } + + /** + * Obtain the {@link List} of {@link NamedEventInterceptorBuilder}s for + * this {@link CacheConfig}. + * + * @return a List of NamedEventInterceptorBuilders or {@code null} if unspecified + */ + public List getEventInterceptorBuilders() + { + return m_listEventInterceptorBuilders; + } + + /** + * Set the {@link List} of {@link NamedEventInterceptorBuilder}s for this + * {@link CacheConfig}. + * + * @param listBuilders the List of NamedEventInterceptorBuilders for this + * CacheConfig + */ + @Injectable("interceptors") + public void setEventInterceptorBuilders(List listBuilders) + { + m_listEventInterceptorBuilders = listBuilders; + } + + /** + * Obtain the cache/topic {@link ResourceMappingRegistry} for the {@link CacheConfig}. + * + * @return the cache/topic {@link ResourceMappingRegistry} + * + * @since Coherence 14.1.1 + */ + public ResourceMappingRegistry getMappingRegistry() + { + return m_registrySchemeMapping; + } + + /** + * Obtain the {@link CacheMappingRegistry} for the {@link CacheConfig}. + * + * @return the {@link CacheMappingRegistry} + * + * @deprecated As of Coherence 14.1.1, replaced by {@link #getMappingRegistry()}. + */ + public CacheMappingRegistry getCacheMappingRegistry() + { + return new CacheMappingRegistry(getMappingRegistry()); + } + + /** + * Set the {@link CacheMappingRegistry}. + * + * @param registry the {@link CacheMappingRegistry} + * + * @deprecated As of Coherence 14.1.1, replaced by {@link #addCacheMappingRegistry(SchemeMappingRegistry)}. + */ + public void setCacheMappingRegistry(CacheMappingRegistry registry) + { + if (registry == null) + { + return; + } + + SchemeMappingRegistry schemeMappingRegistry = m_registrySchemeMapping; + + if (schemeMappingRegistry == null) + { + m_registrySchemeMapping = new SchemeMappingRegistry(); + } + for (CacheMapping mapping : registry) + { + if (schemeMappingRegistry.findMapping(mapping.getNamePattern(), mapping.getClass()) == null) + { + schemeMappingRegistry.register(mapping); + } + } + } + + /** + * Add cache scheme mappings to the {@link SchemeMappingRegistry}. + * + * @param registry the {@link SchemeMappingRegistry} + * + * @since Coherence 14.1.1 + */ + @Injectable("caching-scheme-mapping") + public void addCacheMappingRegistry(SchemeMappingRegistry registry) + { + addRegistrySchemeMapping(registry); + } + + /** + * Add topic scheme mappings to scheme {@link SchemeMappingRegistry} + * if no mapping already exists for the same patter. + * + * @param registry the {@link SchemeMappingRegistry} + * + * @since Coherence 14.1.1 + */ + @Injectable("topic-scheme-mapping") + public void addRegistrySchemeMapping(SchemeMappingRegistry registry) + { + if (registry == null) + { + return; + } + + SchemeMappingRegistry schemeMappingRegistry = m_registrySchemeMapping; + + if (schemeMappingRegistry == null) + { + m_registrySchemeMapping = registry; + } + else + { + for (ResourceMapping mapping : registry) + { + if (schemeMappingRegistry.findMapping(mapping.getNamePattern(), mapping.getClass()) == null) + { + schemeMappingRegistry.register(mapping); + } + } + } + } + + /** + * Obtain the {@link ServiceSchemeRegistry} for the {@link CacheConfig}. + * + * @return the {@link ServiceSchemeRegistry} + */ + public ServiceSchemeRegistry getServiceSchemeRegistry() + { + return m_registrySchemeRegistry; + } + + /** + * Set the {@link ServiceSchemeRegistry} for the {@link CacheConfig}. + * + * @param registry the {@link ServiceSchemeRegistry} + */ + @Injectable("caching-schemes") + public void setServiceSchemeRegistry(ServiceSchemeRegistry registry) + { + m_registrySchemeRegistry = registry; + } + + /** + * Find the {@link CachingScheme} for the specified cache name. + * + * @param sCacheName the cache name + * + * @return the {@link CachingScheme} or null if not found + */ + public CachingScheme findSchemeByCacheName(String sCacheName) + { + // get name of the scheme used by the cache + ResourceMappingRegistry registry = getMappingRegistry(); + CacheMapping mapping = registry.findMapping(sCacheName, CacheMapping.class); + + if (mapping == null) + { + return null; + } + else + { + String sSchemeName = mapping.getSchemeName(); + ServiceScheme serviceScheme = findSchemeBySchemeName(sSchemeName); + + return serviceScheme instanceof CachingScheme ? (CachingScheme) serviceScheme : null; + } + } + + /** + * Find the {@link CachingScheme} for the specified topic name. + * + * @param sTopicName the topic name + * + * @return the {@link NamedTopicScheme} or null if not found + * + * @since Coherence 14.1.1 + */ + public NamedTopicScheme findSchemeByTopicName(String sTopicName) + { + // get name of the scheme used by the topic + ResourceMappingRegistry registry = getMappingRegistry(); + TopicMapping mapping = registry.findMapping(sTopicName, TopicMapping.class); + + if (mapping == null) + { + return null; + } + else + { + String sSchemeName = mapping.getSchemeName(); + ServiceScheme serviceScheme = findSchemeBySchemeName(sSchemeName); + + return serviceScheme instanceof NamedTopicScheme + ? (NamedTopicScheme) serviceScheme : null; + } + } + + /** + * Find the {@link ServiceScheme} given the service name. + * + * @param sServiceName the service name to match + * + * @return the {@link ServiceScheme} or null + */ + public ServiceScheme findSchemeByServiceName(String sServiceName) + { + return getServiceSchemeRegistry().findSchemeByServiceName(sServiceName); + } + + /** + * Find the {@link ServiceScheme} given the scheme name. + * + * @param sSchemeName the scheme name to match + * + * @return the {@link ServiceScheme} or null + */ + public ServiceScheme findSchemeBySchemeName(String sSchemeName) + { + return getServiceSchemeRegistry().findSchemeBySchemeName(sSchemeName); + } + + /** + * Obtain the {@link ParameterResolver} to use for the {@link CacheConfig} + * when no other is available or in context. + * + * @return the default {@link ParameterResolver} + */ + public ParameterResolver getDefaultParameterResolver() + { + return m_parameterResolver; + } + + // ----- internal ------------------------------------------------------- + + /** + * Validate the cache configuration. + * + * @param registry the ResourceRegistry associated with this configuration. + * + * @return this object + */ + public CacheConfig validate(ResourceRegistry registry) + { + ResourceMappingRegistry regMapping = getMappingRegistry(); + ServiceSchemeRegistry regSchemes = getServiceSchemeRegistry(); + + Base.checkNotNull(regMapping, "ResourceMappingRegistry"); + Base.checkNotNull(regSchemes, "ServiceSchemeRegistry"); + + // Ensure mappings map to valid schemes + for (ResourceMapping mapping : regMapping) + { + String sSchemeName = mapping.getSchemeName(); + ServiceScheme scheme = regSchemes.findSchemeBySchemeName(sSchemeName); + if (scheme == null) + { + throw new ConfigurationException("Scheme definition missing for scheme " + sSchemeName, "Provide the scheme definition."); + } + mapping.validateScheme(scheme); + } + + return this; + } + + // ----- constants ------------------------------------------------------ + + /** + * Top-level element name. + */ + public final static String TOP_LEVEL_ELEMENT_NAME = "cache-config"; + + // ----- data members --------------------------------------------------- + + /** + * The scope name. + */ + private String m_sScopeName; + + /** + * The topic/cache mapping to scheme {@link SchemeMappingRegistry} of the {@link CacheConfig}. + */ + private SchemeMappingRegistry m_registrySchemeMapping; + + /** + * The {@link ServiceSchemeRegistry} for the {@link CacheConfig}. + */ + private ServiceSchemeRegistry m_registrySchemeRegistry; + + /** + * The default {@link ParameterResolver} to be used for resolving + * parameters in expressions. This is typically used when attempting + * to resolve expressions outside of a cache context. + */ + private ParameterResolver m_parameterResolver; + + /** + * The {@link List} of {@link NamedEventInterceptorBuilder}s associated + * with this {@link CacheConfig}. + */ + private List m_listEventInterceptorBuilders; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/CacheMapping.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/CacheMapping.java new file mode 100644 index 0000000000000..fe3b1aab6d173 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/CacheMapping.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.coherence.config.scheme.CachingScheme; +import com.tangosol.coherence.config.scheme.Scheme; + +import com.tangosol.config.annotation.Injectable; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.NamedCache; + +import com.tangosol.util.ClassHelper; + +/** + * A {@link CacheMapping} captures configuration information for a pattern-match-based mapping from a proposed + * {@link NamedCache} name to a caching scheme. + *

+ * In addition to the mapping between a cache name and a caching scheme, each {@link CacheMapping} retains a + * {@link ParameterResolver} (representing user-provided parameters) to be during the realization of the said cache + * and scheme. (This allows individual mappings to be parameterized) + *

+ * Lastly {@link CacheMapping}s also provide a mechanism to associate specific strongly typed resources + * with each mapping at runtime. This provides a flexible and dynamic mechanism to associate further configuration + * information with caches. + *

+ * Pattern Matching Semantics: + * The only wildcard permitted for pattern matching with cache names is the "*" and it may only + * be used at the end of a cache name. + *

+ * For example, the following cache name patterns are valid: + * "*" and something-*, but *-something is invalid. + * + * @author bo 2011.06.25 + * @since Coherence 12.1.2 + */ +public class CacheMapping + extends ResourceMapping + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link CacheMapping} for caches that will use rawtypes by default. + * + * @param sCacheNamePattern the pattern that maps cache names to caching schemes + * @param sCachingSchemeName the name of the caching scheme to which caches matching this + * {@link CacheMapping} will be associated + */ + public CacheMapping(String sCacheNamePattern, String sCachingSchemeName) + { + super(sCacheNamePattern, sCachingSchemeName); + + m_sKeyClassName = null; + m_sValueClassName = null; + } + + // ----- ResourceMapping methods -------------------------------------------- + + @Override + public String getConfigElementName() + { + return "cache-name"; + } + + @Override + public void validateScheme(Scheme scheme) + { + if (scheme instanceof CachingScheme) + { + return; + } + + String sElement = getConfigElementName(); + String sPattern = getNamePattern(); + String sScheme = scheme.getSchemeName(); + String sMsg = String.format("Mapping <%s>%s maps to %s which is not a valid caching scheme", sScheme, + sElement, sPattern, sElement); + + throw new IllegalStateException(sMsg); + } + + // ----- CacheMapping methods ------------------------------------------- + /** + * Set true if this cache mapping is for federated caches. + * This has no effect for non-federated caches. + * + * @param fIsFederated true if this cache is to be federated + * @return this CacheMapping object + */ + @Injectable("federated") + public CacheMapping setFederated(boolean fIsFederated) + { + m_fFederated = fIsFederated; + return this; + } + + /** + * Check if this CacheMapping is federated. + * + * @return true if this CacheMapping is federated + */ + public boolean isFederated() + { + return m_fFederated; + } + + @Override + public CacheMapping setInternal(boolean fIsInternal) + { + super.setInternal(fIsInternal); + return this; + } + + /** + * Obtains the pattern used to match cache names to this {@link CacheMapping}. + * + * @return the pattern + * + * @deprecated As of Coherence 14.1.1, use {@link #getNamePattern()}. + */ + public String getCacheNamePattern() + { + return getNamePattern(); + } + + /** + * Obtains the name of the caching scheme to be used for {@link NamedCache}s that match this {@link CacheMapping}. + * + * @return the name of the associated caching scheme + * + * @deprecated As of Coherence 14.1.1, use {@link #getSchemeName()}. + */ + public String getCachingSchemeName() + { + return getSchemeName(); + } + + /** + * Obtains the name of the key class for {@link NamedCache}s using this {@link CacheMapping}. + * + * @return the name of the key class or null if rawtypes are being used + */ + public String getKeyClassName() + { + return m_sKeyClassName; + } + + /** + * Obtains the name of the value class for {@link NamedCache}s using this {@link CacheMapping}. + * + * @return the name of the value class or null if rawtypes are being used + */ + public String getValueClassName() + { + return m_sValueClassName; + } + + /** + * Determines if the {@link CacheMapping} is configured to use raw-types + * (ie: no type checking or constraints) + * + * @return true if using rawtypes, false otherwise + */ + public boolean usesRawTypes() + { + return m_sKeyClassName == null || m_sValueClassName == null; + } + + /** + * Sets the name of the key class for {@link NamedCache}s using this {@link CacheMapping}. + * + * @param sKeyClassName the name of the key class or null if rawtypes are being used + */ + @Injectable("key-type") + public void setKeyClassName(String sKeyClassName) + { + m_sKeyClassName = ClassHelper.getFullyQualifiedClassNameOf(sKeyClassName); + } + + /** + * Sets the name of the value class for {@link NamedCache}s using this {@link CacheMapping}. + * + * @param sValueClassName the name of the value class or null if rawtypes are being used + */ + @Injectable("value-type") + public void setValueClassName(String sValueClassName) + { + m_sValueClassName = ClassHelper.getFullyQualifiedClassNameOf(sValueClassName); + } + + // ----- data members --------------------------------------------------- + + /** + * The flag to indicate if this {@link CacheMapping} is federated. + */ + public boolean m_fFederated = true; + + /** + * The name of the key class or null if rawtypes are being used (the default). + */ + private String m_sKeyClassName; + + /** + * The name of the value class or null if rawtypes are being used (the default). + */ + private String m_sValueClassName; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/CacheMappingRegistry.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/CacheMappingRegistry.java new file mode 100644 index 0000000000000..feb694f78e4af --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/CacheMappingRegistry.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import java.util.Iterator; + +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * A {@link CacheMappingRegistry} provides a mechanism manage a collection + * of {@link CacheMapping}s, together with the ability to search the registry for + * said {@link CacheMapping}s, possibly using wild-cards. + *

+ * {@link CacheMappingRegistry}s are {@link Iterable}, the order of iteration + * being that in which the {@link CacheMapping}s where added to the said + * {@link CacheMappingRegistry}. + * + * @author bo 2011.06.29 + * @since Coherence 12.1.2 + * + * @deprecated As Coherence 14.1.1, use {@link ResourceMappingRegistry}. + */ +public class CacheMappingRegistry + implements Iterable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link CacheMappingRegistry}. + */ + public CacheMappingRegistry() + { + m_registry = new SchemeMappingRegistry(); + } + + /** + * CacheMappingRegistry delegates to {@link ResourceMappingRegistry}. + * + * @param registry delegate resource registry containing CacheMapping + * + * @since Coherence 14.1.1 + */ + public CacheMappingRegistry(ResourceMappingRegistry registry) + { + m_registry = registry; + } + + // ----- Iterable interface --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Iterator iterator() + { + Stream stream = StreamSupport.stream(m_registry.spliterator(), false).filter(e -> e instanceof CacheMapping); + return stream.iterator(); + } + + // ----- CacheMappingRegistry methods ----------------------------------- + + /** + * Registers a {@link CacheMapping} with the {@link CacheMappingRegistry}. + * + * @param cacheMapping the {@link CacheMapping} to register + * + * @throws IllegalArgumentException if a {@link CacheMapping} with the same + * pattern has already been registered + */ + public void register(CacheMapping cacheMapping) + throws IllegalArgumentException + { + if (findCacheMapping(cacheMapping.getNamePattern()) == null) + { + m_registry.register(cacheMapping); + } + else + { + throw new IllegalArgumentException(String.format( + "Attempted to redefined an existing cache mapping for the %s", + cacheMapping.getNamePattern())); + } + } + + /** + * Attempts to find the {@link CacheMapping} that matches the specified + * cache name. + *

+ * The matching algorithm first attempts to find an exact match of a + * {@link CacheMapping} with the provided cache name. Should that fail, + * all of the currently registered wild-carded {@link CacheMapping}s are + * searched to find a match (in the order in which they were registered), + * with the most specific (longest match) being returned if there are + * multiple matches. + * + * @param sCacheName the cache name + * + * @return null if a {@link CacheMapping} could not be located + * for the specified cache name + */ + public CacheMapping findCacheMapping(String sCacheName) + { + return m_registry.findCacheMapping(sCacheName); + } + + /** + * Determines the number of {@link CacheMapping}s in the {@link CacheMappingRegistry}. + * + * @return the number of {@link CacheMapping}s + */ + public int size() + { + return (int) StreamSupport.stream(m_registry.spliterator(), false).filter(e -> e instanceof CacheMapping).count(); + } + + /** + * Get the underlying {@link ResourceMappingRegistry}. + * + * @return underlying registry + */ + public ResourceMappingRegistry getMappingRegistry() + { + return m_registry; + } + + // ----- data members --------------------------------------------------- + + /** + * The resource mapping registry containing {@link CacheMapping}s and other {@link ResourceMapping}s. + */ + private ResourceMappingRegistry m_registry; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/Config.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/Config.java new file mode 100644 index 0000000000000..5e3e1831ac7b6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/Config.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import java.util.function.Supplier; + +/** + * {@link Config} is a helper class for processing a Coherence configuration + * system properties. + *

+ * As of Coherence 12.2.1, all Coherence system properties start with coherence.. + * There is backwards compatibility support that if the property is not found, + * then the property is looked up again with tansogol. prepended and + * lastly by replacing the coherence. in the system property name to + * support Coherence system property naming conventions prior to Coherence 12.2.1. + *

+ * Note: These methods should only be used when the system property name may begin with "coherence.*" or "tangosol.*". + * The native method for looking up a system or environment property should be used to lookup up a OS/Language + * native property. + * + * @author jf 2015.04.21 + * @since Coherence 12.2.1 + */ +public abstract class Config + { + // ----- Config methods ------------------------------------------------- + + /** + * Get the value of Coherence property sName + *

+ * This implementation differs from {@link System#getProperty(String)} that a + * {@link java.lang.SecurityException} is handled and logged as a warning + * and null is returned as the property's value. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * + * @return value of property sName or null if property lookup + * fails or property does not exist + */ + static public String getProperty(String sName) + { + String sValue = SYS_PROPS.getProperty(sName); + + if (sValue == null) + { + // initial property lookup failed. + // trying using alternative coherence system property naming convention. + if (sName.startsWith("coherence.")) + { + return getPropertyBackwardsCompatibleMode(sName); + } + + // handle cases that sName is still following pre 12.2.1 coherence + // system property conventions, try 12.2.1 naming convention. + if (sName.startsWith("tangosol.coherence.")) + { + return SYS_PROPS.getProperty(sName.replaceFirst("tangosol.", "")); + } + else if (sName.startsWith("tangosol.")) + { + return SYS_PROPS.getProperty(sName.replaceFirst("tangosol", "coherence")); + } + } + + return sValue; + } + + /** + * Get a Coherence property value, return default if property lookup fails. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * @param sDefault default value returned if property lookup fails + * + * @return value of property sName, or sDefault if + * property lookup fails or no property defined + */ + static public String getProperty(String sName, String sDefault) + { + String sValue = getProperty(sName); + + return sValue == null ? sDefault : sValue; + } + + /** + * Get a Coherence property value, returning the value provided by the supplier + * if property lookup fails. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * @param supDefault the supplier that provides a default value if property lookup fails + * + * @return value of property sName, or value provided by the supDefault + * if property lookup fails or no property defined + */ + static public String getProperty(String sName, Supplier supDefault) + { + String sValue = getProperty(sName); + + return sValue == null ? supDefault.get() : sValue; + } + + /** + * Returns true if coherence system property sName exists and + * value is equal to string true. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * + * @return true if system property exists and equal to true + */ + static public boolean getBoolean(String sName) + { + return getBoolean(sName, false); + } + + /** + * Return true if property sName exists and its value is string true. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * @param fDefault default value if property value lookup or conversion fails. + * + * @return true if sName exists and its value is string true; otherwise, + * return sDefault. + */ + static public boolean getBoolean(String sName, boolean fDefault) + { + String sValue = getProperty(sName); + try + { + return sValue == null ? fDefault : Boolean.parseBoolean(sValue); + } + catch (RuntimeException e) + { + return fDefault; + } + } + + /** + * Return Coherence system property value as an Integer. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * + * @return property value as integer if property lookup and conversion + * of the String value to integer succeeds; otherwise, return null + */ + static public Integer getInteger(String sName) + { + String sValue = getProperty(sName); + try + { + return sValue == null ? null : Integer.parseInt(sValue); + } + catch (RuntimeException e) + { + return null; + } + } + + /** + * Return Coherence system property value as an Integer. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * @param iDefault integer default value + * + * @return property value as integer if property lookup and conversion + * of the String value to integer succeeds; otherwise, return iDefault + */ + static public Integer getInteger(String sName, int iDefault) + { + Integer i = getInteger(sName); + + return i == null ? Integer.valueOf(iDefault) : i; + } + + /** + * Return Coherence system property value as a Long. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * + * @return property value as long if property lookup and conversion + * of the String value to long succeeds; otherwise, return null + */ + static public Long getLong(String sName) + { + String sValue = getProperty(sName); + try + { + return sValue == null ? null : Long.parseLong(getProperty(sName)); + } + catch (RuntimeException e) + { + return null; + } + } + + /** + * Return Coherence system property value as a long. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * @param lDefault long default value + * + * @return property value as long if property lookup and conversion + * of the String value to long succeeds; otherwise, return lDefault + */ + static public Long getLong(String sName, long lDefault) + { + Long l = getLong(sName); + + return l == null ? Long.valueOf(lDefault) : l; + } + + /** + * Return Coherence system property value as a Float. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * + * @return property value as float if property lookup and conversion + * of the String value to float succeeds; otherwise, return null + */ + static public Float getFloat(String sName) + { + String sValue = getProperty(sName); + try + { + return sValue == null ? null : Float.parseFloat(getProperty(sName)); + } + catch (RuntimeException e) + { + return null; + } + } + + /** + * Return Coherence system property value as a float. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * @param fDefault float default value + * + * @return property value as long if property lookup and conversion + * of the String value to float succeeds; otherwise, return fDefault + */ + static public Float getFloat(String sName, float fDefault) + { + Float d = getFloat(sName); + + return d == null ? new Float(fDefault) : d; + } + + /** + * Return Coherence system property value as a Double. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * + * @return property value as double if property lookup and conversion + * of the String value to double succeeds; otherwise, return null + */ + static public Double getDouble(String sName) + { + String sValue = getProperty(sName); + try + { + return sValue == null ? null : Double.parseDouble(sValue); + } + catch (RuntimeException e) + { + return null; + } + } + + /** + * Return Coherence system property value as a double. + *

+ * Backwards compatibility support is described in {@link Config}. + * + * @param sName Coherence system property name beginning with coherence. + * @param dDefault double default value + * + * @return property value as double if property lookup and conversion + * of the String value to double succeeds; otherwise, return dDefault + */ + static public Double getDouble(String sName, double dDefault) + { + Double d = getDouble(sName); + + return d == null ? new Double(dDefault) : d; + } + + /** + * Coherence enhanced system environment getter + * Use instead of {@link System#getenv(String)}. + * + * @param sName Coherence system environment property name + * + * @return value for system environment property if it exists or null + */ + static public String getenv(String sName) + { + String sValue = ENV_VARS.getEnv(sName); + + if (sValue == null) + { + // initial property lookup failed. + // trying using alternative coherence system property naming convention. + if (sName.startsWith("coherence.")) + { + return getEnvironmentBackwardsCompatibleMode(sName); + } + + // handle cases that sName is still following pre 12.2.1 coherence + // system property conventions, try 12.2.1 naming convention. + if (sName.startsWith("tangosol.coherence.")) + { + return ENV_VARS.getEnv(sName.replaceFirst("tangosol.", "")); + } + else if (sName.startsWith("tangosol.")) + { + return ENV_VARS.getEnv(sName.replaceFirst("tangosol", "coherence")); + } + } + + return sValue; + + } + + // ----- helpers -------------------------------------------------------- + + /** + * Resolve sName using backwards compatibility rules. + * Checks for backwards compatible properties "tangosol.coherence.*" and "tangosol.*". + * + * @param sName Coherence system property name beginning with "coherence." + * @return String for backwards compatibility conversion of sName or null if not defined + * + * @since Coherence 12.2.1 + */ + static private String getPropertyBackwardsCompatibleMode(String sName) + { + // check for tangosol.coherence.* backwards compatibility + String sValue = SYS_PROPS.getProperty("tangosol." + sName); + + return sValue == null ? SYS_PROPS.getProperty(sName.replaceFirst("coherence", "tangosol")) : sValue; + } + + /** + * Resolve sName using backwards compatibility rules. + * Checks for backwards compatible properties "tangosol.coherence.*" and "tangosol.*". + * + * @param sName Coherence system property name beginning with "coherence." + * @return String for backwards compatibility conversion of sName or null if not defined + * + * @since Coherence 12.2.1 + */ + static private String getEnvironmentBackwardsCompatibleMode(String sName) + { + // check for tangosol.coherence.* backwards compatibility + String sValue = ENV_VARS.getEnv("tangosol." + sName); + + return sValue == null ? ENV_VARS.getEnv(sName.replaceFirst("coherence", "tangosol")) : sValue; + } + + // ----- static members ------------------------------------------------- + + /** + * {@link SystemPropertyResolver} to use. + */ + private static final SystemPropertyResolver SYS_PROPS + = SystemPropertyResolver.getInstance(); + + /** + * {@link EnvironmentVariableResolver} to use. + */ + private static final EnvironmentVariableResolver ENV_VARS + = EnvironmentVariableResolver.getInstance(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/EnvironmentVariableResolver.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/EnvironmentVariableResolver.java new file mode 100644 index 0000000000000..e36c9262e6ad4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/EnvironmentVariableResolver.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + + +import java.util.Iterator; +import java.util.Optional; +import java.util.ServiceLoader; + + +/** + * An abstraction that allows us to make environment variable resolution + * customizable. + * + * @author as 2019.10.11 + */ +public interface EnvironmentVariableResolver + { + /** + * Return the value of the specified environment variable. + * + * @param sVarName the name of the environment variable to return + * + * @return the value of the specified environment variable + */ + public String getEnv(String sVarName); + + /** + * Return the value of the specified environment variable, or the specified + * default value if the variable doesn't exist. + * + * @param sVarName the name of the environment variable to return + * @param sDefaultValue the default value to return if the variable + * doesn't exist + * + * @return the value of the specified environment variable, or the specified + * default value if the variable doesn't exist + */ + public default String getEnv(String sVarName, String sDefaultValue) + { + return Optional.ofNullable(getEnv(sVarName)).orElse(sDefaultValue); + } + + /** + * Return an instance of a {@link EnvironmentVariableResolver} discovered by + * the {@code ServiceLoader}, or a default instance if none are discovered. + * + * @return an instance of a {@code EnvironmentVariableResolver} + */ + public static EnvironmentVariableResolver getInstance() + { + ServiceLoader serviceLoader = + ServiceLoader.load(EnvironmentVariableResolver.class); + Iterator resolvers = serviceLoader.iterator(); + return resolvers.hasNext() ? resolvers.next() : new Default(); + } + + // ---- inner class: Default -------------------------------------------- + + /** + * Default {@link EnvironmentVariableResolver} implementation. + *

+ * This implementation simply delegates to {@link System#getenv(String)}. + */ + class Default implements EnvironmentVariableResolver + { + @Override + public String getEnv(String sVarName) + { + return System.getenv(sVarName); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ParameterList.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ParameterList.java new file mode 100644 index 0000000000000..1a08fbaf71307 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ParameterList.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +/** + * An {@link ParameterList} is a strictly ordered and {@link Iterable} collection of {@link Parameter}s. + * + * @see Parameter + * @see ParameterResolver + * + * @author bo 2011.09.14 + * @since Coherence 12.1.2 + */ +public interface ParameterList + extends Iterable + { + /** + * Determines if there are any {@link Parameter}s in the {@link ParameterList}. + * + * @return true if there are {@link Parameter}s, false otherwise + */ + public boolean isEmpty(); + + /** + * Obtains the number of {@link Parameter}s in the {@link ParameterList}. + * + * @return the number of {@link Parameter}s + */ + public int size(); + + /** + * Adds a {@link Parameter} to the end of the {@link ParameterList} or replaces an existing {@link Parameter} + * in the {@link ParameterList}. + *

+ * Should a {@link Parameter} with the same name as the specified {@link Parameter} already exist in the list, the + * specified {@link Parameter} will replace the existing {@link Parameter} in the list. + * + * @param parameter the {@link Parameter} to add or replace + */ + public void add(Parameter parameter); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ParameterMacroExpression.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ParameterMacroExpression.java new file mode 100644 index 0000000000000..8edd99653c83b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ParameterMacroExpression.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.expression.Value; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.util.Base; +import com.tangosol.util.ExternalizableHelper; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import javax.json.bind.annotation.JsonbProperty; + +/** + * A {@link ParameterMacroExpression} is an {@link Expression} representing the use of a Coherence Parameter Macro, + * typically occurring with in a Coherence Cache Configuration file. + *

+ * Coherence Macro Parameters are syntactically represented as follows: + *

+ * {parameter-name [default-value]} + *

+ * When a {@link ParameterMacroExpression} is evaluated the parameter-name and it's associated value is resolved by + * consulting the provided {@link ParameterResolver}. If the parameter is resolvable, + * the value of the resolved parameter is returned. If it's not resolvable the default value is returned. + *

+ * Note: Returned values are always coerced into the type defined by the {@link Expression}. + * + * @author bo 2011.06.22 + * @since Coherence 12.1.2 + */ +public class ParameterMacroExpression + implements Expression, ExternalizableLite, PortableObject + { + // ----- constructor ---------------------------------------------------- + + /** + * Default constructor needed for serialization. + */ + public ParameterMacroExpression() + { + } + + /** + * Construct a {@link ParameterMacroExpression}. + * + * @param sExpression a string representation of the {@link Expression} + * @param clzResultType the type of value the {@link Expression} will return when evaluated + */ + public ParameterMacroExpression(String sExpression, Class clzResultType) + { + Base.azzert(clzResultType != null); + + m_clzResultType = clzResultType; + m_sExpression = sExpression.trim(); + } + + // ----- Expression interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + public T evaluate(ParameterResolver resolver) + { + // there are two styles of usage for parameter macros with coherence. + // i). the expression contains a single parameter macro usage (and nothing else) in which the parameter + // is evaluated and returned. + // + // ii). the expression contains zero or more parameter macros in a string in which case we have to + // resolve each parameter and replace them in the string, after which we have to evaluate the string. + + // assume we don't have a result + Value result = null; + + // assume we won't require the string based result + boolean fUseStringBasedResult = false; + + // we may need to build a string containing the result, before we can produce a result + StringBuilder bldr = new StringBuilder(); + + for (int idx = 0; idx < m_sExpression.length(); idx++) + { + if (m_sExpression.startsWith("\\{", idx)) + { + bldr.append("{"); + idx++; + fUseStringBasedResult = true; + } + else if (m_sExpression.startsWith("\\}", idx)) + { + bldr.append("}"); + idx++; + fUseStringBasedResult = true; + } + else if (m_sExpression.charAt(idx) == '{') + { + String sParameterName; + String sDefaultValue; + int idxDefaultValue = m_sExpression.indexOf(" ", idx + 1); + + if (idxDefaultValue >= 0) + { + sParameterName = m_sExpression.substring(idx + 1, idxDefaultValue).trim(); + + int idxEndMacro = m_sExpression.indexOf("}", idxDefaultValue); + + if (idxEndMacro > idxDefaultValue) + { + sDefaultValue = m_sExpression.substring(idxDefaultValue, idxEndMacro).trim(); + idx = idxEndMacro; + } + else + { + throw new IllegalArgumentException(String.format( + "Invalid parameter macro definition in [%s]. " + + "Missing closing brace '}'.", m_sExpression)); + } + } + else + { + int idxEndMacro = m_sExpression.indexOf("}", idx + 1); + + if (idxEndMacro > idx + 1) + { + sParameterName = m_sExpression.substring(idx + 1, idxEndMacro).trim(); + sDefaultValue = null; + idx = idxEndMacro; + } + else + { + throw new IllegalArgumentException(String.format( + "Invalid parameter macro definition in [%s]. " + + "Missing closing brace '}'.", m_sExpression)); + } + } + + Parameter parameter = resolver.resolve(sParameterName); + Value value; + + if (parameter == null) + { + if (sDefaultValue == null) + { + throw new IllegalArgumentException(String.format( + "The specified parameter name '%s' in the macro " + + "parameter '%s' is unknown and not resolvable", + sParameterName, m_sExpression)); + } + else + { + value = new Value(sDefaultValue); + } + } + else + { + value = parameter.evaluate(resolver); + } + + result = value == null ? new Value() : value; + + if (fUseStringBasedResult || (value.get() instanceof String)) + { + try + { + bldr.append(value.get().toString()); + } + catch (Exception e) + { + CacheFactory.log("ParameterMacroExpression evaluation " + + "resulted in a toString Exception for class " + + value.get().getClass().getName(), CacheFactory.LOG_WARN); + throw Base.ensureRuntimeException(e); + } + } + } + else + { + bldr.append(m_sExpression.charAt(idx)); + fUseStringBasedResult = true; + } + } + + Object o = fUseStringBasedResult + ? new Value(bldr.toString()).as(m_clzResultType) : result == null ? null : result.as(m_clzResultType); + + return fUseStringBasedResult ? new Value(bldr.toString()).as(m_clzResultType) + : result == null ? null :result.as(m_clzResultType); + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + m_sExpression = ExternalizableHelper.readUTF(in); + + String sClzName = ExternalizableHelper.readSafeUTF(in); + if (sClzName.length() > 0) + { + try + { + m_clzResultType = (Class) Class.forName(sClzName); + } + catch (ClassNotFoundException e) + { + throw Base.ensureRuntimeException(e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + ExternalizableHelper.writeUTF(out, m_sExpression); + ExternalizableHelper.writeUTF(out, m_clzResultType.getName()); + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) throws IOException + { + m_sExpression = in.readString(0); + + String sClzName = in.readString(1); + if (sClzName.length() > 0) + { + try + { + m_clzResultType = (Class) Class.forName(sClzName); + } + catch (ClassNotFoundException e) + { + throw Base.ensureRuntimeException(e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter out) throws IOException + { + out.writeString(0, m_sExpression); + out.writeString(1, m_clzResultType.getName()); + } + + // ----- Object interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return String.format("ParameterMacroExpression{type=%s, expression=%s}", m_clzResultType, m_sExpression); + } + + // ----- data members --------------------------------------------------- + + /** + * The type of value to be returned by the {@link Expression}. + */ + @JsonbProperty("resultType") + private Class m_clzResultType; + + /** + * The expression to be evaluated. + */ + @JsonbProperty("expression") + private String m_sExpression; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ParameterMacroExpressionParser.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ParameterMacroExpressionParser.java new file mode 100644 index 0000000000000..7e8842c1f4a53 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ParameterMacroExpressionParser.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.ExpressionParser; + +import java.text.ParseException; + +/** + * A {@link ParameterMacroExpressionParser} is an {@link ExpressionParser} for Coherence Parameter Macros. + * + * @author bo 2011.10.18 + * @since Coherence 12.1.2 + */ +public class ParameterMacroExpressionParser + implements ExpressionParser + { + // ----- ExpressionParser interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Expression parse(String sExpression, Class clzResultType) + throws ParseException + { + return new ParameterMacroExpression(sExpression, clzResultType); + } + + // ----- constants ------------------------------------------------------ + + /** + * The singleton instance of the {@link ParameterMacroExpressionParser}. + */ + public final static ExpressionParser INSTANCE = new ParameterMacroExpressionParser(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/RegExCacheMapping.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/RegExCacheMapping.java new file mode 100644 index 0000000000000..3518a76440d6d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/RegExCacheMapping.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import java.util.regex.Pattern; + +/** + * An extension of {@link CacheMapping} that can use a regular expression to + * match with a given cache name. + * + * @author jk 2015.05.29 + * @since Coherence 14.1.1 + */ +public class RegExCacheMapping + extends CacheMapping + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link RegExCacheMapping} for caches that will use raw types by default. + * + * @param sCacheNamePattern the RegEx pattern that maps cache names to caching schemes + * @param sCachingSchemeName the name of the caching scheme to which caches matching this + * {@link RegExCacheMapping} will be associated + */ + public RegExCacheMapping(String sCacheNamePattern, String sCachingSchemeName) + { + super(sCacheNamePattern, sCachingSchemeName); + + f_pattern = Pattern.compile(sCacheNamePattern); + } + + // ----- CacheMapping methods ------------------------------------------- + + @Override + public boolean isForName(String sName) + { + return f_pattern.matcher(sName).matches(); + } + + // ----- data members --------------------------------------------------- + + /** + * The RegEx {@link Pattern} to use to match cache names + */ + private final Pattern f_pattern; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ResolvableParameterList.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ResolvableParameterList.java new file mode 100644 index 0000000000000..42931a7bd44bf --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ResolvableParameterList.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.ExternalizableHelper; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.json.bind.annotation.JsonbProperty; + +/** + * A {@link ResolvableParameterList} is a {@link ParameterList} implementation that additionally supports + * name-based {@link Parameter} resolution as defined by the {@link ParameterResolver} interface. + * + * @author bo 2011.06.22 + * @since Coherence 12.1.2 + */ +public class ResolvableParameterList + implements ParameterList, ParameterResolver, ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs an empty {@link ResolvableParameterList}. + */ + public ResolvableParameterList() + { + m_mapParameters = new LinkedHashMap(); + } + + /** + * Constructs a {@link ResolvableParameterList} based on a {@link ParameterList}. + * + * @param listParameters the {@link ParameterList} from which {@link Parameter}s should be drawn + */ + public ResolvableParameterList(ParameterList listParameters) + { + this(); + + for (Parameter parameter : listParameters) + { + add(parameter); + } + } + + /** + * Construct a {@link ResolvableParameterList} from provided map. + * The key of the map is used as the parameter name and the + * corresponding value as the parameter value. + * + * @param map the Map of Named Parameter of type String to an Object value + * + * @since Coherence 12.2.1 + */ + public ResolvableParameterList(Map map) + { + this(); + + if (map != null && ! map.isEmpty()) + { + Map map1 = map; + for (Map.Entry entry : map1.entrySet()) + { + add(new Parameter(String.valueOf(entry.getKey()), entry.getValue())); + } + } + } + + + // ----- ParameterResolver interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Parameter resolve(String sName) + { + return m_mapParameters.get(sName); + } + + // ----- ParameterList interface ---------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void add(Parameter parameter) + { + m_mapParameters.put(parameter.getName(), parameter); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() + { + return m_mapParameters.isEmpty(); + } + + /** + * {@inheritDoc} + */ + @Override + public int size() + { + return m_mapParameters.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator iterator() + { + return m_mapParameters.values().iterator(); + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + ExternalizableHelper.readMap(in, m_mapParameters, this.getClass().getClassLoader()); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + ExternalizableHelper.writeMap(out, m_mapParameters); + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader reader) throws IOException + { + reader.readMap(0, m_mapParameters); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter writer) throws IOException + { + writer.writeMap(0, m_mapParameters); + } + + // ----- logging support ------------------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return "{" + m_mapParameters.toString() + "}"; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Parameter} map. + *

+ * NOTE: It's important that this is a {@link LinkedHashMap} as we expect to be able to iterate over + * the {@link Parameter}s in the order in which they were added to the {@link ResolvableParameterList}. + * This is to support implementation of the {@link ParameterList} interface. + */ + @JsonbProperty("parameters") + private LinkedHashMap m_mapParameters; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ResourceMapping.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ResourceMapping.java new file mode 100644 index 0000000000000..717af8e3d54ad --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ResourceMapping.java @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.scheme.Scheme; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.Base; +import com.tangosol.util.ResourceRegistry; +import com.tangosol.util.SimpleResourceRegistry; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * A base class for mapping elements. + * + * @author jk 2015.05.21 + * @since Coherence 14.1.1 + */ +public abstract class ResourceMapping + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link ResourceMapping} for resources that will use raw types by default. + * + * @param sNamePattern the pattern that maps resource names to caching schemes + * @param sSchemeName the name of the caching scheme to which a resource matching + * this {@link ResourceMapping} will be associated + */ + public ResourceMapping(String sNamePattern, String sSchemeName) + { + Base.azzert(sNamePattern != null); + Base.azzert(sSchemeName != null); + + f_sNamePattern = sNamePattern; + f_sSchemeName = sSchemeName; + m_parameterResolver = new ResolvableParameterList(); + m_listEventInterceptorBuilders = new LinkedList<>(); + f_resourceRegistry = new SimpleResourceRegistry(); + f_subMappings = new ArrayList<>(); + } + + // ----- ResourceMapping methods -------------------------------------------- + + /** + * Obtain the xml element name of this mapping. + * + * @return the xml element name of this mapping + */ + public abstract String getConfigElementName(); + + /** + * Determine whether the specified schem is valid + * for this mapping type. + * + * @param scheme the scheme to validate + * + * @throws IllegalStateException if the scheme is not valid + */ + public abstract void validateScheme(Scheme scheme); + + /** + * Set the flag to indicate if this mapping is for internal resources + * used by the service. + * + * @param fInternal true if this is for internal resource + * + * @return this ResourceMapping object + */ + public ResourceMapping setInternal(boolean fInternal) + { + m_fInternal = fInternal; + + return this; + } + + /** + * Check if this ResourceMapping is for internal resources. + * + * @return true if this is for internal resources + */ + public boolean isInternal() + { + return m_fInternal; + } + + /** + * Obtains the pattern used to match resource names + * to this {@link ResourceMapping}. + * + * @return the pattern + */ + public String getNamePattern() + { + return f_sNamePattern; + } + + /** + * Obtains the name of the caching scheme to be used + * that match this {@link ResourceMapping}. + * + * @return the name of the associated caching scheme + */ + public String getSchemeName() + { + return f_sSchemeName; + } + + /** + * Obtains the {@link ResourceRegistry} that holds resources + * associated with the {@link ResourceMapping}. + * + * @return the {@link ResourceRegistry} + */ + public ResourceRegistry getResourceRegistry() + { + return f_resourceRegistry; + } + + /** + * Obtains the {@link ParameterResolver} that is to be used to + * resolve {@link Parameter}s associated with this {@link ResourceMapping}. + * + * @return the {@link ParameterResolver} + */ + public ParameterResolver getParameterResolver() + { + return m_parameterResolver; + } + + /** + * Sets the {@link ParameterResolver} that is used to resolve + * {@link Parameter}s associated with the {@link ResourceMapping}. + * + * @param resolver the {@link ParameterResolver} + */ + @Injectable("init-params") + public void setParameterResolver(ParameterResolver resolver) + { + m_parameterResolver = resolver == null + ? new ResolvableParameterList() + : resolver; + } + + /** + * Obtains the {@link List} of {@link NamedEventInterceptorBuilder}s + * for this {@link ResourceMapping}. + * + * @return an {@link List} over {@link NamedEventInterceptorBuilder}s + * or null if none are defined + */ + public List getEventInterceptorBuilders() + { + return m_listEventInterceptorBuilders; + } + + /** + * Sets the {@link List} of {@link NamedEventInterceptorBuilder}s for the {@link ResourceMapping}. + * + * @param listBuilders the {@link List} of {@link NamedEventInterceptorBuilder}s + */ + @Injectable("interceptors") + public void setEventInterceptorBuilders(List listBuilders) + { + m_listEventInterceptorBuilders = listBuilders; + } + + /** + * Determines if the {@link ResourceMapping} is for (matches) the specified resource name. + * + * @param sName the resource name to check for a match + * + * @return true if the {@link ResourceMapping} is for the specified resource name, + * false otherwise + */ + public boolean isForName(String sName) + { + return (f_sNamePattern.endsWith("*") && sName.regionMatches(0, f_sNamePattern, 0, f_sNamePattern.length() - 1)) + || f_sNamePattern.equals(sName); + } + + /** + * Determines if the {@link ResourceMapping} pattern contains a * wildcard. + * + * @return true if the pattern contains a * wildcard, + * false otherwise + */ + public boolean usesWildcard() + { + return f_sNamePattern.contains("*"); + } + + /** + * Determines the value the wildcard * declared in the resource name + * pattern for the {@link ResourceMapping} matches. If the pattern does + * not contain a wildcard * or the resource name does not match the + * mapping, null is returned. + *

+ * Examples: + *

+ * 1. Calling mapping.getWildcardMatch("dist-test") on a ResourceMapping with + * the resource name pattern "dist-*" will return "test". + *

+ * 2. Calling mapping.getWildcardMatch("dist-*") on a ResourceMapping with + * the resource name pattern "dist-*" will return "*". + *

+ * 3. Calling mapping.getWildcardMatch("dist-fred") on a ResourceMapping with + * the resource name pattern "dist-fred" will return null. + *

+ * 4. Calling mapping.getWildcardMatch("dist-fred") on a ResourceMapping with + * the resource name pattern "repl-*" will return null. + *

+ * 5. Calling mapping.getWildcardMatch("dist-fred") on a ResourceMapping with + * the resource name pattern "*" will return "dist-fred". + * + * @param sName the resource name to match + * + * @return the resource name string that matches the wildcard. + */ + public String getWildcardMatch(String sName) + { + if (sName != null && !sName.isEmpty() && usesWildcard() && isForName(sName)) + { + return f_sNamePattern.equals("*") ? sName : sName.substring(f_sNamePattern.indexOf("*")); + } + else + { + return null; + } + } + + /** + * Get value of sParamName associated with this {@link CacheMapping} + * + * @param sParamName parameter name to look up + * @param paramValueType parameter value type + * @param parameter value type + * + * @return parameter value as an instance of paramValueType or null if parameter is not defined + */ + public T getValue(String sParamName, Class paramValueType) + { + Parameter param = getParameterResolver().resolve(sParamName); + return param == null ? null : param.evaluate(getParameterResolver()).as(paramValueType); + } + + /** + * Get value of sParamName associated with this {@link CacheMapping} + * + * @param sParamName parameter name to look up + * + * @return parameter value or null if parameter is not found + */ + public Object getValue(String sParamName) + { + ParameterResolver resolver = getParameterResolver(); + Parameter param = resolver.resolve(sParamName); + + return param == null ? null : param.evaluate(resolver).get(); + } + + /** + * Determines the name of a resource given a value for the wildcard + * (assuming the resource name pattern for the mapping is using a wildcard). + * If the pattern does not contain a wildcard *, null will + * be returned. + *

+ * Examples: + *

+ * 1. Calling mapping.getNameUsing("test") on a ResourceMapping with + * the resource name pattern "dist-*" will return "dist-test". + *

+ * 2. Calling mapping.getNameUsing("*") on a ResourceMapping with + * the resource name pattern "dist-*" will return "dist-*". + *

+ * 3. Calling mapping.getNameUsing("fred") on a ResourceMapping with + * the resource name pattern "dist-fred" will return null. + *

+ * 4. Calling mapping.getNameUsing("dist-fred") on a ResourceMapping with + * the resource name pattern "*" will return "dist-fred". + * + * @param sWildCardValue the value to replace the wildcard * with + * + * @return the resource name with the wildcard replaced with the specified value + */ + public String getNameUsing(String sWildCardValue) + { + if (sWildCardValue != null && !sWildCardValue.isEmpty() && usesWildcard()) + { + return f_sNamePattern.replaceAll("\\*", sWildCardValue); + } + else + { + return null; + } + + } + + /** + * Obtain the list of sub-mappings that this mapping contains + * + * @return the list of sub-mappings that this mapping contains. + */ + public List getSubMappings() + { + return f_subMappings; + } + + // ----- data members --------------------------------------------------- + + /** + * The flag to indicate if this {@link ResourceMapping} is for internal resources. + */ + private boolean m_fInternal; + + /** + * The pattern to be used to match and associate resource names with this {@link ResourceMapping}. + */ + private final String f_sNamePattern; + + /** + * The name of the caching scheme to which resource matching this {@link ResourceMapping} will be bound. + */ + private final String f_sSchemeName; + + /** + * The {@link ParameterResolver} to use for resolving {@link Parameter}s defined by this {@link ResourceMapping}. + */ + private ParameterResolver m_parameterResolver; + + /** + * The {@link ResourceRegistry} associated with this {@link ResourceMapping}. + */ + private final ResourceRegistry f_resourceRegistry; + + /** + * The {@link List} of {@link NamedEventInterceptorBuilder}s associated with this {@link ResourceMapping}. + */ + private List m_listEventInterceptorBuilders; + + /** + * The {@link List} of child mappings of this mapping. + */ + private final List f_subMappings; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ResourceMappingRegistry.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ResourceMappingRegistry.java new file mode 100644 index 0000000000000..037f371954754 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ResourceMappingRegistry.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +/** + * A {@link SchemeMappingRegistry} provides a mechanism manage a collection + * of {@link ResourceMapping}s, together with the ability to search the registry for + * said {@link ResourceMapping}s, possibly using wild-cards. + *

+ * {@link ResourceMappingRegistry}s are {@link Iterable}, the order of iteration + * being that in which the {@link ResourceMapping}s where added to the said + * {@link ResourceMappingRegistry}. + * + * @author jk 2015.06.01 + * @since Coherence 14.1.1 + */ +public interface ResourceMappingRegistry + extends Iterable + { + // ----- ResourceMappingRegistry methods ---------------------------------------- + + /** + * Registers a {@link ResourceMapping} with the {@link ResourceMappingRegistry}. + * + * @param mapping the {@link ResourceMapping} to register + * + * @throws IllegalArgumentException if a {@link ResourceMapping} with the same + * pattern has already been registered + */ + public void register(ResourceMapping mapping) + throws IllegalArgumentException; + + /** + * Attempts to find the {@link CacheMapping} that matches the specified + * name and type. + *

+ * The matching algorithm first attempts to find an exact match of a + * {@link CacheMapping} with the provided name. Should that fail, + * all of the currently registered wild-carded {@link CacheMapping}s are + * searched to find a match (in the order in which they were registered), + * with the most specific (longest match) being returned if there are + * multiple matches. + * + * @param sName the name + * + * @return null if a mapping could not be located + * for the specified name and type + */ + public default CacheMapping findCacheMapping(String sName) + { + return findMapping(sName, CacheMapping.class); + } + + /** + * Attempts to find the {@link ResourceMapping} that matches the specified + * name and type. + *

+ * The matching algorithm first attempts to find an exact match of a + * {@link ResourceMapping} with the provided name. Should that fail, + * all of the currently registered wild-carded {@link ResourceMapping}s are + * searched to find a match (in the order in which they were registered), + * with the most specific (longest match) being returned if there are + * multiple matches. + * + * @param sName the name + * @param type the type of the mapping to locate + * + * @return null if a mapping could not be located + * for the specified name and type + */ + public M findMapping(String sName, Class type); + + /** + * Determines the number of {@link ResourceMapping}s in this {@link ResourceMappingRegistry}. + * + * @return the number of {@link ResourceMapping}s + */ + public int size(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/SchemeMappingRegistry.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/SchemeMappingRegistry.java new file mode 100644 index 0000000000000..b3978caa06b8d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/SchemeMappingRegistry.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.util.Base; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; + +/** + * A {@link SchemeMappingRegistry} provides a mechanism to manage a collection + * of {@link ResourceMapping}s, together with the ability to search the registry for + * said {@link ResourceMapping}s, possibly using wildcards. + *

+ * {@link SchemeMappingRegistry}s are {@link Iterable}, the order of iteration + * being that in which the {@link ResourceMapping}s where added to the said + * {@link SchemeMappingRegistry}. + *

+ * There is a separate namespace for {@link CacheMapping} and {@link TopicMapping}, allowing + * for a cache and a topic with exactly same name. + * + * @author jk 2015.06.01 + * @since Coherence 14.1.1 + */ +public class SchemeMappingRegistry + implements ResourceMappingRegistry + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link SchemeMappingRegistry}. + */ + public SchemeMappingRegistry() + { + f_mapMappings = new LinkedHashMap<>(); + } + + // ----- ResourceMappingRegistry methods ---------------------------------------- + + @Override + public Iterator iterator() + { + return f_mapMappings.values().iterator(); + } + + @Override + public void register(ResourceMapping mapping) + throws IllegalArgumentException + { + String sName = mapping.getNamePattern(); + Class classType = mapping instanceof CacheMapping ? CacheMapping.class : mapping instanceof TopicMapping ? TopicMapping.class : null; + SchemeMappingKey key = new SchemeMappingKey(classType, sName); + + if (classType == null) + { + throw new IllegalArgumentException("SchemeMappingRegistry.register: unknown class type: " + mapping.getClass().getCanonicalName()); + } + + if (f_mapMappings.containsKey(key)) + { + String sElementName = mapping.getConfigElementName(); + throw new IllegalArgumentException(String.format( + "Attempted to redefine an existing mapping for <%s>%s", + sElementName, sName, sElementName)); + } + else + { + f_mapMappings.put(key, mapping); + } + + List listMappings = mapping.getSubMappings(); + if (listMappings != null) + { + for (ResourceMapping mappingChild : listMappings) + { + register(mappingChild); + } + } + } + + @Override + public M findMapping(String sName, Class type) + { + ResourceMapping mapping; + + // is there an exact match for the provided name? + // ie: is the name explicitly defined as a mapping without wildcards? + + SchemeMappingKey key = new SchemeMappingKey(type, sName); + if (f_mapMappings.containsKey(key)) + { + mapping = f_mapMappings.get(key); + } + else + { + // attempt to find the most specific (ie: longest) wildcard defined + // mapping that matches the name + mapping = null; + + for (ResourceMapping mappingNext : f_mapMappings.values()) + { + if (type.isAssignableFrom(mappingNext.getClass()) && mappingNext.isForName(sName)) + { + if (mapping == null) + { + mapping = mappingNext; + } + else if (mappingNext.getNamePattern().length() > mapping.getNamePattern().length()) + { + mapping = mappingNext; + } + } + } + } + + if (mapping == null) + { + return null; + } + + //noinspection unchecked + return (M) mapping; + } + + @Override + public int size() + { + return f_mapMappings.size(); + } + + // ----- inner classes -------------------------------------------------- + + /** + * Key class for a scheme mapping. + */ + static public class SchemeMappingKey + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a {@link SchemeMappingKey}. + * + * @param clz class of registered scheme mapping + * @param sName name of registered scheme mapping + */ + public SchemeMappingKey(Class clz, String sName) + { + if (clz == null) + { + throw new NullPointerException("SchemeMapping class cannot be null"); + } + + if (sName == null) + { + throw new NullPointerException("SchemeMapping name cannot be null"); + } + + f_clz = clz; + f_sName = sName; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the scheme mapping class. + * + * @return the scheme mapping class + */ + public Class getSchemeMappingClass() + { + return f_clz; + } + + /** + * Return the resource name. + * + * @return the resource name + */ + public String getName() + { + return f_sName; + } + + // ----- Object methods --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + + if (o == null || !Base.equals(getClass(), o.getClass())) + { + return false; + } + + SchemeMappingKey that = (SchemeMappingKey) o; + + return Base.equals(f_clz, that.f_clz) && Base.equals(f_sName, that.f_sName); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() + { + int result = f_clz.hashCode(); + + result = 31 * result + f_sName.hashCode(); + + return result; + } + + // ----- data members ----------------------------------------------- + + /** + * The scheme mapping class. + */ + private final Class f_clz; + + /** + * The scheme mapping name. + */ + private final String f_sName; + } + + // ----- data members --------------------------------------------------- + + /** + * The map of {@link ResourceMapping}s. + */ + private final LinkedHashMap f_mapMappings; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ServiceSchemeRegistry.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ServiceSchemeRegistry.java new file mode 100644 index 0000000000000..de1122d6f31a6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/ServiceSchemeRegistry.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.coherence.config.scheme.Scheme; +import com.tangosol.coherence.config.scheme.ServiceScheme; + +import com.tangosol.util.UUID; + +import java.util.Iterator; +import java.util.LinkedHashMap; + +/** + * A {@link ServiceSchemeRegistry} provides a mechanism manage a collection + * of {@link ServiceScheme}s together with the ability to search the registry for + * said {@link ServiceScheme}s, either by name or service name. + *

+ * {@link ServiceSchemeRegistry}s are {@link Iterable}, the order of iteration + * being the order in which the {@link ServiceScheme}s where added to the said + * {@link ServiceSchemeRegistry}. + * + * @author bo 2012.05.02 + * @since Coherence 12.1.2 + */ +public class ServiceSchemeRegistry + implements Iterable + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link ServiceSchemeRegistry}. + */ + public ServiceSchemeRegistry() + { + m_mapServiceSchemesBySchemeName = new LinkedHashMap(); + } + + // ----- Iterable interface --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Iterator iterator() + { + return m_mapServiceSchemesBySchemeName.values().iterator(); + } + + // ----- SchemeRegistry methods ----------------------------------------- + + /** + * Attempts to register the specified {@link ServiceScheme}. + * + * @param scheme the {@link ServiceScheme} to register + * + * @throws IllegalArgumentException if a {@link ServiceScheme} with the same + * scheme and/or service name has already + * been registered + */ + public void register(ServiceScheme scheme) + { + if (scheme.isAnonymous()) + { + // ensure that there is no other scheme with the same service name + ServiceScheme schemeOther = findSchemeByServiceName(scheme.getServiceName()); + + if (schemeOther != null) + { + throw new IllegalArgumentException(String.format( + "Attempted to register an anonymous service scheme with a %s that is already defined.", + scheme.getSchemeName())); + } + else + { + // register the scheme with an anonymous name. no one will ever + // try to look this up, but it needs a scheme name in the registry + m_mapServiceSchemesBySchemeName.put("anonymous-" + scheme.getServiceName() + "-" + new UUID(), scheme); + } + } + else if (m_mapServiceSchemesBySchemeName.containsKey(scheme.getSchemeName())) + { + throw new IllegalArgumentException(String.format( + "Attempted to register or redefine a service scheme called %s that already exists with %s", + scheme.getSchemeName(), scheme.toString())); + } + else + { + m_mapServiceSchemesBySchemeName.put(scheme.getSchemeName(), scheme); + } + } + + /** + * Attempts to locate a {@link ServiceScheme} registered with the specified + * {@link ServiceScheme#getSchemeName()}. + * + * @param sSchemeName the scheme of the {@link ServiceScheme} to find + * + * @return the registered {@link ServiceScheme} or + * null if not registered + */ + public ServiceScheme findSchemeBySchemeName(String sSchemeName) + { + return m_mapServiceSchemesBySchemeName.get(sSchemeName); + } + + /** + * Attempts to locate a {@link ServiceScheme} registered with the specified + * {@link ServiceScheme#getServiceName()} giving preference to "autostart" + * schemes. + * + * @param sServiceName the service name of {@link ServiceScheme} to find + * + * @return the registered {@link ServiceScheme} or + * null if not registered + */ + public ServiceScheme findSchemeByServiceName(String sServiceName) + { + ServiceScheme schemeMatch = null; + for (ServiceScheme scheme : this) + { + if (scheme.getServiceName().equals(sServiceName)) + { + if (scheme.isAutoStart()) // see COH-15292 + { + return scheme; + } + schemeMatch = scheme; + } + } + + return schemeMatch; + } + + /** + * Determines the number of {@link Scheme}s registered with the + * {@link ServiceSchemeRegistry}. + * + * @return the number of {@link Scheme}s + */ + public int size() + { + return m_mapServiceSchemesBySchemeName.size(); + } + + // ----- data members --------------------------------------------------- + + /** + * The map of {@link ServiceScheme}s keyed by {@link ServiceScheme#getSchemeName()}. + */ + private LinkedHashMap m_mapServiceSchemesBySchemeName; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/SimpleParameterList.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/SimpleParameterList.java new file mode 100644 index 0000000000000..c9177c232f68f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/SimpleParameterList.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.config.expression.Parameter; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.ExternalizableHelper; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.Iterator; + +import javax.json.bind.annotation.JsonbProperty; + +/** + * A {@link SimpleParameterList} is a simple implementation of {@link ParameterList}. + * + * @author bo 2012.02.02 + * @since Coherence 12.1.2 + */ +public class SimpleParameterList + implements ParameterList, ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs an empty {@link SimpleParameterList}. + */ + public SimpleParameterList() + { + m_listParameters = new ArrayList(5); + } + + /** + * Constructs a {@link SimpleParameterList} based on the specified array of objects, + * each object becoming it's own {@link Parameter} in the resulting list. + * + * @param aObjects the objects to be considered as parameters + */ + public SimpleParameterList(Object... aObjects) + { + this(); + + if (aObjects != null) + { + for (Object o : aObjects) + { + add(o); + } + } + } + + /** + * Constructs a {@link SimpleParameterList} based on a {@link ParameterList}. + * + * @param listParameters the {@link ParameterList} from which {@link Parameter}s should be drawn + */ + public SimpleParameterList(ParameterList listParameters) + { + this(); + + for (Parameter parameter : listParameters) + { + add(parameter); + } + } + + // ----- ParameterList interface ---------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void add(Parameter parameter) + { + m_listParameters.add(parameter); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() + { + return m_listParameters.isEmpty(); + } + + /** + * {@inheritDoc} + */ + @Override + public int size() + { + return m_listParameters.size(); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterator iterator() + { + return m_listParameters.iterator(); + } + + // ----- SimpleParameterList methods ------------------------------------ + + /** + * Adds the specified object to the end of the {@link ParameterList} as an anonymous {@link Parameter}. + * + * @param o the object to add as a {@link Parameter} + */ + public void add(Object o) + { + if (o instanceof Parameter) + { + add((Parameter) o); + } + else + { + add(new Parameter("param-" + Integer.toString(size() + 1), o)); + } + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + ExternalizableHelper.readCollection(in, m_listParameters, null); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + ExternalizableHelper.writeCollection(out, m_listParameters); + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader reader) throws IOException + { + reader.readCollection(0, m_listParameters); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter writer) throws IOException + { + writer.writeCollection(0, m_listParameters); + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Parameter} list. + */ + @JsonbProperty("parameters") + private ArrayList m_listParameters; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/SystemPropertyResolver.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/SystemPropertyResolver.java new file mode 100644 index 0000000000000..cfe5ebf5b73b3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/SystemPropertyResolver.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + + +import java.util.Iterator; +import java.util.Optional; +import java.util.ServiceLoader; + + +/** + * An abstraction that allows us to make system property resolution + * customizable. + * + * @author as 2019.10.11 + */ +public interface SystemPropertyResolver + { + /** + * Return the value of the specified system property. + * + * @param sPropertyName the name of the property to return + * + * @return the value of the specified system property + */ + public String getProperty(String sPropertyName); + + /** + * Return the value of the specified system property, or the specified + * default value if the property doesn't exist. + * + * @param sPropertyName the name of the property to return + * @param sDefaultValue the default value to return if the property + * doesn't exist + * + * @return the value of the specified system property, or the specified + * default value if the property doesn't exist + */ + public default String getProperty(String sPropertyName, String sDefaultValue) + { + return Optional.ofNullable(getProperty(sPropertyName)).orElse(sDefaultValue); + } + + /** + * Return an instance of a {@link SystemPropertyResolver} discovered by + * the {@code ServiceLoader}, or a default instance if none are discovered. + * + * @return an instance of a {@code SystemPropertyResolver} + */ + public static SystemPropertyResolver getInstance() + { + ServiceLoader serviceLoader = + ServiceLoader.load(SystemPropertyResolver.class); + Iterator resolvers = serviceLoader.iterator(); + return resolvers.hasNext() ? resolvers.next() : new Default(); + } + + // ---- inner class: Default -------------------------------------------- + + /** + * Default {@link SystemPropertyResolver} implementation. + *

+ * This implementation simply delegates to {@link System#getProperty(String)}. + */ + class Default implements SystemPropertyResolver + { + @Override + public String getProperty(String sPropertyName) + { + return System.getProperty(sPropertyName); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/TopicMapping.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/TopicMapping.java new file mode 100644 index 0000000000000..563c745f87915 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/TopicMapping.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config; + +import com.tangosol.coherence.config.builder.SubscriberGroupBuilder; +import com.tangosol.coherence.config.scheme.Scheme; +import com.tangosol.coherence.config.scheme.TopicScheme; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.topic.NamedTopic; + +import com.tangosol.util.ClassHelper; + +import java.util.Collection; +import java.util.LinkedList; + +/** + * A {@link TopicMapping} captures configuration information for a pattern-match-based mapping from a proposed + * {@link NamedTopic} name to a topic scheme. + *

+ * In addition to the mapping between a topic name and a topic scheme, each {@link TopicMapping} retains a + * {@link ParameterResolver} (representing user-provided parameters) to be used during the realization of the said + * topic and scheme. (This allows individual mappings to be parameterized) + *

+ * Lastly {@link TopicMapping}s also provide a mechanism to associate specific strongly typed resources + * with each mapping at runtime. This provides a flexible and dynamic mechanism to associate further configuration + * information with topics. + *

+ * Pattern Matching Semantics: + * The only wildcard permitted for pattern matching with topic names is the "*" and it may only + * be used at the end of a topic name. + *

+ * For example, the following topic name patterns are valid: + * "*" and something-*, but *-something is invalid. + * + * @author jk 2015.05.28 + * @since Coherence 14.1.1 + */ +public class TopicMapping + extends ResourceMapping + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link TopicMapping} for topics that will use raw types by default. + * + * @param sTopicNamePattern the pattern that maps topic names to caching schemes + * @param sCachingSchemeName the name of the caching scheme to which topics matching this + * {@link TopicMapping} will be associated + */ + public TopicMapping(String sTopicNamePattern, String sCachingSchemeName, + Class clsScheme) + { + super(sTopicNamePattern, sCachingSchemeName); + + f_clsSchemeType = clsScheme; + m_sNameValueClass = null; + } + + // ----- ResourceMapping methods -------------------------------------------- + + @Override + public String getConfigElementName() + { + return "topic-name"; + } + + @Override + public void validateScheme(Scheme scheme) + { + if (f_clsSchemeType == null || f_clsSchemeType.isAssignableFrom(scheme.getClass())) + { + return; + } + + String sElement = getConfigElementName(); + String sPattern = getNamePattern(); + String sScheme = scheme.getSchemeName(); + String sMsg = String.format("Mapping <%s>%s maps to scheme %s, which is not a valid %s", + sScheme, sElement, sPattern, sElement, f_clsSchemeType.getSimpleName()); + + throw new IllegalStateException(sMsg); + } + + // ----- TopicMapping methods ------------------------------------------- + + /** + * Obtains the name of the value class for {@link NamedTopic}s using this {@link TopicMapping}. + * + * @return the name of the value class or null if rawtypes are being used + */ + public String getValueClassName() + { + return m_sNameValueClass; + } + + /** + * Sets the name of the value class for {@link NamedTopic}s using this {@link TopicMapping}. + * + * @param sElementClassName the name of the value class or null if rawtypes are being used + */ + @Injectable("value-type") + public void setValueClassName(String sElementClassName) + { + m_sNameValueClass = ClassHelper.getFullyQualifiedClassNameOf(sElementClassName); + } + + /** + * Determines if the {@link TopicMapping} is configured to use raw-types + * (ie: no type checking or constraints) + * + * @return true if using raw types, false otherwise + */ + public boolean usesRawTypes() + { + return m_sNameValueClass == null; + } + + /** + * Set the durable {@link SubscriberGroupBuilder}s for this {@link TopicMapping}. + * + * @param colBuilders collection of SubscriberGroupBuilders. + */ + @Injectable("subscriber-groups") + public void setSubscriberGroupBuilders(Collection colBuilders) + { + m_colSubscriberGroupBuilder.addAll(colBuilders); + } + + /** + * Get the durable {@link SubscriberGroupBuilder}s for this {@link TopicMapping}. + * + * @return collection of SubscriberGroupBuilder(s). + */ + public Collection getSubscriberGroupBuilders() + { + return m_colSubscriberGroupBuilder; + } + + // ----- data members --------------------------------------------------- + + /** + * The name of the value class or null if rawtypes are being used (the default). + */ + private String m_sNameValueClass; + + /** + * The type of scheme that this mapping should map to + */ + private final Class f_clsSchemeType; + + /** + * {@link Collection} of durable {@link SubscriberGroupBuilder}s associated with this {@link TopicMapping}. + */ + private Collection m_colSubscriberGroupBuilder = new LinkedList<>(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ActionPolicyBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ActionPolicyBuilder.java new file mode 100644 index 0000000000000..e941748c60fce --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ActionPolicyBuilder.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.ActionPolicy; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.NullImplementation; + +/** + * The abstract {@link ActionPolicyBuilder} is a base class builder for building {@link ActionPolicy}'s instances + * and defers cache configuration validation until the instance is realized. + * The xml content held by the builder is only used to construct an informative message for ConfigurationException. + *

+ * This class removes fail fast cache configuration and replaces it with lazy evaluation to be more + * like Coherence instantiation in 12.1.2 and prior. + * + * @author jf 2015.02.02 + * @since Coherence 12.2.1 + */ +public abstract class ActionPolicyBuilder + implements ParameterizedBuilder + { + + // ----- inner classes -------------------------------------------------- + + /** + * {@link ActionPolicyBuilder} wrapper for a ParameterizedBuilder. + */ + static public class ActionPolicyParameterizedBuilder + extends ActionPolicyBuilder + { + // ----- constructors ----------------------------------------------- + + /** + * Constructs {@link ActionPolicyParameterizedBuilder} + * + * @param bldr customized ActionPolicy ParameterizedBuilder. + */ + public ActionPolicyParameterizedBuilder(ParameterizedBuilder bldr) + { + m_builder = bldr; + } + + // ----- ParameterizedBuilder methods ------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ActionPolicy realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + throws ConfigurationException + { + return m_builder.realize(resolver, loader, listParameters); + } + + // ----- data members -------------------------------------------- + + /** + * underlying ParameterizedBuilder + */ + private ParameterizedBuilder m_builder; + } + + /** + * ActionPolicy Null Implementation + */ + static public class NullImplementationBuilder + extends ActionPolicyBuilder + { + // ----- ParameterizedBuilder methods ------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ActionPolicy realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + throws ConfigurationException + { + return NullImplementation.getActionPolicy(); + } + } + + /** + * Intermediate QuorumRule with enough information to report a ConfigurationException + * at instantiation time. + */ + public static class QuorumRule + { + // ----- Constructors ----------------------------------------------- + + /** + * Constructs {@link QuorumRule} + * + * @param sRuleElementName to report configuration exception context + * @param nRuleMask rule mask + * @param nThreshold threshold + * @param xmlElement optional to report configuration exception context + */ + public QuorumRule(String sRuleElementName, int nRuleMask, int nThreshold, XmlElement xmlElement) + { + this(sRuleElementName, nRuleMask, nThreshold, 0.0f, xmlElement); + } + + /** + * Constructs {@link QuorumRule} + * + * @param sRuleElementName to report configuration exception context + * @param nRuleMask rule mask + * @param nThreshold threshold + * @param flThresholdPct the threshold in percentage + * @param xmlElement optional to report configuration exception context + */ + public QuorumRule(String sRuleElementName, int nRuleMask, int nThreshold, float flThresholdPct, XmlElement xmlElement) + { + m_sRuleElementName = sRuleElementName; + m_nRuleMask = nRuleMask; + m_nThreshold = nThreshold; + m_flThresholdPct = flThresholdPct; + m_xmlElement = xmlElement; + } + + // ----- QuorumRule methods ----------------------------------------- + + /** + * Throw ConfigurationException if this {@link #m_xmlElement} configuration violates constraints. + * + * @throws ConfigurationException describing invalid configuration. + */ + public void validate() + throws ConfigurationException + { + if (m_nThreshold < 0 || m_flThresholdPct < 0.0f || m_flThresholdPct > 1.0f) + { + String sValue = m_nThreshold < 0 + ? String.valueOf(m_nThreshold) + : m_flThresholdPct * 100 + "%"; + + throw new ConfigurationException( + "Invalid value [" + sValue + "] for <" + m_sRuleElementName + + "> in [" + m_xmlElement + "]", "The <" + m_sRuleElementName + + "> must be non-negative and less than 100% if percentage is specified."); + } + } + + // ----- data members ----------------------------------------------- + + /** + * An optional xml configuration element to be used in ConfigurationException. + */ + protected XmlElement m_xmlElement; + + /** + * A rule element name to be used to construct ConfigurationException + * description if it throws at instantiation time. + */ + protected String m_sRuleElementName; + + /** + * Action policy rule mask. + */ + protected int m_nRuleMask; + + /** + * Action policy threshold which is always non-negative. + */ + protected int m_nThreshold; + + /** + * Action policy threshold in percentage. + */ + protected float m_flThresholdPct; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/AddressProviderBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/AddressProviderBuilder.java new file mode 100644 index 0000000000000..4d19ea9a0ba2d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/AddressProviderBuilder.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.net.AddressProvider; +import com.tangosol.net.AddressProviderFactory; + +/** + * AddressProviderBuilder interface + * + * @author pfm 2013.09.12 + * @since Coherence 12.1.3 + */ +public interface AddressProviderBuilder + extends ParameterizedBuilder, AddressProviderFactory + { + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/BackingMapManagerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/BackingMapManagerBuilder.java new file mode 100644 index 0000000000000..969dd46eea58b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/BackingMapManagerBuilder.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.net.BackingMapManager; +import com.tangosol.net.ConfigurableCacheFactory; + +/** + * A {@link BackingMapManagerBuilder} realizes {@link BackingMapManager}s. + * + * @author bo 2012.11.06 + * @since Coherence 12.1.2 + */ +public interface BackingMapManagerBuilder + { + /** + * Realize a {@link BackingMapManager} to be scoped by the specified + * {@link ConfigurableCacheFactory}. + * + * @param ccf the {@link ConfigurableCacheFactory} + * + * @return a {@link BackingMapManager} + */ + public BackingMapManager realizeBackingMapManager(ConfigurableCacheFactory ccf); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/BuilderCustomization.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/BuilderCustomization.java new file mode 100755 index 0000000000000..13076f5e0b2e8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/BuilderCustomization.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + + +/** + * A class that implements {@link BuilderCustomization} is one that allows an alternate + * builder, as a {@link ParameterizedBuilder}, to be provided so that the said class + * may use it for realizing objects. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public interface BuilderCustomization + { + /** + * Obtains the custom {@link ParameterizedBuilder}. + * + * @return the {@link ParameterizedBuilder} + */ + public ParameterizedBuilder getCustomBuilder(); + + /** + * Sets the {@link ParameterizedBuilder} to be used as the alternate builder. + * + * @param bldr the ParameterizedBuilder + */ + public void setCustomBuilder(ParameterizedBuilder bldr); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ClusterQuorumPolicyBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ClusterQuorumPolicyBuilder.java new file mode 100644 index 0000000000000..42f07515a4d08 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ClusterQuorumPolicyBuilder.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.ActionPolicy; +import com.tangosol.net.ConfigurableQuorumPolicy; + +import com.tangosol.run.xml.XmlElement; + +import java.util.Map; + +import static com.tangosol.net.ConfigurableQuorumPolicy.ClusterQuorumPolicy; + +/** + * Defer cache configuration validation of a {@link ClusterQuorumPolicyBuilder} until realized. + * + * @author jf 2015.02.04 + * @since Coherence 12.2.1 + */ +public class ClusterQuorumPolicyBuilder + extends ActionPolicyBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link ClusterQuorumPolicyBuilder} from mapQuorum + * + * @param mapQuorum map of role to quorum threshold + * @param xmlConfigElement optional configuration element cluster-quorum-policy used for reporting + * configuration exception context. + */ + public ClusterQuorumPolicyBuilder(Map mapQuorum, XmlElement xmlConfigElement) + { + m_mapQuorum = mapQuorum; + m_xmlConfig = xmlConfigElement; + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ActionPolicy realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + throws ConfigurationException + { + validate(); + return ConfigurableQuorumPolicy.instantiateClusterPolicy(m_mapQuorum); + } + + // ----- helpers -------------------------------------------------------- + + /** + * Validate cluster-quorum-configuration at instantiation time. + * + * @throws IllegalArgumentException if any value in m_mapQuorum map is negative. + */ + private void validate() + throws IllegalArgumentException + { + for (Map.Entry entry : m_mapQuorum.entrySet()) + { + if (entry.getValue() < 0) + { + throw new IllegalArgumentException("The cluster-quorum for role " + entry.getKey() + + " must be non-negative in configuration element <" + + m_xmlConfig + ">."); + } + + } + } + + // ----- data members --------------------------------------------------- + + /** + * Map of role and quorum. The role can be {@link ClusterQuorumPolicy#ROLE_ALL}. + */ + private Map m_mapQuorum; + + + /** + * Optional configuration element to assist in reporting ConfigurationException. + */ + private XmlElement m_xmlConfig; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/CustomAddressProviderBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/CustomAddressProviderBuilder.java new file mode 100644 index 0000000000000..87b6c80479cab --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/CustomAddressProviderBuilder.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.NullParameterResolver; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.AddressProvider; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.util.NullImplementation; + +/** + * This class builds an AddressProviderBuilder from a customized {@link ParameterizedBuilder} of + * {@link AddressProvider}. + * + * @author jf 2015.02.26 + * @since Coherence 12.2.1 + */ +public class CustomAddressProviderBuilder + implements AddressProviderBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link CustomAddressProviderBuilder} + * + * @param builder customized AddressProvider + */ + public CustomAddressProviderBuilder(ParameterizedBuilder builder) + { + this(builder, new NullParameterResolver(), null); + } + + /** + * Constructs {@link CustomAddressProviderBuilder} + * + * @param builder customized AddressProvider + * @param resolver optional resolver + */ + public CustomAddressProviderBuilder(ParameterizedBuilder builder, ParameterResolver resolver) + { + this(builder, resolver, null); + } + + /** + * Constructs {@link CustomAddressProviderBuilder} + * + * @param builder customized AddressProvider + * @param resolver resolver + * @param xmlConfig optional xmlConfig info to only be used in reporting + * {@link ConfigurationException}. + */ + public CustomAddressProviderBuilder(ParameterizedBuilder builder, ParameterResolver resolver, XmlElement xmlConfig) + { + m_builder = builder; + m_resolver = resolver; + m_xmlConfig = xmlConfig; + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * Realize the custom builder. + * + * @param resolver if non-null, use it. otherwise use resolver provided at construction time. + * @param loader classloader + * @param listParameters list of parameters. + */ + @Override + public AddressProvider realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + try + { + return m_builder == null ? NullImplementation.getAddressProvider() : m_builder.realize(resolver == null ? m_resolver : resolver, loader, listParameters); + } + catch (ClassCastException e) + { + StringBuilder sb = new StringBuilder(); + sb.append("invalid customized AddressProviderBuilder ").append(m_builder.getClass().getCanonicalName()); + if (m_xmlConfig != null) + { + sb.append(" configured in element <").append(m_xmlConfig).append(">"); + } + throw new ConfigurationException(sb.toString(), + "fix configuration to reference a class that returns an AddressProvider", e); + } + } + + // ----- AddressProviderFactory methods --------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public AddressProvider createAddressProvider(ClassLoader loader) + { + return realize(null, loader, null); + } + + // ----- data members --------------------------------------------------- + + /** + * custom builder + */ + private ParameterizedBuilder m_builder; + + /** + * resolver + */ + private ParameterResolver m_resolver; + + /** + * optional xml configuration info. only to be used to enhance + * configuration exception. + */ + private XmlElement m_xmlConfig = null; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/DefaultBuilderCustomization.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/DefaultBuilderCustomization.java new file mode 100644 index 0000000000000..8f3207d5e077f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/DefaultBuilderCustomization.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + + +/** + * The {@link DefaultBuilderCustomization} class is the default implementation + * of {@link BuilderCustomization}. + * + * @author pfm 2012.01.06 + * @since Coherence 12.1.2 + */ +public class DefaultBuilderCustomization + implements BuilderCustomization + { + // ----- BuilderCustomization interface --------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ParameterizedBuilder getCustomBuilder() + { + return m_bldr; + } + + /** + * {@inheritDoc} + */ + public void setCustomBuilder(ParameterizedBuilder bldr) + { + m_bldr = bldr; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link ParameterizedBuilder} used to build the custom instance. + */ + private ParameterizedBuilder m_bldr; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/EvictionPolicyBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/EvictionPolicyBuilder.java new file mode 100644 index 0000000000000..89c8d83be1766 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/EvictionPolicyBuilder.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.builder.ParameterizedBuilder.ReflectionSupport; + +import com.tangosol.config.annotation.Injectable; + +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.ParameterResolver; + + +import com.tangosol.net.cache.ConfigurableCacheMap.EvictionPolicy; +import com.tangosol.net.cache.LocalCache; + + +/** + * The {@link EvictionPolicyBuilder} builds a {@link EvictionPolicy}. + * + * @author pfm 2012.01.07 + * @since Coherence 12.1.2 + */ +public class EvictionPolicyBuilder + extends DefaultBuilderCustomization + implements ParameterizedBuilder, ReflectionSupport + { + // ----- EvictionPolicyBuilder methods --------------------------------- + + /** + * Obtains the EvictionPolicy type. + * + * @param resolver the {@link ParameterResolver} + * + * @return the type of EvictionPolicy + */ + public String getEvictionType(ParameterResolver resolver) + { + return m_exprPolicy.evaluate(resolver); + } + + /** + * Set the EvictionPolicy type. + * + * @param expr the EvictionPolicy type + */ + @Injectable + public void setEvictionType(Expression expr) + { + m_exprPolicy = expr; + } + + // ----- ParameterizedBuilder methods ---------------------------------- + + /** + * {@inheritDoc} + */ + public boolean realizes(Class clzClass, ParameterResolver resolver, ClassLoader loader) + { + return getClass().isAssignableFrom(clzClass); + } + + /** + * {@inheritDoc} + */ + public EvictionPolicy realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + EvictionPolicy policy; + ParameterizedBuilder bldr = getCustomBuilder(); + + if (bldr == null) + { + // use a Coherence built-in EvictionPolicy + String sType = getEvictionType(resolver).toUpperCase(); + + switch (sType) + { + case "HYBRID": + policy = LocalCache.INSTANCE_HYBRID; + break; + case "LRU": + policy = LocalCache.INSTANCE_LRU; + break; + case "LFU": + policy = LocalCache.INSTANCE_LFU; + break; + default: + throw new IllegalArgumentException( + "Error: the value " + sType + + "is invalid"); + } + } + else + { + // create a custom Eviction + policy = bldr.realize(resolver, loader, listParameters); + } + + return policy; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link EvictionPolicy}. + */ + private Expression m_exprPolicy = new LiteralExpression("HYBRID"); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/FactoryBasedAddressProviderBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/FactoryBasedAddressProviderBuilder.java new file mode 100644 index 0000000000000..c7d113fca2fea --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/FactoryBasedAddressProviderBuilder.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.AddressProvider; +import com.tangosol.net.AddressProviderFactory; +import com.tangosol.util.NullImplementation; + +/** + * This class builds an AddressProviderBuilder from a AddressProviderFactory. + * + * @author jf 2015.02.26 + * @since Coherence 12.2.1 + */ +public class FactoryBasedAddressProviderBuilder + implements AddressProviderBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link FactoryBasedAddressProviderBuilder} + * + * + * @param factory wrapped factory + */ + public FactoryBasedAddressProviderBuilder(AddressProviderFactory factory) + { + m_factory = factory; + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public AddressProvider realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + if (m_eDeferred != null) + { + throw m_eDeferred; + } + try + { + return m_factory == null ? NullImplementation.getAddressProvider() : m_factory.createAddressProvider(loader); + } + catch (ClassCastException e) + { + throw new ConfigurationException("AddressProviderFactory must return a AddressProvider", + "fix configuration so the referenced class implements AddressProviderFactory", e); + } + } + + // ----- AddressProviderFactory methods --------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public AddressProvider createAddressProvider(ClassLoader loader) + { + return realize(null, loader, null); + } + + // ----- AddressProviderFactoryBuilder methods -------------------------- + + /** + * Defer reporting ConfigurationException until realized. + * + * @param e ConfigurationException to throw when instantiated. + */ + public AddressProviderBuilder setDeferredException(ConfigurationException e) + { + m_eDeferred = e; + return this; + } + + // ----- data members --------------------------------------------------- + + /** + * wrapped factory + */ + private AddressProviderFactory m_factory = null; + + /** + * defer configuration exception until realized. + */ + private ConfigurationException m_eDeferred = null; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/InetAddressRangeFilterBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/InetAddressRangeFilterBuilder.java new file mode 100644 index 0000000000000..3f344f18e7119 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/InetAddressRangeFilterBuilder.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.net.InetAddressRangeFilter; + +import com.tangosol.util.Base; +import com.tangosol.util.Filter; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * InetAddressRangeFilterBuilder defers evaluating configuration parameters + * until Filter is instantiated. + * + * @author jf 2015.02.10 + * @since Coherence 12.2.1 + */ +public class InetAddressRangeFilterBuilder + implements ParameterizedBuilder + { + // ----- Constructors --------------------------------------------------- + + /** + * Construct a {@link InetAddressRangeFilterBuilder} + */ + public InetAddressRangeFilterBuilder() + { + m_filter = new InetAddressRangeFilter(); + m_fFilterAdded = false; + m_exceptionDeferred = null; + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Filter realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + if (m_exceptionDeferred != null) + { + throw m_exceptionDeferred; + } + + return m_fFilterAdded ? m_filter : null; + } + + // ----- InetAddressRangeFilterBuilder methods -------------------------- + + /** + * Add an authorized host range to the filter. + * + * @param sAddrFrom from address + * @param sAddrTo to address + */ + public void addAuthorizedHostsToFilter(String sAddrFrom, String sAddrTo) + { + if (sAddrFrom == null || sAddrFrom.length() == 0) + { + if (sAddrTo != null && sAddrTo.length() != 0) + { + m_exceptionDeferred = + new IllegalStateException("Error adding host filter. Both and elements must be specified"); + } + + return; + } + + InetAddress addrFrom; + InetAddress addrTo; + + try + { + addrFrom = InetAddress.getByName(sAddrFrom); + addrTo = sAddrTo == null ? addrFrom : InetAddress.getByName(sAddrTo); + } + catch (UnknownHostException e) + { + Base.trace("Unresolvable authorized host will be ignored: " + e); + + return; + } + + m_filter.addRange(addrFrom, addrTo); + m_fFilterAdded = true; + + return; + } + + // ----- data members --------------------------------------------------- + + /** + * {@link Filter} being built by this builder. + */ + private InetAddressRangeFilter m_filter; + + /** + * Have any address ranges been successfully added to filter. + */ + private boolean m_fFilterAdded; + + /** + * Deferred configuration exception thrown only when realized. + */ + private IllegalStateException m_exceptionDeferred; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/InstanceBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/InstanceBuilder.java new file mode 100644 index 0000000000000..9aaf7de3f697e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/InstanceBuilder.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.SimpleParameterList; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.Base; +import com.tangosol.util.ExternalizableHelper; + +import static com.tangosol.coherence.config.builder.ParameterizedBuilderHelper.getAssignableValue; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import java.lang.reflect.Constructor; + +import javax.json.bind.annotation.JsonbProperty; + +/** + * An {@link InstanceBuilder} is a {@link ParameterizedBuilder} implementation that additionally supports injection + * based on Coherence <instance%gt; or <class-scheme> configurations. + *

+ * While supporting injection this class may also be used in situations where a {@link ParameterizedBuilder} is + * required (must be passed) for a known type of the class. + *

+ * For example, if you need a {@link ParameterizedBuilder} for a java.awt.Point class, but you don't + * want to create an anonymous {@link ParameterizedBuilder} implementation for a Point, you can use the following: + *

+ * new InstanceBuilder(Point.class); + *

+ * Further you may also provide constructor parameters as follows: + *

+ * new InstanceBuilder(Point.class, 10, 12); + * + * @author bo 2011.06.24 + * @since Coherence 12.1.2 + */ +public class InstanceBuilder + implements ParameterizedBuilder, ParameterizedBuilder.ReflectionSupport, + ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an {@link InstanceBuilder}. + */ + public InstanceBuilder() + { + m_exprClassName = new LiteralExpression("undefined"); + m_listConstructorParameters = new ResolvableParameterList(); + } + + /** + * Constructs a {@link InstanceBuilder} that will realize an instance of the specified {@link Class}. + * + * @param clzToRealize the {@link Class} to realize + * @param aConstructorParameters the optional constructor parameters + */ + public InstanceBuilder(Class clzToRealize, Object... aConstructorParameters) + { + m_exprClassName = new LiteralExpression(clzToRealize.getName()); + m_listConstructorParameters = new SimpleParameterList(aConstructorParameters); + } + + /** + * Constructs a {@link InstanceBuilder} that will realize an instance of the specifically named class. + * + * @param exprClassName an {@link Expression} that when evaluated will return the class name to realize + * @param aConstructorParameters the optional constructor parameters + */ + public InstanceBuilder(Expression exprClassName, Object... aConstructorParameters) + { + m_exprClassName = exprClassName; + m_listConstructorParameters = new SimpleParameterList(aConstructorParameters); + } + + /** + * Constructs a {@link InstanceBuilder} that will realize an instance of the specifically named class. + * + * @param sClassName the name of the class to realize + * @param aConstructorParameters the optional constructor parameters + */ + public InstanceBuilder(String sClassName, Object... aConstructorParameters) + { + m_exprClassName = new LiteralExpression(sClassName); + m_listConstructorParameters = new SimpleParameterList(aConstructorParameters); + } + + // ----- InstanceBuilder methods ---------------------------------------- + + /** + * Return the expression representing the name of the class this builder + * will attempt to instantiate. + * + * @return an expression representing the class name + */ + public Expression getClassName() + { + return m_exprClassName; + } + + /** + * Sets the {@link Expression} that when evaluated will produce the name of the class to realize. + * + * @param exprClassName the {@link Expression} + */ + @Injectable("class-name") + public void setClassName(Expression exprClassName) + { + m_exprClassName = exprClassName; + } + + /** + * Return the {@link ParameterList} to be used for constructor parameters + * when realizing the class. + * + * @return the {@link ParameterList} for the constructor + */ + public ParameterList getConstructorParameterList() + { + return m_listConstructorParameters; + } + + /** + * Sets the {@link ParameterList} to be used for constructor parameters when realizing the class. + * + * @param listParameters the {@link ParameterList} for the constructor + */ + @Injectable("init-params") + public void setConstructorParameterList(ParameterList listParameters) + { + m_listConstructorParameters = listParameters; + } + + // ----- ParameterizedBuilder Interface --------------------------------- + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public T realize(ParameterResolver resolver, ClassLoader loader, ParameterList listConstructorParameters) + { + try + { + // ensure we have a classloader + loader = Base.ensureClassLoader(loader); + + // resolve the actual class name + String sClassName = m_exprClassName.evaluate(resolver); + + // attempt to load the class + Class clzClass = loader.loadClass(sClassName); + + // determine which list of parameters to use + ParameterList listParameters = listConstructorParameters == null + ? m_listConstructorParameters : listConstructorParameters; + + // find a constructor that is compatible with the constructor parameters + Constructor[] aConstructors = clzClass.getConstructors(); + int cConstructors = aConstructors.length; + int cConstructorParameters = listParameters.size(); + Object[] aConstructorParameters = cConstructorParameters == 0 ? null : new Object[cConstructorParameters]; + + // assume a compatible constructor is not found + Constructor constructor = null; + + for (int i = 0; i < cConstructors && constructor == null; i++) + { + if (aConstructors[i].getParameterTypes().length == cConstructorParameters) + { + // ensure that the constructor parameter types are compatible + try + { + Class[] aParameterTypes = aConstructors[i].getParameterTypes(); + int j = 0; + + for (Parameter parameter : listParameters) + { + aConstructorParameters[j] = getAssignableValue(aParameterTypes[j], parameter, resolver, + loader); + j++; + } + + constructor = aConstructors[i]; + } + + catch (Exception e) + { + // an exception means the constructor is not compatible + } + } + } + + // did we find a compatible constructor? + if (constructor == null) + { + throw new NoSuchMethodException(String.format( + "Unable to find a compatible constructor for [%s] with the parameters [%s]", sClassName, + listParameters)); + } + else + { + return (T)constructor.newInstance(aConstructorParameters); + } + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e); + } + } + + // ----- ParameterizedBuilder.ReflectionSupport interface --------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean realizes(Class clzClass, ParameterResolver resolver, ClassLoader loader) + { + try + { + // ensure we have a classloader + loader = Base.ensureClassLoader(loader); + + // attempt to load the class name, but don't initialize it + Class clz = loader.loadClass(m_exprClassName.evaluate(resolver)); + + // ensure the class is assignment compatible with the requested class + return clzClass.isAssignableFrom(clz); + } + catch (ClassNotFoundException e) + { + return false; + } + } + + // ----- object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return "InstanceBuilder{" + + "className=" + m_exprClassName + ", " + + "constructorParameters=" + m_listConstructorParameters + + '}'; + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + m_exprClassName = (Expression) ExternalizableHelper.readObject(in, null); + m_listConstructorParameters = (ParameterList) ExternalizableHelper.readObject(in, null); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + ExternalizableHelper.writeObject(out, m_exprClassName); + ExternalizableHelper.writeObject(out, m_listConstructorParameters); + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader reader) throws IOException + { + m_exprClassName = (Expression) reader.readObject(0); + m_listConstructorParameters = (ParameterList) reader.readObject(1); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter writer) throws IOException + { + writer.writeObject(0, m_exprClassName); + writer.writeObject(1, m_listConstructorParameters); + } + + // ----- data members --------------------------------------------------- + + /** + * An expression that when evaluated will return the name of the class to realize. + */ + @JsonbProperty("exprClassName") + private Expression m_exprClassName; + + /** + * The {@link ParameterList} containing the constructor {@link Parameter}s for realizing the class. + */ + @JsonbProperty("constructorParams") + private ParameterList m_listConstructorParameters; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/InvalidConfigServiceLoadBalancerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/InvalidConfigServiceLoadBalancerBuilder.java new file mode 100644 index 0000000000000..63c4d25af1277 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/InvalidConfigServiceLoadBalancerBuilder.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.ServiceLoadBalancer; + +import com.tangosol.run.xml.XmlElement; + +/** + * {@link InvalidConfigServiceLoadBalancerBuilder} defers reporting + * configuration exception until realized. Enables non-autostarted + * services to be improperly configured and configuration error reporting + * performed only if containing service is started. + * + * @author jf 2015.02.10 + * @since Coherence 12.2.1 + */ +public class InvalidConfigServiceLoadBalancerBuilder + extends ServiceLoadBalancerBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link InvalidConfigServiceLoadBalancerBuilder}. Defer reporting + * configuration exception until instantiated. + * + * @param sServiceKind invalid load-balancer default value or used on invalid scheme + * @param xmlConfig xml config element + */ + public InvalidConfigServiceLoadBalancerBuilder(String sServiceKind, XmlElement xmlConfig) + { + super(null, xmlConfig); + + StringBuilder msg = new StringBuilder(); + + msg.append("Unknown load balancer [").append(sServiceKind).append("]"); + + if (xmlConfig != null) + { + msg.append(" specified in xml element [").append(xmlConfig).append("]."); + } + + m_eDeferred = new ConfigurationException(msg.toString(), "Please specify a known load balancer"); + } + + // ----- ServiceLoadBalancerBuilder methods ----------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ServiceLoadBalancer getDefaultLoadBalancer() + { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceLoadBalancer realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + throw m_eDeferred; + } + + // ----- data members --------------------------------------------------- + + /** + * Only non-null when configuration processing results in an exception. + * Defer throwing ConfigurationException until instance is realized. + */ + private final ConfigurationException m_eDeferred; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ListBasedAddressProviderBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ListBasedAddressProviderBuilder.java new file mode 100644 index 0000000000000..6f80e8cada0fc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ListBasedAddressProviderBuilder.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.AddressProvider; +import com.tangosol.net.ConfigurableAddressProvider; +import com.tangosol.net.InetAddressHelper; +import com.tangosol.net.RefreshableAddressProvider; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class builds an AddressProviderBuilder from a list of address and port. + * + * @author jf 2015.02.26 + * @since Coherence 12.2.1 + */ +public class ListBasedAddressProviderBuilder + implements AddressProviderBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link ListBasedAddressProviderBuilder} + */ + public ListBasedAddressProviderBuilder() + { + m_listAddress = new ArrayList<>(); + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public AddressProvider realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + validate(); + ConfigurableAddressProvider provider = new ConfigurableAddressProvider(m_listAddress, true); + + return m_fRefreshable ? new RefreshableAddressProvider(provider) : provider; + } + + // ----- AddressProvideBuilder methods ---------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public AddressProvider createAddressProvider(ClassLoader loader) + { + return realize(null, loader, null); + } + + // ----- AddressListProviderBuilder methods ----------------------------- + + /** + * Add an address and port. + * + * @param sAddr either an ip address or a host name + * @param nPort a non-negative port + */ + public ListBasedAddressProviderBuilder add(String sAddr, int nPort) + { + m_fRefreshable |= (sAddr != null && InetAddressHelper.isHostName(sAddr)); + m_listAddress.add(new ConfigurableAddressProvider.AddressHolder(sAddr, nPort)); + + return this; + } + + /** + * Returns true if any of the added addresses has been computed to be a hostname. + *

+ * Introduced for unit testing. + * + * @return true iff the realized AddressProvider will refresh its list. + */ + public boolean isRefreshable() + { + return m_fRefreshable; + } + + // ----- helpers -------------------------------------------------------- + + private void validate() + { + for (ConfigurableAddressProvider.AddressHolder holder : m_listAddress) + { + try + { + holder.validate(); + } + catch (Exception e) + { + throw new ConfigurationException("invalid address in AddressListProviderBuilder", "fix constraint violation", e); + } + } + } + + // ----- data members --------------------------------------------------- + + /** + * list of addresses. + */ + private List m_listAddress; + + /** + * Computed value that is true if one of the address' added to the builder + * is a host name. + */ + private boolean m_fRefreshable = false; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/LocalAddressProviderBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/LocalAddressProviderBuilder.java new file mode 100644 index 0000000000000..fef60b61c193e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/LocalAddressProviderBuilder.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.oracle.coherence.common.net.InetAddresses; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.AddressProvider; + +import com.tangosol.run.xml.XmlElement; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; + +/** + * This class builds an AddressProviderBuilder from a local address. + * All ConfigurationExceptions are deferred until realization time. + * + * @author jf 2015.02.26 + * @since Coherence 12.2.1 + */ +public class LocalAddressProviderBuilder + implements AddressProviderBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Helper constructor for all public constructors to use + * @param addr resolved {@link InetAddress} + * @param sAddr IP address or hostname + * @param nPortMin minimum port value, or -1 to use ephemeral sub-ports + * @param nPortMax maximum port value + * @param xmlConfig optional xml config + */ + private LocalAddressProviderBuilder(final InetAddress addr, final String sAddr, + final int nPortMin, final int nPortMax, final XmlElement xmlConfig) + { + assert(m_addr == null || m_sAddr == null); + m_addr = addr; + m_sAddr = sAddr; + m_nPortMin = nPortMin; + m_nPortMinOriginal = nPortMin; + m_nPortMax = nPortMax; + m_xmlConfig = xmlConfig; + } + + /** + * Constructs {@link LocalAddressProviderBuilder} with a resolved address. + * + * @param addr the local address, or null + * @param nPortMin the minimum port to use, or -1 to use ephemeral sub-ports + * @param nPortMax the maximum port to use + */ + public LocalAddressProviderBuilder(final InetAddress addr, final int nPortMin, final int nPortMax) + { + this(addr, null, nPortMin, nPortMax, null); + } + + /** + * Constructs {@link LocalAddressProviderBuilder} deferring address resolution until realized. + * + * @param sAddr the local address, or null + * @param nPortMin the minimum port to use, or -1 to use ephemeral sub-ports + * @param nPortMax the maximum port to use + * @param xmlConfig optional xml to used in ConfigurationException if this is invalid. + */ + public LocalAddressProviderBuilder(final String sAddr, final int nPortMin, final int nPortMax, XmlElement xmlConfig) + { + this(null, sAddr, nPortMin, nPortMax, xmlConfig); + } + + /** + * Constructs {@link LocalAddressProviderBuilder} deferring address resolution until realized. + * + * @param sAddr the local address, or null + * @param nPortMin the minimum port to use, or -1 to use ephemeral sub-ports + * @param nPortMax the maximum port to use + */ + public LocalAddressProviderBuilder(final String sAddr, final int nPortMin, final int nPortMax) + { + this(sAddr, nPortMin, nPortMax, null); + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public AddressProvider realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + resolveAddress(); + + validate(); + + return new AddressProvider() + { + @Override + public InetSocketAddress getNextAddress() + { + if (m_nPort > m_nPortMax) + { + m_nPort = m_nPortMin; + + return null; + } + + return new InetSocketAddress(m_addr, m_nPort++); + } + + @Override + public void accept() + { + m_nPort = m_nPortMin; + } + + @Override + public void reject(Throwable eCause) + { + // no-op + } + + @Override + public String toString() + { + return "LocalAddressProvider[" + m_addr + ":" + m_nPortMin + + (m_nPortMin == m_nPortMax ? "]" : " .. " + m_nPortMax + "]"); + } + + int m_nPort = m_nPortMin; + }; + } + + // ----- AddressProviderFactory methods --------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public AddressProvider createAddressProvider(ClassLoader loader) + { + return realize(null, loader, null); + } + + // ----- LocalAddressProviderBuilder methods ---------------------------- + + /** + * Resolve the host address if provided as a string and cache the value. + *

+ * May return null. + * + * @return the resolved host address + */ + public InetAddress resolveAddress() + { + if (m_addr == null && m_sAddr != null && !m_sAddr.isEmpty()) + { + try + { + m_addr = InetAddresses.getLocalAddress(m_sAddr); + } + catch (UnknownHostException e) + { + throw new IllegalArgumentException(e); + } + } + return m_addr; + } + + /** + * Set the address to use for any realized {@link AddressProvider}'s + * + * @param addr the {@link InetAddress} + * + * @return this {@link LocalAddressProviderBuilder} + */ + public LocalAddressProviderBuilder setAddress(InetAddress addr) + { + m_addr = addr; + return this; + } + + /** + * Return the minimum port number. + * + * @return the minimum port number + */ + public int getPortMin() + { + return m_nPortMin; + } + + /** + * Set the minimum port number. + * + * @param nPort the minimum port number + * + * @return this {@link LocalAddressProviderBuilder} + */ + public LocalAddressProviderBuilder setPortMin(int nPort) + { + m_nPortMin = nPort; + return this; + } + + /** + * Return the minimum port number provided in the constructor. + * + * @return the minimum port number + */ + public int getPortMinOriginal() + { + return m_nPortMinOriginal; + } + + /** + * Set the maximum port number. + * + * @param nPort the maximum port number + * + * @return this {@link LocalAddressProviderBuilder} + */ + public LocalAddressProviderBuilder setPortMax(int nPort) + { + m_nPortMax = nPort; + return this; + } + + // ----- helpers -------------------------------------------------------- + + /** + * Validate builder values. + */ + private void validate() + { + if (m_nPortMin < MIN_PORT || m_nPortMin > MAX_PORT) + { + throw new ConfigurationException("Invalid configuration [" + m_xmlConfig + "]", + "Please specify a valid " + ", invalid value is " + m_nPortMin); + } + else if (m_nPortMax < m_nPortMin || m_nPortMax > MAX_PORT) + { + throw new ConfigurationException("Invalid configuration [" + m_xmlConfig + "]", + "Please specify a valid , port-auto-adjust is " + + m_nPortMax + " and port value is " + m_nPortMin); + } + } + + // ----- constants ------------------------------------------------------ + + public static final int MIN_PORT = 0; + + public static final int MAX_PORT = 0xFFFF; + + // ----- data members --------------------------------------------------- + + /** + * The address in string format or unresolved hostname. + */ + private String m_sAddr; + + /** + * The resolved address. + */ + private InetAddress m_addr; + + /** + * The maximum port number. + */ + private int m_nPortMax; + + /** + * The minimum port number. + */ + private int m_nPortMin; + + /** + * The minimum port number as specified in the constructor. + */ + private int m_nPortMinOriginal; + + /** + * An optional XML configuration info used only to report configuration exceptions. + */ + private XmlElement m_xmlConfig = null; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/MapBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/MapBuilder.java new file mode 100644 index 0000000000000..cc0d007b301d6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/MapBuilder.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.BackingMapManagerContext; +import com.tangosol.net.CacheService; +import com.tangosol.net.ConfigurableCacheFactory; + +import com.tangosol.util.Base; +import com.tangosol.util.MapListener; + +import java.util.Map; + +/** + * The {@link MapBuilder} interface is used by a builder to create an instance + * of a {@link Map} that is a local to a Java process. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public interface MapBuilder + { + /** + * Realize a {@link Map} based on the state of the {@link MapBuilder}, + * resolvable parameters and provided {@link Dependencies}. + * + * @param resolver the {@link ParameterResolver} + * @param dependencies the {@link Dependencies} for realizing the {@link Map} + * + * @return a {@link Map} + */ + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies); + + /** + * The commonly required {@link Dependencies} for realizing a {@link Map} + * with a {@link MapBuilder}. + */ + public static class Dependencies + { + // ----- constructors ---------------------------------------------- + + /** + * Constructs a {@link Dependencies}. + *

+ * Note: In some circumstances the properties encapsulated by a {@link Dependencies} + * may not be available or not required. In these cases the properties + * will be a default value or null. + * + * @param ccf the {@link ConfigurableCacheFactory} + * @param ctxBackingMapManager the BackingMapManagerContext + * @param loader the {@link ClassLoader} + * @param sCacheName the cache name + * @param sServiceType the service type + */ + public Dependencies(ConfigurableCacheFactory ccf, BackingMapManagerContext ctxBackingMapManager, + ClassLoader loader, String sCacheName, String sServiceType) + { + this(ccf, ctxBackingMapManager, loader, sCacheName, sServiceType, null); + } + + /** + * Constructs a {@link Dependencies}. + *

+ * Note: In some circumstances the properties encapsulated by a {@link Dependencies} + * may not be available or not required. In these cases the properties + * will be a default value or null. + * + * @param ccf the {@link ConfigurableCacheFactory} + * @param ctxBackingMapManager the BackingMapManagerContext + * @param loader the {@link ClassLoader} + * @param sCacheName the cache name + * @param sServiceType the service type + */ + public Dependencies(ConfigurableCacheFactory ccf, BackingMapManagerContext ctxBackingMapManager, + ClassLoader loader, String sCacheName, String sServiceType, + Map mapMapListeners) + { + m_ccf = ccf; + m_ctxBackingMapManager = ctxBackingMapManager; + m_contextClassLoader = loader == null ? Base.getContextClassLoader() : loader; + m_sCacheName = sCacheName; + m_sServiceType = sServiceType; + m_fBinaryMap = ctxBackingMapManager != null && + CacheService.TYPE_DISTRIBUTED.equals(sServiceType); + m_mapMapListeners = mapMapListeners; + } + + // ----- Dependencies methods -------------------------------------- + + /** + * Return true if the map is binary. + * + * @return true if the map is binary + */ + public boolean isBinary() + { + return m_fBinaryMap; + } + + /** + * Return true if the map is a backup map. + * + * @return true if the map is a backup map + */ + public boolean isBackup() + { + return m_fBackup; + } + + /** + * Set the flag indicating that the map is a backup map. + * + * @param fBackup true if the map is a backup map + */ + public void setBackup(boolean fBackup) + { + m_fBackup = fBackup; + } + + /** + * Return true if the map is in blind mode. + * + * @return true if the map is in blind mode + */ + public boolean isBlind() + { + return m_fBlind; + } + + /** + * Set the flag indicating that the map is in blind mode. + * + * @param fBlind true if the map is in blind mode. + */ + public void setBlind(boolean fBlind) + { + m_fBlind = fBlind; + } + + /** + * Return the {@link BackingMapManagerContext}. + * + * @return the BackingMapManagerContext + */ + public BackingMapManagerContext getBackingMapManagerContext() + { + return m_ctxBackingMapManager; + } + + /** + * Return the cache name. + * + * @return the cache name + */ + public String getCacheName() + { + return m_sCacheName; + } + + /** + * Return the {@link ConfigurableCacheFactory} needed to create nested caches. + * + * @return the ConfigurableCacheFactory + */ + public ConfigurableCacheFactory getConfigurableCacheFactory() + { + return m_ccf; + } + + /** + * Returns the {@link ClassLoader} to use in the context of + * realizing {@link Map}s and other associated infrastructure. + * + * @return the {@link ClassLoader} + */ + public ClassLoader getClassLoader() + { + return m_contextClassLoader; + } + + /** + * Return the Service type. + * + * @return the Service type + */ + public String getServiceType() + { + return m_sServiceType; + } + + /** + * Obtains the registry of {@link MapListener}s, which is a {@link Map} + * keyed by {@link Map} to their associated {@link MapListener}. + * + * @return the {@link Map} of {@link Map}s to {@link MapListener}s + */ + public Map getMapListenersRegistry() + { + return m_mapMapListeners; + } + + // ----- data members ---------------------------------------------- + + /** + * The flag that indicates that the map is used as a backup. + */ + private boolean m_fBackup; + + /** + * The flag that indicates that the map is binary. + */ + private boolean m_fBinaryMap; + + /** + * A flag to indicate the constructed map should operate in blind mode. + */ + private boolean m_fBlind; + + /** + * The {@link ConfigurableCacheFactory} in which the realized {@link Map} + * will exist. + */ + private ConfigurableCacheFactory m_ccf; + + /** + * The {@link BackingMapManagerContext}. + */ + private BackingMapManagerContext m_ctxBackingMapManager; + + /** + * The cache name. + */ + private String m_sCacheName; + + /** + * The {@link ClassLoader}. + */ + private ClassLoader m_contextClassLoader; + + /** + * The Service type. + */ + private String m_sServiceType; + + /** + * A registry of {@link MapListener}s keyed by the {@link Map} to which + * they are attached. + */ + Map m_mapMapListeners; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/NamedCacheBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/NamedCacheBuilder.java new file mode 100644 index 0000000000000..638f5b6d2ead1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/NamedCacheBuilder.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.NamedCache; + +import java.util.Map; + +/** + * A {@link NamedCacheBuilder} realizes {@link NamedCache}s. + * + * @author pfm 2011.12.27 + * @since Coherence 12.1.2 + */ +public interface NamedCacheBuilder + { + /** + * Realizes a {@link NamedCache} (possibly "ensuring it") based on the state + * of the builder, the provided {@link ParameterResolver} and {@link MapBuilder} + * dependencies. + *

+ * The {@link MapBuilder} dependencies are required to satisfy the requirement + * when realizing a {@link NamedCache} additionally involves realizing one + * or more internal {@link Map}s. + * + * @param resolver the ParameterResolver + * @param dependencies the {@link MapBuilder} dependencies + * + * @return a {@link NamedCache} + */ + public NamedCache realizeCache(ParameterResolver resolver, MapBuilder.Dependencies dependencies); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/NamedCollectionBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/NamedCollectionBuilder.java new file mode 100644 index 0000000000000..8537bd735aece --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/NamedCollectionBuilder.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.NamedCollection; +import com.tangosol.net.ValueTypeAssertion; + +import java.util.Map; + +/** + * A {@link NamedCollectionBuilder} realizes {@link NamedCollection}s. + * + * @author jk 2015.06.27 + * @since Coherence 14.1.1 + */ +public interface NamedCollectionBuilder + { + /** + * Realizes a {@link NamedCollection} (possibly "ensuring it") based on the state + * of the builder, the provided {@link ParameterResolver} and {@link MapBuilder} + * dependencies. + *

+ * The {@link MapBuilder} dependencies are required to satisfy the requirement + * when realizing a {@link NamedCollection} additionally involves realizing one + * or more internal {@link Map}s. + * + * @param typeConstraint type constraint assertion for elements of this {@link NamedCollection} + * @param resolver the ParameterResolver + * @param dependencies the {@link MapBuilder} dependencies + * + * @param the element type of {@link NamedCollection} + * + * @return a {@link NamedCollection} + */ + public C realize( + ValueTypeAssertion typeConstraint, + ParameterResolver resolver, MapBuilder.Dependencies dependencies); + + /** + * Determines whether this {@link NamedCollectionBuilder} can realize a + * {@link NamedCollection} of the specified type. + * + * @param type the {@link Class} of the type to verify + * @param the type of the class to verify + * + * @return true if this builder can realize a {@link NamedCollection} of the + * specified type. + */ + public boolean realizes(Class type); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/NamedEventInterceptorBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/NamedEventInterceptorBuilder.java new file mode 100644 index 0000000000000..f2cb7127ce2ae --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/NamedEventInterceptorBuilder.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.events.EventInterceptor; +import com.tangosol.net.events.annotation.Interceptor; +import com.tangosol.net.events.annotation.Interceptor.Order; +import com.tangosol.net.events.internal.NamedEventInterceptor; + +import com.tangosol.util.RegistrationBehavior; + +/** + * An NamedEventInterceptorBuilder facilitates the construction of a + * {@link NamedEventInterceptor}, which wraps an {@link EventInterceptor}. The + * wrapped EventInterceptor will be constructed based on its instance / + * class-scheme XML declaration, honoring any init-params + * defined. + *

+ * The construction of a NamedEventInterceptor allows metadata associated + * with the EventInterceptor to be held. This metadata is used to determine + * eligibility against {@link com.tangosol.net.events.EventDispatcher}s and + * for registration purposes. + *

+ * In addition to XML being used as input in defining an EventInterceptor and + * its metadata the presence of an annotation ({@link Interceptor}) can also + * contribute. This annotation can define the identifier for this interceptor, + * the event types to subscribe to and whether to be placed first in the chain + * of EventInterceptors ({@link Order#HIGH}. + *

+ * NamedEventInterceptor objects also use class level generic type + * definitions as input for configuration. The generic type definition allows + * EventInterceptor implementations to restrict on event types by narrowing + * the type definition within the reference to the EventInterceptor interface. + * For example, the following interceptor restricts to only accept transfer + * events: + *


+ *     class MyInterceptor
+ *             implements EventInterceptor<TransferEvent>
+ *         {
+ *         public void onEvent(TransferEvent event);
+ *         }
+ * 
+ * The precedence, in highest order first reading left to right, for + * configuration is as follows: + *

+ *     XML -> Annotation -> Generic Type Bounds
+ * 
+ * + * @author hr 2011.10.05 + * @since Coherence 12.1.2 + */ +public class NamedEventInterceptorBuilder + implements ParameterizedBuilder, BuilderCustomization + { + // ----- ParameterizedBuilder interface --------------------------------- + + /** + * {@inheritDoc} + */ + @SuppressWarnings("rawtypes") + public NamedEventInterceptor realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + // ensure we have a classloader + loader = loader == null ? getClass().getClassLoader() : loader; + + ParameterizedBuilder bldr = getCustomBuilder(); + + EventInterceptor interceptor = null; + + try + { + interceptor = bldr.realize(resolver, loader, listParameters); + } + catch (Exception e) + { + throw new IllegalArgumentException("Unable to build an EventInterceptor based on the specified class: " + + String.valueOf(bldr), e); + } + + // determine the service and cache name for the event interceptor + Parameter paramServiceName = resolver.resolve("service-name"); + String sServiceName = paramServiceName == null ? null : paramServiceName.evaluate(resolver).as(String.class); + Parameter paramCacheName = resolver.resolve("cache-name"); + String sCacheName = paramCacheName == null ? null : paramCacheName.evaluate(resolver).as(String.class); + + // instantiate the NamedEventInterceptor which provides a store for the + // metadata used in determining the interceptor's applicability to an + // event dispatcher + // if a RegistrationBehavior is prescribed in the XML we will honor it + // otherwise we make an appropriate decision on the correct behavior in + // NamedEventInterceptor.ensureInitialized() + NamedEventInterceptor incptrNamed = new NamedEventInterceptor(getName(), interceptor, sCacheName, sServiceName, + getOrder(), getRegistrationBehavior()); + + return incptrNamed; + } + + // ----- BuilderCustomization interface --------------------------------- + + /** + * {@inheritDoc} + */ + public ParameterizedBuilder getCustomBuilder() + { + return m_bldrEventInterceptor; + } + + /** + * {@inheritDoc} + */ + @Override + public void setCustomBuilder(ParameterizedBuilder bldr) + { + m_bldrEventInterceptor = bldr; + } + + // ----- accessors ------------------------------------------------------ + + /** + * Get the logical name / identifier for this {@link EventInterceptor}. + * + * @return the logical name / identifier for this {@link EventInterceptor} + */ + public String getName() + { + return m_sName; + } + + /** + * Set the logical name / identifier for this {@link EventInterceptor}. + * + * @param sName logical name / identifier for this {@link EventInterceptor}. + * + * @return a reference to {@code this} builder + */ + @Injectable + public NamedEventInterceptorBuilder setName(String sName) + { + m_sName = sName; + + return this; + } + + /** + * Whether this {@link EventInterceptor} should be head of the stack. + * + * @return whether this {@link EventInterceptor} should be head of the stack + */ + public boolean isFirst() + { + return m_order != null && Interceptor.Order.HIGH.equals(m_order); + } + + /** + * Return the {@link Order} of this interceptor. + * + * @return the {@link Order} of this interceptor + */ + public Order getOrder() + { + return m_order; + } + + /** + * Set the {@link EventInterceptor}'s order (HIGH || LOW), hence + * whether it should be at the start of the chain of interceptors. + * + * @param order whether this {@link EventInterceptor} should be + * head of the stack based on the values {@link Order#HIGH} + * or {@link Order#LOW} + * + * @return a reference to {@code this} builder + */ + @Injectable + public NamedEventInterceptorBuilder setOrder(Order order) + { + m_order = order; + + return this; + } + + /** + * Returns the behavior upon duplicate registration. + * + * @return the behavior upon duplicate registration. + */ + public RegistrationBehavior getRegistrationBehavior() + { + return m_behavior; + } + + /** + * Specifies the behavior upon duplicate registration. + * + * @param behavior the behavior upon duplicate registration + * + * @return a reference to {@code this} builder + */ + @Injectable + public NamedEventInterceptorBuilder setRegistrationBehavior(RegistrationBehavior behavior) + { + m_behavior = behavior; + + return this; + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return "NamedEventInterceptorBuilder{" + "Builder = " + m_bldrEventInterceptor + ", name = '" + m_sName + '\'' + + ", priority = '" + isFirst() + '}'; + } + + // ---- data members ---------------------------------------------------- + + /** + * The logical name / identifier to be used for the realized {@link EventInterceptor}. + */ + private String m_sName; + + /** + * The {@link ParameterizedBuilder} responsible for realizing an instance + * of an {@link EventInterceptor} class. + */ + private ParameterizedBuilder m_bldrEventInterceptor; + + /** + * Whether this {@link EventInterceptor} should be head of the stack, with + * default behaviour being the tail/end. + */ + private Order m_order; + + /** + * Specifies the behavior upon duplicate registration. + */ + private RegistrationBehavior m_behavior; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ParameterizedBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ParameterizedBuilder.java new file mode 100644 index 0000000000000..c9b1fdc4a7f9a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ParameterizedBuilder.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +/** + * A {@link ParameterizedBuilder} is an implementation of the classic Builder Pattern that utilizes a + * {@link ParameterResolver} to resolve any required runtime {@link Parameter}s necessary for realizing an object. + *

+ * {@link ParameterizedBuilder}s are typically used to: + *

+ * 1. encapsulate the ability to dynamically configure the mechanism to realize/create/construct/resolve objects of a + * required type at runtime, typically based on some externally defined and parameterized configuration. + *

+ * 2. allow developers to postpone the creation of required objects at runtime, thus allowing lazy initialization of + * system components. + * + * @author bo 2011.06.23 + * @since Coherence 12.1.2 + */ +public interface ParameterizedBuilder + { + /** + * Realizes (creates if necessary) an instance of a object of type T, using the provided {@link ParameterResolver} + * to resolve values any referenced {@link Parameter}s. + * + * @param resolver the {@link ParameterResolver} for resolving named {@link Parameter}s + * @param loader the {@link ClassLoader} for loading any necessary classes and if null the + * {@link ClassLoader} used to load the builder will be used instead + * @param listParameters an optional {@link ParameterList} (may be null) to be used for realizing the + * instance, eg: used as constructor parameters + * + * @return an instance of T + */ + public T realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters); + + /** + * WARNING: Do not use this interface. It is no longer used internally and this + * deprecated interface will be removed in the future. + *

+ * A deprecated interface that {@link ParameterizedBuilder}s may implement + * to provide runtime type information about the type of objects that may be built. + * + * @since 12.1.3 + * @see ParameterizedBuilderHelper#realizes(ParameterizedBuilder, Class, ParameterResolver, ClassLoader) + */ + @Deprecated + public interface ReflectionSupport + { + /** + * Determines if the {@link ParameterizedBuilder} will realize an instance of the specified class (without + * requiring the builder to actually realize an object). + *

+ * This method is synonymous with the Java keyword instanceof but allows dynamic runtime type + * querying of the types of objects a builder may realize. + * + * @param clzClass the expected type + * @param resolver the {@link ParameterResolver} to use for resolving necessary {@link Parameter}s + * @param loader the {@link ClassLoader} for loading any necessary classes and if null the + * {@link ClassLoader} used to load the builder will be used instead + * + * @return true if the {@link ParameterizedBuilder} will realize an instance of the class, + * false otherwise + */ + public boolean realizes(Class clzClass, ParameterResolver resolver, ClassLoader loader); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ParameterizedBuilderHelper.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ParameterizedBuilderHelper.java new file mode 100644 index 0000000000000..8570e2ace7063 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ParameterizedBuilderHelper.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder.ReflectionSupport; + +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.expression.Value; + +import com.tangosol.util.ClassHelper; + +import java.lang.reflect.Type; + +import java.util.Map; + +/** + * The {@link ParameterizedBuilderHelper} defines helper methods for {@link ParameterizedBuilder} implementations. + * + * @author bo 2011-09-28 + * @since Coherence 12.1.2 + */ +public class ParameterizedBuilderHelper + { + /** + * Obtains an assignment compatible value of the required type given an actual {@link Parameter}. + *

+ * This allows us to accept parameters and produce an {@link Object} value that may be assigned using Java + * reflection. + * + * @param clzRequiredType the required type of value + * @param parameter the actual {@link Parameter} from which to determine the value + * @param resolver the {@link ParameterResolver} to resolve {@link Parameter}s used in {@link Parameter}s + * @param loader the {@link ClassLoader} to use for loading necessary classes (required) + * + * @return an object that is assignable to the required type + * @throws ClassCastException when it's not possible to determine an assignable value + */ + public static Object getAssignableValue(Class clzRequiredType, Parameter parameter, ParameterResolver resolver, + ClassLoader loader) + throws ClassCastException + { + // evaluate the parameter to produce the actual value + Value value = parameter.evaluate(resolver); + + if (value == null || value.isNull()) + { + // we can't do anything with nulls, so just accept them as a compatible value + return null; + } + + else if (clzRequiredType.isAssignableFrom(Value.class)) + { + // the value is already of the required type + return value; + } + else + { + Object oValue = value.get(); + Class clzValue = oValue.getClass(); + + if (clzRequiredType.isAssignableFrom(clzValue) + || (clzRequiredType.isPrimitive() && isAssignablePrimitive(clzRequiredType, clzValue))) + { + // the value is assignable to the required type + return oValue; + } + + else if (oValue instanceof ParameterizedBuilder) + { + // the value is a ParameterizedBuilder that can produce an assignable value + Object object = ((ParameterizedBuilder) oValue).realize(resolver, loader, null); + + if (clzRequiredType.isAssignableFrom(object.getClass()) + || (clzRequiredType.isPrimitive() && isAssignablePrimitive(clzRequiredType, object.getClass()))) + { + return object; + } + else + { + throw new ClassCastException(String.format("Can't coerce [%s] into a [%s].", + object.getClass().getCanonicalName(), clzRequiredType.getCanonicalName())); + } + } + else if (parameter.isExplicitlyTyped()) + { + // the parameter is of a specific type, so use that + Class clzParameterType = parameter.getExplicitType(); + + if (clzRequiredType.isAssignableFrom(clzParameterType) + || (clzRequiredType.isPrimitive() && isAssignablePrimitive(clzRequiredType, clzParameterType))) + { + return value.as(clzParameterType); + } + else + { + throw new ClassCastException(String.format("Can't coerce [%s] into a [%s].", value, + clzParameterType)); + } + } + else + { + // we've done all of the type checking we can, so just attempt to blindly coerce the value + return value.as(clzRequiredType); + } + } + } + + /** + * Determines if a primitive type is assignable to a wrapper type. + * + * @param clzPrimitive the primitive class type + * @param clzWrapper the wrapper class type + * + * @return true if primitive and wrapper are assignment compatible + */ + public static boolean isAssignablePrimitive(Class clzPrimitive, Class clzWrapper) + { + return (clzPrimitive.equals(java.lang.Boolean.TYPE) && clzWrapper.equals(java.lang.Boolean.class)) + || (clzPrimitive.equals(java.lang.Byte.TYPE) && clzWrapper.equals(java.lang.Byte.class)) + || (clzPrimitive.equals(java.lang.Character.TYPE) && clzWrapper.equals(java.lang.Character.class)) + || (clzPrimitive.equals(java.lang.Double.TYPE) && clzWrapper.equals(java.lang.Double.class)) + || (clzPrimitive.equals(java.lang.Float.TYPE) && clzWrapper.equals(java.lang.Float.class)) + || (clzPrimitive.equals(java.lang.Integer.TYPE) && clzWrapper.equals(java.lang.Integer.class)) + || (clzPrimitive.equals(java.lang.Long.TYPE) && clzWrapper.equals(java.lang.Long.class)) + || (clzPrimitive.equals(java.lang.Short.TYPE) && clzWrapper.equals(java.lang.Short.class)); + } + + /** + * Note: no longer used internally. deprecated for external usages, will be removed in future. + *

+ * Determines if a {@link ParameterizedBuilder} will build a specified + * {@link Class} of object. + * + * @param bldr the {@link ParameterizedBuilder} + * @param clzClass the {@link Class} of object expected + * @param resolver a {@link ParameterResolver} to resolve parameters + * @param loader the {@link ClassLoader} to use if classes need to be loaded + * + * @return true if the {@link ParameterizedBuilder} will build + * the specified {@link Class} of object, false otherwise + */ + @Deprecated + public static boolean realizes(ParameterizedBuilder bldr, Class clzClass, ParameterResolver resolver, + ClassLoader loader) + { + if (bldr == null) + { + return false; + } + + if (bldr instanceof ParameterizedBuilder.ReflectionSupport) + { + ReflectionSupport reflectionSupport = (ReflectionSupport) bldr; + + return reflectionSupport.realizes(clzClass, resolver, loader); + } + else + { + // TODO: attempt to use the re-ifed type to determine the type + Map mapTypes = ClassHelper.getReifiedTypes(bldr.getClass(), ParameterizedBuilder.class); + + if (mapTypes.containsKey("T")) + { + Type[] aTypes = mapTypes.get("T"); + + if (aTypes.length == 1 && aTypes[0] instanceof Type) + { + Class clzBuilt = (Class) ClassHelper.getClass(aTypes[0]); + + return clzClass.isAssignableFrom(clzBuilt); + } + } + + return false; + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ParameterizedBuilderRegistry.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ParameterizedBuilderRegistry.java new file mode 100644 index 0000000000000..8b1edecc32c15 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ParameterizedBuilderRegistry.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.oracle.coherence.common.base.Disposable; + +/** + * A registry of strongly typed and possibly named {@link ParameterizedBuilder}s. + *

+ * When a {@link ParameterizedBuilder} is registered with a {@link ParameterizedBuilderRegistry}, the + * {@link ParameterizedBuilderRegistry} assumes ownership of the said builder, up until + * at which point the {@link ParameterizedBuilderRegistry} is {@link #dispose() disposed}. + *

+ * Important: Although a {@link ParameterizedBuilderRegistry} manages + * builders in a thread-safe manner, it is possible for a thread calling + * {@link #getBuilder(Class, String)} to receive a null return + * value while another thread is registering a builder. + * + * @author bo 2014.10.27 + * @since Coherence 12.1.3 + */ +public interface ParameterizedBuilderRegistry + extends Disposable, Iterable + { + /** + * Attempts to retrieve the builder that was registered with the + * specified class. + * + * @param the type of the instance that will be produced by the builder + * @param clzInstance the class of the instance + * + * @return the registered builder or null if the builder is + * unknown to the {@link ParameterizedBuilderRegistry} + */ + public ParameterizedBuilder getBuilder(Class clzInstance); + + /** + * Attempts to retrieve the builder that was registered with the + * specified class and name. + * + * @param the type of the instance that will be produced by the builder + * @param clzInstance the class of the instance + * @param sBuilderName the name of the builder + * + * @return the registered builder or null if the builder is + * unknown to the {@link ParameterizedBuilderRegistry} + */ + public ParameterizedBuilder getBuilder(Class clzInstance, String sBuilderName); + + /** + * Registers a {@link ParameterizedBuilder} for later retrieval with {@link #getBuilder(Class)}. + *

+ * Notes: + *

    + *
  1. Multiple builders for the same class can be registered if each + * builder is registered with a unique name via + * {@link #registerBuilder(Class, String, ParameterizedBuilder)} + *
  2. Registration of builders will occur in a thread-safe manner. + *
  3. Builders that are {@link Disposable} will be disposed when the + * {@link ParameterizedBuilderRegistry} is disposed. + *
+ * + * @param clzInstance the class of instances produced by the builder + * @param builder the builder + * + * @return the actual name used to register the builder + * + * @throws IllegalArgumentException if a builder of the same specified type + * is already registered + */ + public String registerBuilder(Class clzInstance, ParameterizedBuilder builder) + throws IllegalArgumentException; + + /** + * Registers a {@link ParameterizedBuilder} with the specified name for later retrieval with + * {@link #getBuilder(Class, String)}. + *

+ * Notes: + *

    + *
  1. Registration of builders will occur in a thread-safe manner. + *
  2. Builders that are {@link Disposable} will be disposed when the + * {@link ParameterizedBuilderRegistry} is disposed. + *
+ * + * @param clzInstance the class of instances produced by the builder + * @param builder the builder + * @param sBuilderName the proposed name of the builder + * + * @return the actual name used to register the builder + * + * @throws IllegalArgumentException if a builder of the same specified type + * and name is already registered + */ + public String registerBuilder(Class clzInstance, String sBuilderName, + ParameterizedBuilder builder) + throws IllegalArgumentException; + + // ----- Registration interface ------------------------------------------------------ + + /** + * Defines a single {@link ParameterizedBuilder} registration with a + * {@link ParameterizedBuilderRegistry}. + */ + public static interface Registration + { + /** + * Obtains the name of the builder. + * + * @return the name of the builder + */ + public String getName(); + + /** + * Obtains the class of instance produced by the builder. + * + * @return the class of instance + */ + public Class getInstanceClass(); + + /** + * Obtains the {@link ParameterizedBuilder}. + * + * @return the {@link ParameterizedBuilder} + */ + public ParameterizedBuilder getBuilder(); + } + + // ----- constants ------------------------------------------------------ + + /** + * The name to use for the registration of a singleton and thus default resource. + */ + public static final String DEFAULT_NAME = "default"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/PartitionAssignmentStrategyBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/PartitionAssignmentStrategyBuilder.java new file mode 100644 index 0000000000000..580af5c58fe00 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/PartitionAssignmentStrategyBuilder.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.partition.MirroringAssignmentStrategy; +import com.tangosol.net.partition.PartitionAssignmentStrategy; +import com.tangosol.net.partition.SimpleAssignmentStrategy; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +/** + * The {@link PartitionAssignmentStrategyBuilder} builds a {@link PartitionAssignmentStrategy}. + * + * @author jf 2015.01.29 + * @since Coherence 12.2.1 + */ +public class PartitionAssignmentStrategyBuilder + extends DefaultBuilderCustomization + implements ParameterizedBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link PartitionAssignmentStrategyBuilder} + * + * @param bldr customized PartitionAssignmentStrategy builder + * @param xmlElement optional configuration context to be used to provide details if + * a ConfigurationException is detected. + * + */ + public PartitionAssignmentStrategyBuilder(ParameterizedBuilder bldr, XmlElement xmlElement) + { + m_builder = bldr; + m_sStrategy = null; + m_xmlElement = xmlElement; + } + + /** + * Constructs a {@link PartitionAssignmentStrategyBuilder} for a predefined strategy. + * + * @param sStrategy implementation defined partition assignment strategy + * @param xmlElement optional configuration context to be used to provide details if + * a ConfigurationException is detected. + */ + public PartitionAssignmentStrategyBuilder(String sStrategy, XmlElement xmlElement) + { + m_sStrategy = sStrategy; + m_xmlElement = xmlElement; + m_builder = null; + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public PartitionAssignmentStrategy realize(ParameterResolver resolver, ClassLoader loader, + ParameterList listParameters) + { + ParameterizedBuilder builder = m_builder; + String sPAS = m_sStrategy; + XmlElement xmlPAS = m_xmlElement; + if (builder != null) + { + try + { + return (PartitionAssignmentStrategy) builder.realize(resolver, loader, listParameters); + } + catch (Exception e) + { + String sName = xmlPAS == null ? "" : xmlPAS.getName(); + throw new ConfigurationException("Invalid <" + sName + + "> declaration. The specified builder doesn't produce a PartitionAssignmentStrategy in [" + + xmlPAS + "]", "Please specify a <" + sName + ">", e); + } + } + else if ("simple".equals(sPAS)) + { + return new SimpleAssignmentStrategy(); + } + else if (sPAS != null && sPAS.startsWith("mirror:")) + { + return new MirroringAssignmentStrategy(sPAS.substring(7).trim()); + } + throw new ConfigurationException("Invalid declaration of '" + sPAS + + "' in [" + xmlPAS + "]", "Please specify a valid partition-assignment-strategy"); + } + + // ----- data members --------------------------------------------------- + + /** + * Customized builder + */ + private ParameterizedBuilder m_builder; + + /** + * Provides configuration content if there is a ConfigurationException. + */ + private XmlElement m_xmlElement; + + /** + * Predefined strategy names + */ + private String m_sStrategy; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/PartitionedCacheQuorumPolicyBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/PartitionedCacheQuorumPolicyBuilder.java new file mode 100644 index 0000000000000..11a51d0b490c2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/PartitionedCacheQuorumPolicyBuilder.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.ActionPolicy; +import com.tangosol.net.AddressProvider; +import com.tangosol.net.ConfigurableQuorumPolicy; + +import com.tangosol.run.xml.XmlElement; + +import java.util.ArrayList; + +import static com.tangosol.net.ConfigurableQuorumPolicy.MembershipQuorumPolicy; +import static com.tangosol.net.ConfigurableQuorumPolicy.PartitionedCacheQuorumPolicy; + + +/** + * Defer cache configuration validation of an ActionPolicy until realized. + * + * @author jf 2015.01.29 + * @since Coherence 12.2.1 + */ +public class PartitionedCacheQuorumPolicyBuilder + extends ActionPolicyBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link PartitionedCacheQuorumPolicyBuilder} from configuration file context and xml element + * + * @param bldrRecoveryHostAddress Recovery Host AddressProvider builder + */ + public PartitionedCacheQuorumPolicyBuilder(AddressProviderBuilder bldrRecoveryHostAddress, XmlElement xmlConfig) + { + m_bldrRecoveryHostAddress = bldrRecoveryHostAddress; + m_aRules = new ArrayList<>(PartitionedCacheQuorumPolicy.ActionRule.values().length); + m_xmlConfig = xmlConfig; + } + + + // ----- PartitionedCacheQuorumPolicyBuilder methods -------------------- + + public void addQuorumRule(String sRuleName, int nRuleMask, int nRuleThreshold) + { + addQuorumRule(sRuleName, nRuleMask, nRuleThreshold, 0.0f); + } + + public void addQuorumRule(String sRuleName, int nRuleMask, int nRuleThreshold, float flRuleThresholdPct) + { + m_aRules.add(new QuorumRule(sRuleName, nRuleMask, nRuleThreshold, flRuleThresholdPct, m_xmlConfig)); + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ActionPolicy realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + throws ConfigurationException + { + MembershipQuorumPolicy.QuorumRule[] rules = new MembershipQuorumPolicy.QuorumRule[m_aRules.size()]; + + int i = 0; + for (QuorumRule rule : m_aRules) + { + rule.validate(); + rules[i] = new MembershipQuorumPolicy.QuorumRule(rule.m_nRuleMask, rule.m_nThreshold, rule.m_flThresholdPct); + i++; + } + + AddressProvider recoveryHostAddress = m_bldrRecoveryHostAddress == null ? null : + m_bldrRecoveryHostAddress.realize(resolver, loader, listParameters); + + return ConfigurableQuorumPolicy.instantiatePartitionedCachePolicy(rules, recoveryHostAddress); + } + + // ----- data members --------------------------------------------------- + + /** + * ActionPolicy rules with xml configuration info to enable constructing + * informative ConfigurationException message if a constraint is violated. + */ + private ArrayList m_aRules; + + /** + * RecoveryHost AddressBuilder + */ + private AddressProviderBuilder m_bldrRecoveryHostAddress; + + /** + * Optional xml configuration for reporting CacheConfiguration error. + */ + private XmlElement m_xmlConfig; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/PersistenceEnvironmentParamBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/PersistenceEnvironmentParamBuilder.java new file mode 100644 index 0000000000000..fe5278e6780ef --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/PersistenceEnvironmentParamBuilder.java @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.oracle.coherence.common.base.Continuation; + +import com.oracle.coherence.persistence.PersistenceEnvironment; +import com.oracle.coherence.persistence.PersistenceManager; +import com.oracle.coherence.persistence.PersistentStore; + +import com.tangosol.coherence.config.Config; +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.net.service.grid.PersistenceDependencies; +import com.tangosol.io.FileHelper; +import com.tangosol.io.ReadBuffer; + +import com.tangosol.persistence.AbstractPersistenceEnvironment; +import com.tangosol.persistence.CachePersistenceHelper; +import com.tangosol.persistence.SafePersistenceWrappers; +import com.tangosol.persistence.bdb.BerkeleyDBEnvironment; + +import com.tangosol.util.Base; + +import java.io.File; + +/** + * Build a {@link PersistenceEnvironment}. + *

+ * Provide a means to get a {@link PersistenceEnvironmentInfo} without + * creating a {@link PersistenceEnvironment} to be used by a viewer. + *

+ * Defaulting built into the builder so same defaults whether + * read in from xml configuration or constructed solely by builder api. + * + * @author jf 2015.03.12 + * @since Coherence 12.2.1 + */ +// TODO: rename class to PersistenceEnvironmentBuilder once the unnecessary +// interface named PersistenceEnvironmentBuilder has been removed +public class PersistenceEnvironmentParamBuilder + implements ParameterizedBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link PersistenceEnvironment} builder. + */ + public PersistenceEnvironmentParamBuilder() + { + // Determine default persistence directories. + String sHome = getDefaultPersistenceEnvironmentHomeDirectory(); + + m_sActive = sHome + File.separatorChar + CachePersistenceHelper.DEFAULT_ACTIVE_DIR; + m_sSnapshot = sHome + File.separatorChar + CachePersistenceHelper.DEFAULT_SNAPSHOT_DIR; + m_sTrash = sHome + File.separatorChar + CachePersistenceHelper.DEFAULT_TRASH_DIR; + m_sMode = "active"; + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + @Override + public PersistenceEnvironment realize(ParameterResolver resolver, ClassLoader loader, + ParameterList listParameters) + { + String sServiceName = getValueAsString(resolver, "service-name"); + String sClusterName = getValueAsString(resolver, "cluster-name"); + + PersistenceEnvironmentInfo info = getPersistenceEnvironmentInfo(sClusterName, sServiceName); + + PersistenceEnvironment environment; + try + { + // default to a BerkeleyDBEnvironment or delegate to the builder + environment = m_bldr == null + ? new BerkeleyDBEnvironment( + info.getPersistenceActiveDirectory(), + info.getPersistenceSnapshotDirectory(), + info.getPersistenceTrashDirectory()) + : m_bldr.realize(createResolver(sClusterName, sServiceName), loader, listParameters); + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e, + "Error creating PersistenceEnvironment: " + this); + } + return wrap(environment); + } + + // ----- PersistenceEnvironmentBuilder methods -------------------------- + + /** + * Return a {@link PersistenceEnvironmentInfo} encapsulating the configuration + * this builder uses to construct {@link PersistenceEnvironment environments}. + * + * @param sClusterName the cluster name + * @param sServiceName the service name + * + * @return a PersistenceEnvironmentInfo encapsulating the configuration + * this builder uses to construct environments + */ + public PersistenceEnvironmentInfo getPersistenceEnvironmentInfo(String sClusterName, String sServiceName) + { + // validate the cluster name and create a filesystem-safe version + if (sClusterName == null || (sClusterName = sClusterName.trim()).length() == 0) + { + throw new IllegalArgumentException("invalid cluster name"); + } + + String sCluster = FileHelper.toFilename(sClusterName); + + // validate the service name and create a filesystem-safe version + if (sServiceName == null || (sServiceName = sServiceName.trim()).length() == 0) + { + throw new IllegalArgumentException("invalid service name"); + } + + String sService = FileHelper.toFilename(sServiceName); + + // create a directory reference for the active, snapshot and trash locations + File fileActive = isActive() ? new File(new File(new File(m_sActive), sCluster), sService) : null; + File fileSnapshot = new File(new File(new File(m_sSnapshot), sCluster), sService); + File fileTrash = new File(new File(new File(m_sTrash), sCluster), sService); + + return new PersistenceEnvironmentInfo(fileActive, fileSnapshot, fileTrash, m_sMode); + } + + public String getPersistenceMode() + { + return m_sMode; + } + + /** + * Set persistence mode. + * + * @param sMode active, active-async or on-demand + */ + @Injectable("persistence-mode") + public void setPersistenceMode(String sMode) + { + if (sMode != null && sMode.length() > 0) + { + m_sMode = sMode; + } + } + + /** + * Set the persistence active directory. + * + * @param sPathname either relative or absolute pathname + */ + @Injectable("active-directory") + public void setActiveDirectory(String sPathname) + { + if (sPathname != null && sPathname.length() > 0) + { + m_sActive = sPathname; + } + } + + /** + * Set the persistence snapshot directory. + * + * @param sPathname either relative or absolute pathname + */ + @Injectable("snapshot-directory") + public void setPersistenceSnapshotDirectory(String sPathname) + { + if (sPathname != null && sPathname.length() > 0) + { + m_sSnapshot = sPathname; + } + } + + /** + * Set the persistence trash directory. + * + * @param sPathname either relative or absolute pathname + */ + @Injectable("trash-directory") + public void setPersistenceTrashDirectory(String sPathname) + { + if (sPathname != null && sPathname.length() > 0) + { + m_sTrash = sPathname; + } + } + + /** + * Set a {@link ParameterizedBuilder builder} to be used instantiate the + * appropriate {@link PersistenceEnvironment}. + * + * @param bldrPersistence the builder that creates a PersistenceEnvironment + */ + @Injectable("instance") + public void setCustomEnvironment(ParameterizedBuilder> bldrPersistence) + { + m_bldr = bldrPersistence; + } + + // ----- Object methods ------------------------------------------------- + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder() + .append("\n Mode: ").append(m_sMode) + .append("\n Active Location: ").append(m_sActive) + .append("\n Snapshot Location:").append(m_sSnapshot) + .append("\n Trash Location:").append(m_sTrash); + + if (m_bldr != null) + { + sb.append("\n Instance: ").append(m_bldr); + } + + return sb.toString(); + } + + // ----- helpers -------------------------------------------------------- + + /** + * Wrap a {@link PersistenceEnvironment} with a SafePersistenceEnvironment. + * + * @param env a wrapped PersistenceEnvironment + * + * @return a wrapped PersistenceEnvironment + */ + protected PersistenceEnvironment wrap(PersistenceEnvironment env) + { + // wrap the environment in a safe layer + return new SafePersistenceWrappers.SafePersistenceEnvironment<>(env, DEFAULT_FACTORY); + } + + /** + * Create a {@link ResolvableParameterList resolver} based on the provided + * cluster, service name and the state of this builder (active, snapshot + * and trash directories). + * + * @param sClusterName the name of the cluster + * @param sServiceName the name of the service + * + * @return a resolver based on the provided cluster and service name + */ + protected ResolvableParameterList createResolver(String sClusterName, String sServiceName) + { + PersistenceEnvironmentParamBuilder.PersistenceEnvironmentInfo info = + getPersistenceEnvironmentInfo(sClusterName, sServiceName); + ResolvableParameterList resolver = new ResolvableParameterList(); + + resolver.add(new Parameter("cluster-name", String.class, sClusterName)); + resolver.add(new Parameter("service-name", String.class, sServiceName)); + resolver.add(new Parameter("persistence-mode", String.class, info.getPersistenceMode())); + resolver.add(new Parameter("active-directory", File.class, info.getPersistenceActiveDirectory())); + resolver.add(new Parameter("snapshot-directory", File.class, info.getPersistenceSnapshotDirectory())); + resolver.add(new Parameter("trash-directory", File.class, info.getPersistenceTrashDirectory())); + + return resolver; + } + + /** + * Return true if the persistence mode is active. + * + * @return true if the persistence mode is active + */ + protected boolean isActive() + { + return m_sMode.equalsIgnoreCase(PersistenceDependencies.MODE_ACTIVE) || + m_sMode.equalsIgnoreCase(PersistenceDependencies.MODE_ACTIVE_ASYNC); + } + + /** + * Return the default 'base' persistence directory. + *

+ * This location is the base for active, snapshot and trash locations if + * they have not been specified in the operational configuration. + *

+ * The default base location is {@code $HOME/coherence}. + * This location can be overridden by specifying the JVM argument: + * -D{@value CachePersistenceHelper#DEFAULT_BASE_DIR_PROPERTY}. + * + * @return default base directory for persistence + */ + public static String getDefaultPersistenceEnvironmentHomeDirectory() + { + String sHome = Config.getProperty(CachePersistenceHelper.DEFAULT_BASE_DIR_PROPERTY); + + if (sHome == null) + { + sHome = System.getProperty("user.home") + File.separatorChar + CachePersistenceHelper.DEFAULT_BASE_DIR; + } + + return sHome; + } + + /** + * Return a string value from the given {@link ParameterResolver} and parameter + * name. + * + * @param resolver resolver + * @param sParamName parameter name + * + * @return a string value from the given ParameterResolver and parameter + * name + */ + private static String getValueAsString(ParameterResolver resolver, String sParamName) + { + Parameter p = resolver.resolve(sParamName); + + return p == null ? "" : p.evaluate(resolver).as(String.class); + } + + // ----- inner class: DefaultFailureContinuation ------------------------ + + /** + * Default failure continuation factory that provides continuations that + * only throw PersistenceExceptions. + */ + protected static final SafePersistenceWrappers.FailureContinuationFactory DEFAULT_FACTORY = + new SafePersistenceWrappers.FailureContinuationFactory() + { + @Override + public Continuation getEnvironmentContinuation(PersistenceEnvironment env) + { + return new AbstractPersistenceEnvironment.DefaultFailureContinuation(env); + } + + @Override + public Continuation getManagerContinuation(PersistenceManager mgr) + { + return new AbstractPersistenceEnvironment.DefaultFailureContinuation(mgr); + } + + @Override + public Continuation getStoreContinuation(PersistentStore store) + { + return new AbstractPersistenceEnvironment.DefaultFailureContinuation(store); + } + }; + + // ----- inner class: PersistenceEnvironmentInfo ------------------------ + + /** + * A {@link com.tangosol.persistence.PersistenceEnvironmentInfo PersistenceEnvironmentInfo} + * implementation that exposes the active, snapshot and trash directories, + * in addition to the persistence mode. + */ + public static class PersistenceEnvironmentInfo + implements com.tangosol.persistence.PersistenceEnvironmentInfo + { + // ----- constructors ----------------------------------------------- + + /** + * Constructs {@link PersistenceEnvironmentInfo}. + * + * @param dirActive active directory + * @param dirSnapshot snapshot directory + * @param dirTrash trash directory + * @param sMode persistence mode (active or on-demand) + */ + public PersistenceEnvironmentInfo(File dirActive, File dirSnapshot, File dirTrash, + String sMode) + { + f_dirActive = dirActive; + f_dirSnapshot = dirSnapshot; + f_dirTrash = dirTrash; + f_sMode = sMode; + } + + // ------ PersistenceEnvironmentInfo interface ------------------------ + + @Override + public File getPersistenceActiveDirectory() + { + return f_dirActive; + } + + @Override + public File getPersistenceSnapshotDirectory() + { + return f_dirSnapshot; + } + + @Override + public File getPersistenceTrashDirectory() + { + return f_dirTrash; + } + + @Override + public long getPersistenceActiveSpaceUsed() + { + return 0L; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the persistence mode. + * + * @return the persistence mode (active or on-demand) + */ + public String getPersistenceMode() + { + return f_sMode; + } + + /** + * Return whether the persistence mode is active. + * + * @return true if the persistence mode is active + */ + public boolean isActive() + { + return "active".equalsIgnoreCase(f_sMode); + } + + // ------ Object methods -------------------------------------------- + + @Override + public String toString() + { + return getClass().getSimpleName() + + "{mode=" + f_sMode + + ", activeDir=" + (f_dirActive == null ? null : f_dirActive.getAbsoluteFile()) + + ", snapshotDir=" + f_dirSnapshot.getAbsoluteFile() + + ", trashDir=" + f_dirTrash.getAbsoluteFile() + '}'; + } + + // ------ data members ---------------------------------------------- + + /** + * Path to the active directory. + */ + private final File f_dirActive; + + /** + * Path to the snapshot directory. + */ + private final File f_dirSnapshot; + + /** + * Path to the trash directory. + */ + private final File f_dirTrash; + + /** + * The persistence mode. + */ + private final String f_sMode; + } + + // ----- data members --------------------------------------------------- + + /** + * The mode used by persistence; either active or on-demand. + */ + protected String m_sMode; + + /** + * The active directory used by persistence. + */ + protected String m_sActive; + + /** + * The snapshot directory used by persistence. + */ + protected String m_sSnapshot; + + /** + * The trash directory used by persistence. + */ + protected String m_sTrash; + + /** + * A {@link ParameterizedBuilder} that creates a {@link PersistenceEnvironment}. + */ + protected ParameterizedBuilder> m_bldr; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ProxyQuorumPolicyBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ProxyQuorumPolicyBuilder.java new file mode 100644 index 0000000000000..dba95a78d71b2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ProxyQuorumPolicyBuilder.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.ActionPolicy; +import com.tangosol.net.ConfigurableQuorumPolicy; + +import com.tangosol.run.xml.XmlElement; + +import static com.tangosol.net.ConfigurableQuorumPolicy.MembershipQuorumPolicy; + +/** + * Defer cache configuration validation of a ProxyQuorumPolicy until realized. + * + * @author jf 2015.02.05 + * @since Coherence 12.2.1 + */ +public class ProxyQuorumPolicyBuilder + extends ActionPolicyBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link ProxyQuorumPolicyBuilder} from configuration file context and xml element + * + * @param nRuleThreshold connect quorum rule threshold + * @param xmlConfig containing element proxy-cache-quorum-policy + */ + public ProxyQuorumPolicyBuilder(int nRuleThreshold, XmlElement xmlConfig) + { + m_aRule = new QuorumRule(CONNECT_RULE_NAME, MASK_CONNECT, nRuleThreshold, xmlConfig); + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ActionPolicy realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + throws ConfigurationException + { + m_aRule.validate(); + + MembershipQuorumPolicy.QuorumRule rules[] = + { + new MembershipQuorumPolicy.QuorumRule(m_aRule.m_nRuleMask, m_aRule.m_nThreshold) + }; + + return ConfigurableQuorumPolicy.MembershipQuorumPolicy.instantiateProxyPolicy(rules); + } + + // ----- constants ------------------------------------------------------ + + /** + * Connect quorum mask. + */ + public static final int MASK_CONNECT = ConfigurableQuorumPolicy.ProxyQuorumPolicy.MASK_CONNECT; + + /** + * Connect description and also xml configuration element name. + */ + public static final String CONNECT_RULE_NAME = "connect-quorum"; + + // ----- data members --------------------------------------------------- + + /** + * ActionPolicy rules with xml configuration info to enable constructing + * informative {@link ConfigurationException} message if a constraint is violated. + */ + private QuorumRule m_aRule; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ProxyServiceLoadBalancerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ProxyServiceLoadBalancerBuilder.java new file mode 100644 index 0000000000000..e5381eeb8b248 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ProxyServiceLoadBalancerBuilder.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.ServiceLoadBalancer; +import com.tangosol.net.proxy.DefaultProxyServiceLoadBalancer; +import com.tangosol.net.proxy.ProxyServiceLoadBalancer; + +import com.tangosol.run.xml.XmlElement; + +/** + * {@link ProxyServiceLoadBalancerBuilder} defers evaluating configuration parameters + * until ServiceLoadBalancer is instantiated. + * + * @author jf 2015.02.10 + * @since Coherence 12.2.1 + */ +public class ProxyServiceLoadBalancerBuilder + extends ServiceLoadBalancerBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link ProxyServiceLoadBalancerBuilder} + * + * @param bldr optional customized ProxyServiceLoadBalancer + * @param xmlConfig optional configuration element for reporting configuration exception. + */ + public ProxyServiceLoadBalancerBuilder(ParameterizedBuilder bldr, XmlElement xmlConfig) + { + super(bldr, xmlConfig); + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ServiceLoadBalancer realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + try + { + ProxyServiceLoadBalancer loadBalancer = (ProxyServiceLoadBalancer) super.realize(resolver, loader, listParameters); + return loadBalancer; + } + catch (ClassCastException e) + { + throw new ConfigurationException("invalid ProxyServiceLoadBalancer configuration for element <" + + f_xmlConfig + ">", "Provide a ProxyServiceLoadBalancer", e); + } + } + + // ----- ServiceLoadBalancerBuilder methods ----------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ServiceLoadBalancer getDefaultLoadBalancer() + { + return new DefaultProxyServiceLoadBalancer(); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SSLSocketProviderDependenciesBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SSLSocketProviderDependenciesBuilder.java new file mode 100644 index 0000000000000..77230890a4502 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SSLSocketProviderDependenciesBuilder.java @@ -0,0 +1,1101 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.oracle.coherence.common.net.SocketProvider; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.xml.processor.PasswordProviderBuilderProcessor; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.NullParameterResolver; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.net.ssl.SSLSocketProviderDefaultDependencies; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.PasswordProvider; +import com.tangosol.net.SocketProviderFactory; +import com.tangosol.net.security.SecurityProvider; +import com.tangosol.util.Base; + +import java.io.IOException; +import java.io.InputStream; + +import java.net.URL; + +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.Provider; +import java.security.SecureRandom; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import java.util.concurrent.Executor; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +/** + * {@link SSLSocketProviderDependenciesBuilder} enables lazy instantiation of SSL SocketProvider. + * + * Builder includes methods that allows one to specify whether to get a datagram or demultiplexed + * {@link SocketProvider} and what subport to use for the socketprovider. + * + * @author jf 2015.11.11 + * @since Coherence 12.2.1.1 + */ +public class SSLSocketProviderDependenciesBuilder + implements ParameterizedBuilder + { + // ----- constructors ---------------------------------------------------- + + /** + * Constructs {@link SSLSocketProviderDependenciesBuilder} + * + * @param deps {@link SSLSocketProviderDefaultDependencies} defaults for cluster + */ + public SSLSocketProviderDependenciesBuilder(SSLSocketProviderDefaultDependencies deps) + { + m_deps = deps; + m_bldrDelegateSocketProvider = new SocketProviderBuilder(SocketProviderFactory.DEFAULT_SOCKET_PROVIDER); + m_sNameProtocol = SSLSocketProviderDefaultDependencies.DEFAULT_SSL_PROTOCOL; + } + + + // ----- SSLSocketProviderDependenciesBuilder methods -------------------------------- + + /** + * Set the SSL protocol name + * + * @param sName + */ + @Injectable("protocol") + public void setProtocol(String sName) + { + m_sNameProtocol = sName; + } + + /** + * Get the SSL protocol name + * + * @return protocol name + */ + public String getProtocol() + { + return m_sNameProtocol; + } + + /** + * Set the SSL provider builder. + * + * @param builder SSL provider builder + */ + @Injectable("provider") + public void setProviderBuilder(ProviderBuilder builder) + { + m_bldrProvider = builder; + } + + /** + * Get the SSL provider builder. + * + * @return the provider builder + */ + public ProviderBuilder getProvider() + { + return m_bldrProvider; + } + + /** + * Set SSL executors builder. + * + * @param bldr builder for SSL executors. + */ + @Injectable("executor") + public void setExecutor(ParameterizedBuilder bldr) + { + m_bldrExecutor = bldr; + } + + /** + * Set the SSL identity manager dependencies. + * + * @param deps configured or defaulted values for identity manager dependencies + */ + @Injectable("identity-manager") + public void setIdentityManager(DefaultManagerDependencies deps) + { + m_depsIdentityManager = deps; + } + + /** + * Get the SSL identity manager dependencies + * + * @return identity manager configured/defaulted values + */ + public DefaultManagerDependencies getIdentityManager() + { + return m_depsIdentityManager; + } + + /** + * Get the SSL trust manager + * + * @return the trust manager + */ + public ManagerDependencies getTrustManager() + { + return m_depsTrustManager; + } + + /** + * Set the SSL trust manager + * + * @param deps trust manager configured/defaulted values + */ + @Injectable("trust-manager") + public void setTrustManager(ManagerDependencies deps) + { + m_depsTrustManager = deps; + } + + /** + * Set the customized HostnameVerifierBuilder + * + * @param bldr HostnameVerifierBuilder + */ + @Injectable("hostname-verifier") + public void setHostnameVerifierBuilder(ParameterizedBuilder bldr) + { + m_bldrHostnameVerifier = bldr; + } + + /** + * Get customized HostnameVerifierBuilder + * + * @return {@link HostnameVerifier} or null + */ + public ParameterizedBuilder getHostnameVerifierBuilder() + { + return m_bldrHostnameVerifier; + } + + /** + * Set cipher-suites dependencies + * + * @param deps cipher-suites config info + */ + @Injectable("cipher-suites") + public void setCipherSuitesNameList(NameListDependencies deps) + { + m_depsCipherSuite = deps; + } + + /** + * Set protocol-versions dependencies + * + * @param deps protocol-versions config info + */ + @Injectable("protocol-versions") + public void setProtocolVersionsNameList(NameListDependencies deps) + { + m_depsProtocolVersion = deps; + } + + /** + * Set delegate SocketProviderBuilder + * + * @param bldr delegate socket provider builder + */ + @Injectable("socket-provider") + public void setDelegate(SocketProviderBuilder bldr) + { + m_bldrDelegateSocketProvider = bldr; + } + + + /** + * Get delegate socket provider builder + * + * @return socket provider builder + */ + public SocketProviderBuilder getSocketProviderBuilder() + { + return m_bldrDelegateSocketProvider; + } + + /** + * Realize a SSLSocketProviderDefaultDependencies based on configured/defaulted values for config element ssl. + * + * Note: unlike typical builders, this is realize once since sensitive password data is nulled after realizing. + * + * @return {@link SSLSocketProviderDefaultDependencies} + */ + public synchronized SSLSocketProviderDefaultDependencies realize() + { + if (m_fRealized) + { + return m_deps; + } + + // realize once + SSLSocketProviderDefaultDependencies deps = m_deps; + + try + { + KeyManager[] aKeyManager = null; + TrustManager[] aTrustManager = null; + SSLContext ctx = null; + StringBuffer sbDesc = new StringBuffer(); + + Provider provider = m_bldrProvider == null ? null : (Provider) m_bldrProvider.realize(null, null, null); + + if (provider == null) + { + if (m_bldrProvider != null && m_bldrProvider.getName() != null) + { + ctx = SSLContext.getInstance(getProtocol(), m_bldrProvider.getName()); + } + } + else + { + ctx = SSLContext.getInstance(getProtocol(), provider); + } + + if (ctx == null) + { + ctx = SSLContext.getInstance(getProtocol()); + } + + deps.setSSLContext(ctx); + + + if (m_bldrExecutor == null) + { + deps.setExecutor(SSLSocketProviderDefaultDependencies.DEFAULT_EXECUTOR); + } + else + { + deps.setExecutor(m_bldrExecutor.realize(new NullParameterResolver(), null, null)); + } + + DefaultManagerDependencies depsIdMgr = getIdentityManager(); + + if (depsIdMgr == null) + { + sbDesc.append("identity=unspecified"); + } + else + { + KeyManagerFactory factory = null; + ProviderBuilder bldrProvider = depsIdMgr.getProviderBuilder(); + String sAlgorithm = depsIdMgr.getAlgorithm(); + + sbDesc.append("identity=").append(sAlgorithm); + + if (bldrProvider != null) + { + provider = bldrProvider.realize(null, null, null); + if (provider == null) + { + if (bldrProvider.getName() != null) + { + factory = KeyManagerFactory.getInstance(sAlgorithm, bldrProvider.getName()); + } + } + else + { + factory = KeyManagerFactory.getInstance(sAlgorithm, provider); + } + } + + if (factory == null) + { + factory = KeyManagerFactory.getInstance(sAlgorithm); + } + + DefaultKeystoreDependencies depsKeystore = depsIdMgr.getKeystoreDependencies(); + String sURL = depsKeystore.getURL(); + KeyStore keyStore = loadKeyStore(sURL, depsKeystore.getPasswordProvider(), depsKeystore.getType()); + + if (sURL != null && sURL.length() > 0) + { + sbDesc.append('/').append(sURL); + } + + char[] achPassword = depsIdMgr.getPasswordProvider().get(); + factory.init(keyStore, achPassword); + aKeyManager = factory.getKeyManagers(); + + //Zero the password + if (achPassword != null) + { + Arrays.fill(achPassword, '0'); + } + } + + ManagerDependencies depsTrustMgr = getTrustManager(); + + if (depsTrustMgr == null) + { + sbDesc.append(", trust=unspecified"); + } + else + { + TrustManagerFactory factory = null; + ProviderBuilder bldrProvider = depsTrustMgr.getProviderBuilder(); + String sAlgorithm = depsTrustMgr.getAlgorithm(); + + sbDesc.append(", trust=").append(sAlgorithm); + + if (bldrProvider != null) + { + provider = bldrProvider.realize(null, null, null); + + String sProvider = bldrProvider.getName(); + + if (provider == null) + { + if (sProvider != null) + { + factory = TrustManagerFactory.getInstance(sAlgorithm, sProvider); + } + } + else + { + factory = TrustManagerFactory.getInstance(sAlgorithm, provider); + } + } + + if (factory == null) + { + factory = TrustManagerFactory.getInstance(sAlgorithm); + } + + DefaultKeystoreDependencies depsKeystore = depsTrustMgr.getKeystoreDependencies(); + String sURL = depsKeystore.getURL(); + KeyStore keyStore = loadKeyStore(sURL, depsKeystore.getPasswordProvider(), depsKeystore.getType()); + + if (sURL != null && sURL.length() > 0) + { + sbDesc.append('/').append(sURL); + } + + factory.init(keyStore); + + aTrustManager = factory.getTrustManagers(); + deps.setClientAuthenticationRequired(aTrustManager != null); + } + + + ParameterizedBuilder bldrHostnameVerifier = getHostnameVerifierBuilder(); + + if (bldrHostnameVerifier != null) + { + deps.setHostnameVerifier(bldrHostnameVerifier.realize(null, null, null)); + sbDesc.append(", hostname-verifier=enabled"); + } + + // intialize a random number source + SecureRandom random = new SecureRandom(); + + random.nextInt(); + + // initialize the SSLContext + ctx.init(aKeyManager, aTrustManager, random); + + if (m_depsCipherSuite != null) + { + List listCipher = m_depsCipherSuite.getNameList(); + + if (m_depsCipherSuite.isBlackList()) + { + SSLEngine engine = ctx.createSSLEngine(); + ArrayList listDefaultCipher = new ArrayList(Arrays.asList(engine.getEnabledCipherSuites())); + + listDefaultCipher.removeAll(listCipher); + listCipher = listDefaultCipher; + } + + deps.setEnabledCipherSuites((String[]) listCipher.toArray(new String[listCipher.size()])); + } + + if (m_depsProtocolVersion != null) + { + List listProtocol = m_depsProtocolVersion.getNameList(); + + if (m_depsProtocolVersion.isBlackList()) + { + SSLEngine engine = ctx.createSSLEngine(); + ArrayList listDefaultProtocols = new ArrayList(Arrays.asList(engine.getEnabledProtocols())); + + listDefaultProtocols.removeAll(listProtocol); + listProtocol = listDefaultProtocols; + } + + deps.setEnabledProtocolVersions((String[]) listProtocol.toArray(new String[listProtocol.size()])); + } + + deps.setDelegateSocketProviderBuilder(m_bldrDelegateSocketProvider); + + String sAuth = aKeyManager == null && aTrustManager == null + ? "none" + : aKeyManager == null && aTrustManager != null + ? "one-way client" + : aKeyManager != null && aTrustManager == null ? "one-way server" : "two-way"; + + deps.setDescription(sbDesc.insert(0, "SSLSocketProvider(auth=" + sAuth + ", ").append(')').toString()); + CacheFactory.log("instantiated SSLSocketProviderDependencies: " + sbDesc.toString(), Base.LOG_DEBUG); + m_fRealized = true; + } + catch (GeneralSecurityException e) + { + throw new IllegalArgumentException("Invalid configuration ", e); + } + catch (IOException e) + { + throw Base.ensureRuntimeException(e); + } + + return deps; + } + + // ----- ParameterizedBuilder methods ------------------------------------ + + /** + * Realize {@link SSLSocketProviderDefaultDependencies} from this builder + * + * @param resolver a resolver + * @param loader class loader + * @param listParameters parameter list + * + * @return SSLSocketProviderDefaultDependencies + */ + @Override + public SSLSocketProviderDefaultDependencies realize(ParameterResolver resolver, ClassLoader loader, + ParameterList listParameters) + { + return realize(); + } + + // ----- helpers --------------------------------------------------------- + + /** + * Utility method for loading a keystore. + * + * @param sURL the URL of the keystore to load + * @param passwordProvider the opitonal password for the keystore (passwordProvider) + * @param sType the keystore type + * + * @throws GeneralSecurityException on keystore access error + * @throws IOException on I/O error + * + * @return the keystore + */ + private KeyStore loadKeyStore(String sURL, PasswordProvider passwordProvider, String sType) + throws GeneralSecurityException, IOException + { + if (sURL == null || sURL.length() == 0) + { + return null; + } + + KeyStore keyStore = KeyStore.getInstance(sType); + InputStream in = null; + char[] achPassword = null; + + try + { + ClassLoader loader = this.getClass().getClassLoader(); + + in = loader.getResourceAsStream(new URL(sURL).getFile()); + + if (in == null) + { + in = new URL(sURL).openStream(); + } + + achPassword = passwordProvider.get(); + + keyStore.load(in, achPassword); + } + finally + { + if (in != null) + { + try + { + in.close(); + } + catch (IOException e) + { + } + } + //Zero the password[] + if (achPassword != null) + { + Arrays.fill(achPassword, '0'); + } + } + + return keyStore; + } + + // ----- inner classes --------------------------------------------------- + + /** + * key-store configuration + */ + public interface KeystoreDependencies + { + /** + * get URL + * + * @return url + */ + public String getURL(); + + /** + * get key-store type, defaults to JKS + * + * @return get key-store type + */ + public String getType(); + + /** + * Get passwordProvider for this Manager. + * + * @return passwordProvider for this Manager. + */ + PasswordProvider getPasswordProvider(); + } + + /** + * trust-manager or identity-manager configuration + */ + public interface ManagerDependencies + { + /** + * Get algorithm name for this {@link ManagerDependencies} + * + * @return algorithm name + */ + String getAlgorithm(); + + /** + * Get provider builder for this {@link ManagerDependencies} + * + * @return provider builder + */ + ProviderBuilder getProviderBuilder(); + + /** + * Get {@link KeystoreDependencies} for this {@link ManagerDependencies} + * + * @return {@link KeystoreDependencies} representing configured/defaulted values for keystore + */ + DefaultKeystoreDependencies getKeystoreDependencies(); + + /** + * Get passwordProvider for this Manager. + * + * @return passwordProvider for this Manager. + */ + PasswordProvider getPasswordProvider(); + } + + /** + * key-store config and defaults + */ + public static class DefaultKeystoreDependencies + implements KeystoreDependencies + { + // ----- KeystoreDependencies methods -------------------------------- + + /** + * Get the configured/defaulted keystore url. + * + * @return the keystore url. + */ + @Override + public String getURL() + { + return m_sURL; + } + + /** + * Get the configured/defaulted keystore defaults. + * + * @return the keystore type + */ + @Override + public String getType() + { + return m_sType; + } + + /** + * Get the configured keystore passwordProvider. + * + * @return keystore passwordProvider. + */ + @Override + public PasswordProvider getPasswordProvider() + { + if (null == m_passProvider) + { + ParameterizedBuilder bldr = + PasswordProviderBuilderProcessor.getNullPasswordProviderBuilder(); + m_passProvider = bldr.realize(null, null, null); + } + return m_passProvider; + } + + // ----- DefaultKeystoreDependencies methods ------------------------- + + /** + * Set the keystore dependencies url. + * + * @param sURL keystore url + */ + @Injectable("url") + public void setURL(String sURL) + { + m_sURL = sURL; + } + + /** + * Set the keystore type. + * + * @param sType the keystore type + */ + @Injectable("type") + public void setType(String sType) + { + m_sType = sType; + } + + /** + * Set the keystore password using a PasswordProvider. + * + * @param sPassword the keystore password + */ + @Injectable("password") + public void setPassword(String sPassword) + { + ParameterizedBuilder bldr = + PasswordProviderBuilderProcessor.getPasswordProviderBuilderForPasswordStr(sPassword); + m_passProvider = bldr.realize(null, null, null); + } + + /** + * Set the keystore password-provider + * + * @param bldrPassProvider the keystore password provider + */ + @Injectable("password-provider") + public void setPasswordProvider(ParameterizedBuilder bldrPassProvider) + { + ParameterizedBuilder bldr = + bldrPassProvider == null + ? PasswordProviderBuilderProcessor.getNullPasswordProviderBuilder() + : bldrPassProvider; + m_passProvider = bldr.realize(null, null, null); + } + + // ----- data members ------------------------------------------------ + + /** + * passwordProvider for keyStore to fetch password + */ + private PasswordProvider m_passProvider; + + /** + * keystore url + */ + private String m_sURL; + + /** + * keystore type + */ + private String m_sType = SSLSocketProviderDefaultDependencies.DEFAULT_KEYSTORE_TYPE; + } + + /** + * Represents either identity-manager or trust-manager config and defaults. + */ + static public class DefaultManagerDependencies + implements ManagerDependencies + { + // ----- constructors ------------------------------------------------ + + /** + * Constructs {@link DefaultManagerDependencies} + * + * @param sNameManagerKind either identity-manager or trust-manager + */ + public DefaultManagerDependencies(String sNameManagerKind) + { + f_sNameManagerKind = sNameManagerKind; + } + + /** + * Get algorithm + * + * @return configured algorithm or default + */ + @Override + public String getAlgorithm() + { + if (m_sAlgorithm == null) + { + // compute default + if (f_sNameManagerKind.equals("trust-manager")) + { + m_sAlgorithm = SSLSocketProviderDefaultDependencies.DEFAULT_TRUST_ALGORITHM; + } + else if (f_sNameManagerKind.equals("identity-manager")) + { + m_sAlgorithm = SSLSocketProviderDefaultDependencies.DEFAULT_IDENTITY_ALGORITHM; + } + else + { + throw new IllegalArgumentException("unknown manager: " + f_sNameManagerKind + "; expected either identity-manager or trust-manager"); + } + } + return m_sAlgorithm; + } + + /** + * get key-store provider builder + * + * @return provider builder + */ + @Override + public ProviderBuilder getProviderBuilder() + { + return m_bldrProvider; + } + + /** + * get manager keystore dependencies + * + * @return key-store dependencies + */ + @Override + public DefaultKeystoreDependencies getKeystoreDependencies() + { + return m_depsKeystore; + } + + /** + * Get the configured keystore passwordProvider. + * + * @return keystore passwordProvider. + */ + @Override + public PasswordProvider getPasswordProvider() + { + if (null == m_passProvider) + { + ParameterizedBuilder bldr = + PasswordProviderBuilderProcessor.getNullPasswordProviderBuilder(); + m_passProvider = bldr.realize(null, null, null); + } + return m_passProvider; + } + + /** + * set key-store password using a PasswordProvider + * + * @param sPassword password + */ + @Injectable("password") + public void setPassword(String sPassword) + { + ParameterizedBuilder bldr = + PasswordProviderBuilderProcessor.getPasswordProviderBuilderForPasswordStr(sPassword); + m_passProvider = bldr.realize(null, null, null); + } + + /** + * set key-store password-provider + * + * @param bldrPasswordProvider password-provider builder + */ + @Injectable("password-provider") + public void setPasswordProvider(ParameterizedBuilder bldrPasswordProvider) + { + ParameterizedBuilder bldr = + bldrPasswordProvider == null + ? PasswordProviderBuilderProcessor.getNullPasswordProviderBuilder() + : bldrPasswordProvider; + m_passProvider = bldr.realize(null, null, null); + } + + /** + * set key-store algorithm + * + * @param sAlgorithm algorithm + */ + @Injectable("algorithm") + public void setAlgorithm(String sAlgorithm) + { + this.m_sAlgorithm = sAlgorithm; + } + + /** + * set key-store dependencies + * + * @param deps key-store configured and defaulted dependencies + */ + @Injectable("key-store") + public void setKeystore(DefaultKeystoreDependencies deps) + { + m_depsKeystore = deps; + } + + /** + * set manager provider builder + * + * @param m_bldrProvider provider builder + */ + @Injectable("provider") + public void setProviderBuilder(ProviderBuilder m_bldrProvider) + { + this.m_bldrProvider = m_bldrProvider; + } + + // ----- constants ------------------------------------------------------- + + /** + * Either identity-manager or trust-manager. + */ + private final String f_sNameManagerKind; + + // ----- data members ---------------------------------------------------- + + private ProviderBuilder m_bldrProvider; + private DefaultKeystoreDependencies m_depsKeystore; + private String m_sAlgorithm; + private PasswordProvider m_passProvider; + } + + /** + * Provider dependencies + */ + static public class ProviderBuilder + implements ParameterizedBuilder + { + /** + * Provider name + * + * @param sName named provided + */ + @Injectable("name") + public void setName(String sName) + { + m_sName = sName; + if (! m_fRegisteredCoherenceSecurityProvider && "CoherenceSecurityProvider".equals(sName)) + { + // make sure the Coherence security provider is loaded since it has been referenced. + SecurityProvider.ensureRegistration(); + } + } + + /** + * Referenced provider name + * + * @return provider name + */ + public String getName() + { + return m_sName; + } + + /** + * Customized provider builder + * + * @param builder provider builder + */ + @Injectable("provider") + public void setBuilder(ParameterizedBuilder builder) + { + m_builder = builder; + } + + /** + * {@inheritDoc} + */ + @Override + public Provider realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return m_builder == null ? null : m_builder.realize(resolver, loader, listParameters); + } + + // ----- data members ------------------------------------------------ + + private String m_sName; + private ParameterizedBuilder m_builder; + static private boolean m_fRegisteredCoherenceSecurityProvider = false; + } + + /** + * SSL encipher-suites and protocol-versions are both a list of names with a usage attribute of the value "white-list" or "black-list" + */ + static public class NameListDependencies + { + // ----- constructors ---------------------------------------------------- + + public NameListDependencies(String sDescription) + { + f_sDescription = sDescription; + } + + public static enum USAGE + { + WHITE_LIST("white-list"), + BLACK_LIST("black-list"); + + private USAGE(String s) + { + f_value = s; + } + + public String toString() + { + return f_value; + } + + public static USAGE myValueOf(String v) + { + if ("white-list".equals(v)) + { + return WHITE_LIST; + } + else if ("black-list".equals(v)) + { + return BLACK_LIST; + } + else + { + throw new IllegalArgumentException("unknown usage value of " + v + "; expected either \"white-list\" or \"black-list\""); + } + } + + public boolean equalsName(String otherName) + { + return (otherName == null) ? false : f_value.equals(otherName); + } + + // ----- constants --------------------------------------------------- + + private final String f_value; + }; + + // ----- NameListDependencies methods ---------------------------------------- + + + public void add(String sName) + { + m_lstNames.add(sName); + } + + public List getNameList() + { + return m_lstNames; + } + + public void setUsage(String v) + { + m_usage = USAGE.myValueOf(v); + } + + public boolean isBlackList() + { + return m_usage == USAGE.BLACK_LIST; + } + + // ----- constants ------------------------------------------------------- + + final String f_sDescription; + + static public final USAGE USAGE_DEFAULT = USAGE.WHITE_LIST; + + // ----- data members ---------------------------------------------------- + + private List m_lstNames = new LinkedList<>(); + private USAGE m_usage = USAGE_DEFAULT; + } + + + // ----- data members ---------------------------------------------------- + + /** + * Delegate socket provider builder + */ + private SocketProviderBuilder m_bldrDelegateSocketProvider; + + /** + * Customized executor or default executors + */ + private ParameterizedBuilder m_bldrExecutor; + + /** + * Hostname verifier builder + */ + private ParameterizedBuilder m_bldrHostnameVerifier; + + /** + * Provider buidler + */ + private ProviderBuilder m_bldrProvider; + + /** + * Dependencies that are being built up. + */ + private SSLSocketProviderDefaultDependencies m_deps; + + /** + * cipher suites white-list, black-list or null to use defaults. + */ + private NameListDependencies m_depsCipherSuite; + + /** + * Identity manager config and defaults. + */ + private DefaultManagerDependencies m_depsIdentityManager; + + /** + * protocol versions white-list, black-list or null to use defaults. + */ + private NameListDependencies m_depsProtocolVersion; + + /** + * Trust manager config and/or defaults + */ + private ManagerDependencies m_depsTrustManager; + + /** + * Realize once since sensitive password data is cleared after dependencies are realized. + */ + private boolean m_fRealized; + + /** + * SSL Socket provider protocol. + */ + private String m_sNameProtocol; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ServiceBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ServiceBuilder.java new file mode 100644 index 0000000000000..92878bd8d1cb7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ServiceBuilder.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.Cluster; +import com.tangosol.net.Service; + +import com.tangosol.run.xml.XmlElement; + + +/** + * The ServiceBuilder interface is used by a builder that creates a Service. + * + * @author pfm 2011.12.14 + * @since Coherence 12.1.2 + */ +public interface ServiceBuilder + { + /** + * Return true if a running cluster is needed before using a service. + * + * @return {@code true} if a running cluster is needed before using a service + */ + public boolean isRunningClusterNeeded(); + + /** + * Return the scope name. + * + * @return the scope name + */ + public String getScopeName(); + + /** + * Return the XmlElement that may be used to realize a + * Service by the ServiceBuilder. + *

+ * Note: There's no guarantee an implementation of this interface + * will use the returned XmlElement. + * + * @return the XmlElement + */ + @Deprecated + public XmlElement getXml(); + + /** + * Set the XmlElement that may be used to realize a Service. + *

+ * Note: There's no guarantee an implementation of this interface + * will use the specified XmlElement. + * + * @param element the XmlElement + */ + @Deprecated + public void setXml(XmlElement element); + + /** + * Realize (ensure) a Service. The returned Service is fully + * configured and ready to be used. + * + * @param resolver the ParameterResolver + * @param loader the ClassLoader + * @param cluster the Cluster which will already be running if necessary + * + * @return the Service + */ + public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ServiceFailurePolicyBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ServiceFailurePolicyBuilder.java new file mode 100644 index 0000000000000..350095193e4e0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ServiceFailurePolicyBuilder.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.net.cluster.DefaultServiceFailurePolicy; + +import com.tangosol.net.ServiceFailurePolicy; + +import com.tangosol.run.xml.XmlElement; + +import static com.tangosol.internal.net.cluster.DefaultServiceFailurePolicy.POLICY_EXIT_CLUSTER; +import static com.tangosol.internal.net.cluster.DefaultServiceFailurePolicy.POLICY_EXIT_PROCESS; +import static com.tangosol.internal.net.cluster.DefaultServiceFailurePolicy.POLICY_LOGGING; + +/** + * Build a default or customized {@link ServiceFailurePolicy}. + *

+ * Defer configuration exception reporting until instantiated. + * + * @author jf 2015.02.24 + * @since Coherence 12.2.1 + */ +public class ServiceFailurePolicyBuilder + implements ParameterizedBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Create Defaults. + * + * @param nPolicy one of {@link DefaultServiceFailurePolicy#POLICY_EXIT_CLUSTER}, {@link DefaultServiceFailurePolicy#POLICY_EXIT_PROCESS} or + * {@link DefaultServiceFailurePolicy#POLICY_LOGGING} + */ + public ServiceFailurePolicyBuilder(int nPolicy) + { + m_xmlConfig = null; + m_builder = null; + + String sPolicy; + + switch (nPolicy) + { + case POLICY_EXIT_CLUSTER : + sPolicy = "exit-cluster"; + break; + + case POLICY_EXIT_PROCESS : + sPolicy = "exit-process"; + break; + + case POLICY_LOGGING : + sPolicy = "logging"; + break; + + default : + sPolicy = "unknown default ServiceFailurePolicy number " + nPolicy; + } + + m_sPolicyDefault = sPolicy; + } + + /** + * {@link ServiceFailurePolicy} constructor for customized builder. + * @param builder customized builder + * @param xmlConfig optional configuration element for reporting configuration error. + */ + public ServiceFailurePolicyBuilder(ParameterizedBuilder builder, XmlElement xmlConfig) + { + m_builder = builder; + m_sPolicyDefault = null; + m_xmlConfig = xmlConfig; + } + + /** + * Default ServiceFailurePolicy from Xml value in configuration <service-failure-policy>. + * + * @param sPolicy default ServiceFailurePolicy {@link com.tangosol.internal.net.cluster.DefaultServiceFailurePolicy} + * @param xmlConfig optional configuration element for reporting configuration error. + */ + public ServiceFailurePolicyBuilder(String sPolicy, XmlElement xmlConfig) + { + m_builder = null; + m_sPolicyDefault = sPolicy; + m_xmlConfig = xmlConfig; + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ServiceFailurePolicy realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + if (m_builder == null) + { + int nPolicyType; + + switch (m_sPolicyDefault) + { + case "exit-cluster" : + + nPolicyType = DefaultServiceFailurePolicy.POLICY_EXIT_CLUSTER; + break; + + case "exit-process" : + + nPolicyType = DefaultServiceFailurePolicy.POLICY_EXIT_PROCESS; + break; + + case "logging" : + + nPolicyType = DefaultServiceFailurePolicy.POLICY_LOGGING; + break; + + default : + { + StringBuilder sb = new StringBuilder(); + + sb.append("Unknown default service failure policy: :").append(m_sPolicyDefault); + + if (m_xmlConfig != null) + { + sb.append(" in [").append(m_xmlConfig).append("]."); + } + + throw new ConfigurationException(sb.toString(), + "Please specify a default ServiceFailurePolicy value of exit-cluster, exit-process or logging"); + } + } + + return new DefaultServiceFailurePolicy(nPolicyType); + } + else + { + try + { + return m_builder.realize(resolver, loader, listParameters); + } + catch (ClassCastException e) + { + StringBuilder sb = new StringBuilder(); + + sb.append("Invalid customized ServiceFailurePolicy class ") + .append(m_builder.getClass().getCanonicalName()); + + if (m_xmlConfig != null) + { + sb.append(" in [").append(m_xmlConfig).append("]"); + } + + throw new ConfigurationException(sb.toString(), + "Provide a customized class that implements ServiceFailurePolicy ", e); + } + } + + } + + // ----- constants ------------------------------------------------------ + + /** + * Default service failure policy. Null if customized builder provided. + */ + private final String m_sPolicyDefault; + + /** + * Customized Builder. Null if Default service failure policy provided. + */ + private final ParameterizedBuilder m_builder; + + /** + * Optional xml configuration. Used in ConfigurationException message if non-null. + */ + private final XmlElement m_xmlConfig; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ServiceLoadBalancerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ServiceLoadBalancerBuilder.java new file mode 100644 index 0000000000000..93a58301dcf31 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/ServiceLoadBalancerBuilder.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.ServiceLoadBalancer; + +import com.tangosol.run.xml.XmlElement; + +/** + * {@link ServiceLoadBalancerBuilder} defers evaluating configuration parameters + * until ServiceLoadBalancer is instantiated. + * + * @author jf 2015.02.10 + * @since Coherence 12.2.1 + */ +public abstract class ServiceLoadBalancerBuilder + implements ParameterizedBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs {@link ServiceLoadBalancerBuilder} + * + * @param builder optional customization of ServiceLoadBalancer. if null, use {@link #getDefaultLoadBalancer}. + */ + public ServiceLoadBalancerBuilder(ParameterizedBuilder builder, XmlElement xmlConfig) + { + m_builder = builder; + f_xmlConfig = xmlConfig; + } + + // ----- ParameterizedBuilder methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ServiceLoadBalancer realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return m_builder == null ? getDefaultLoadBalancer() : m_builder.realize(resolver, loader, listParameters); + } + + // ----- ServiceLoaderBalancerBuilder methods --------------------------- + + /** + * Use this {@link ServiceLoadBalancer} when a customized ServiceLoadBalancer is not provided. + * + * @return default ServiceLoadBalancer + */ + abstract public ServiceLoadBalancer getDefaultLoadBalancer(); + + // ----- data members --------------------------------------------------- + + /** + * Customized ServiceLoadBalancerBuilder. + */ + protected ParameterizedBuilder m_builder = null; + + /** + * Xml Configuration Element to use to report ConfigurationException. + * This element is optional. + */ + protected final XmlElement f_xmlConfig; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SimpleParameterizedBuilderRegistry.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SimpleParameterizedBuilderRegistry.java new file mode 100644 index 0000000000000..f2301a27ad32a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SimpleParameterizedBuilderRegistry.java @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.oracle.coherence.common.base.Disposable; + +import com.tangosol.net.security.LocalPermission; + +import com.tangosol.util.Base; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * A basic implementation of a {@link ParameterizedBuilderRegistry}. + * + * @author bo 2014.10.27 + * + * @since Coherence 12.1.3 + */ +public class SimpleParameterizedBuilderRegistry + implements ParameterizedBuilderRegistry + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link SimpleParameterizedBuilderRegistry}. + */ + public SimpleParameterizedBuilderRegistry() + { + f_mapBuilders = new ConcurrentHashMap<>(); + } + + /** + * Constructs a {@link SimpleParameterizedBuilderRegistry} given another one. + * + * @param registry the registry to copy + */ + public SimpleParameterizedBuilderRegistry(ParameterizedBuilderRegistry registry) + { + this(); + + for (Registration reg : registry) + { + registerBuilder(reg.getInstanceClass(), reg.getName(), reg.getBuilder()); + } + } + + // ----- SimpleParameterizedBuilderRegistry methods --------------------- + + /** + * Determine if the {@link ParameterizedBuilderRegistry} is empty (contains no registrations). + * + * @return true if the registry contains no registrations + */ + public boolean isEmpty() + { + return f_mapBuilders.isEmpty(); + } + + // ----- ParameterizedBuilderRegistry interface ------------------------- + + @Override + public synchronized void dispose() + { + Map mapResource = f_mapBuilders; + + for (Map.Entry entry : mapResource.entrySet()) + { + RegistryValue value = entry.getValue(); + + try + { + value.dispose(); + } + catch (RuntimeException e) + { + Base.log("Exception while disposing the " + entry.getKey().getName() + " builder: " + e); + Base.log(e); + } + } + + mapResource.clear(); + } + + @Override + public ParameterizedBuilder getBuilder(Class clzInstance) + { + RegistryValue value = f_mapBuilders.get(new RegistryKey(clzInstance)); + + return value == null ? null : (ParameterizedBuilder) value.getBuilder(); + } + + @Override + public ParameterizedBuilder getBuilder(Class clzInstance, String sBuilderName) + { + RegistryValue value = f_mapBuilders.get(new RegistryKey(clzInstance, sBuilderName)); + + return value == null ? null : (ParameterizedBuilder) value.getBuilder(); + } + + @Override + public String registerBuilder(Class clzInstance, ParameterizedBuilder builder) + throws IllegalArgumentException + { + return registerBuilder(clzInstance, DEFAULT_NAME, builder); + } + + @Override + public String registerBuilder(Class clzInstance, String sBuilderName, + ParameterizedBuilder builder) + throws IllegalArgumentException + { + SecurityManager security = System.getSecurityManager(); + if (security != null) + { + security.checkPermission(new LocalPermission("Service.registerResource")); + } + + synchronized (clzInstance) + { + // attempt to get an existing registration for the key + RegistryKey key = new RegistryKey(clzInstance, sBuilderName); + RegistryValue value = f_mapBuilders.get(key); + + if (value == null) + { + // register the builder as it's not in the registry + value = new RegistryValue(builder); + + f_mapBuilders.put(key, value); + + return sBuilderName; + } + else + { + throw new IllegalArgumentException(String.format( + "Can not register builder [%s] as [%s] of type [%s] is it already registered [%s]", builder, + sBuilderName, key.getInstanceClass(), value.getBuilder())); + } + } + } + + @Override + public Iterator iterator() + { + ArrayList listRegistrations = new ArrayList<>(f_mapBuilders.size()); + + for (Map.Entry entry : f_mapBuilders.entrySet()) + { + RegistryKey key = entry.getKey(); + RegistryValue value = entry.getValue(); + BuilderRegistration registration = new BuilderRegistration(key.getName(), key.getInstanceClass(), + value.getBuilder()); + + listRegistrations.add(registration); + } + + return listRegistrations.iterator(); + } + + // ----- inner classes -------------------------------------------------- + + /** + * An internal {@link Registration} implementation. + */ + protected class BuilderRegistration + implements Registration + { + /** + * Constructs a {@link BuilderRegistration} + * + * @param sBuilderName the name of the builder + * @param clzInstance the class of instances constructed by the builder + * @param bldr the builder + */ + public BuilderRegistration(String sBuilderName, Class clzInstance, ParameterizedBuilder bldr) + { + m_sBuilderName = sBuilderName; + m_clzInstance = clzInstance; + m_bldr = bldr; + } + + @Override + public String getName() + { + return m_sBuilderName; + } + + @Override + public Class getInstanceClass() + { + return m_clzInstance; + } + + @Override + public ParameterizedBuilder getBuilder() + { + return m_bldr; + } + + /** + * The name of the builder. + */ + private String m_sBuilderName; + + /** + * The class of instances produced by the builder. + */ + private Class m_clzInstance; + + /** + * The builder. + */ + private ParameterizedBuilder m_bldr; + } + + /** + * Key class for a registered resource. + */ + protected class RegistryKey + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a {@link RegistryKey}. Instances created + * with this constructor will return {@link Class#getName()} + * for {@link #getName()}. + * + * @param clz class of registered resource + */ + public RegistryKey(Class clz) + { + this(clz, clz.getName()); + } + + /** + * Construct a {@link RegistryKey}. + * + * @param clz class of instances produced by the registered builder + * @param sName name of registered builder + */ + public RegistryKey(Class clz, String sName) + { + if (clz == null) + { + throw new NullPointerException("ParameterizedBuilder class cannot be null"); + } + + if (sName == null) + { + throw new NullPointerException("ParameterizedBuilder name cannot be null"); + } + + m_clz = clz; + m_sName = sName; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the class of the instance produced by the builder. + * + * @return the instance class + */ + public Class getInstanceClass() + { + return m_clz; + } + + /** + * Return the builder name. + * + * @return the builder name + */ + public String getName() + { + return m_sName; + } + + // ----- Object methods --------------------------------------------- + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + + if (o == null || !Base.equals(getClass(), o.getClass())) + { + return false; + } + + RegistryKey that = (RegistryKey) o; + + return Base.equals(m_clz, that.m_clz) && Base.equals(m_sName, that.m_sName); + } + + @Override + public int hashCode() + { + int result = m_clz.hashCode(); + + result = 31 * result + m_sName.hashCode(); + + return result; + } + + // ----- data members ----------------------------------------------- + + /** + * The instance class. + */ + private Class m_clz; + + /** + * The builder name. + */ + private String m_sName; + } + + /** + * A holder for a {@link ParameterizedBuilder}. + */ + protected class RegistryValue + implements Disposable + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a {@link RegistryValue}. + * + * @param builder the registered builder + */ + public RegistryValue(ParameterizedBuilder builder) + { + if (builder == null) + { + throw new NullPointerException("Resource cannot be null"); + } + + m_builder = builder; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the registered builder. + * + * @return the registered builder + */ + public ParameterizedBuilder getBuilder() + { + return m_builder; + } + + // ----- interface Disposable --------------------------------------- + + @Override + public void dispose() + { + ParameterizedBuilder builder = m_builder; + + if (builder instanceof Disposable) + { + ((Disposable) builder).dispose(); + } + } + + // ----- data members ----------------------------------------------- + + /** + * The registered resource. + */ + private ParameterizedBuilder m_builder; + } + + // ----- data members --------------------------------------------------- + + /** + * The map of builders keyed by class and name. + */ + private final ConcurrentHashMap f_mapBuilders; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SocketProviderBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SocketProviderBuilder.java new file mode 100644 index 0000000000000..3375a580ee809 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SocketProviderBuilder.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.oracle.coherence.common.net.SocketProvider; +import com.oracle.coherence.common.net.SSLSettings; +import com.oracle.coherence.common.net.SSLSocketProvider; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.DatagramSocketProvider; +import com.tangosol.net.SocketProviderFactory; + +/** + * {@link SocketProviderBuilder} enables lazy instantiation of SocketProvider. + * Builder includes methods that allows one to specify whether to get a datagram or demultiplexed + * {@link SocketProvider} and what subport to use for the socket provider. + * + * @author jf 2015.11.11 + * @since Coherence 12.2.1.1 + */ +public class SocketProviderBuilder implements ParameterizedBuilder + { + // ----- Constructors ---------------------------------------------------- + + /** + * Construct a {@link SocketProviderBuilder} from its definition id and its dependencies. + * + * @param sId provider definition id. {@link #UNNAMED_PROVIDER_ID} indicates an inlined anonymous socket provider + * @param deps SocketProvider dependencies + */ + public SocketProviderBuilder(String sId, SocketProviderFactory.Dependencies deps) + { + f_sId = sId; + f_deps = deps; + f_provider = null; + } + + /** + * Wrapper an existing {@link SocketProvider} into a Builder so it can be registered in cluster BuilderRegistry. + * + * @param provider a SocketProvider + */ + public SocketProviderBuilder(SocketProvider provider) + { + f_sId = null; + f_deps = null; + f_provider = provider; + } + + // ----- SocketProviderBuilder methods ----------------------------------- + + /** + * Return either an anonymous SocketProviderFactory dependencies for an inlined socket-provider or + * the global SocketProviderFactory dependencies initialized from cluster socket-providers definitions. + * + * @return {@link com.tangosol.net.SocketProviderFactory.Dependencies} for this builder + */ + public SocketProviderFactory.Dependencies getDependencies() + { + return f_deps; + } + + /** + * Return the identifier for SocketProvider built by this builder. + * + * @return the identifier for {@link SocketProvider} returned by this builder. + */ + public String getId() + { + return f_sId; + } + + /** + * Return a Demultiplexed Socket provider + * + * @param nSubport subport for demultiplexed socket provider. + * + * @return the provider + */ + public SocketProvider getDemultiplexedSocketProvider(int nSubport) + { + return f_deps.getSocketProviderFactory().getDemultiplexedSocketProvider(f_sId, f_deps, nSubport); + } + + /** + * Return an instance of the specified DatagramSocketProvider, creating it if necessary. + * + * @param nSubport subport for a demultiplexed socket provider. + * + * @return the provider + */ + public DatagramSocketProvider getDatagramSocketProvider(int nSubport) + { + return f_deps.getSocketProviderFactory().getDatagramSocketProvider(f_sId, f_deps, nSubport); + } + + + /** + * Return SSLSettings for {@link SocketProviderBuilder}. + * + * @return the sslSettings if the socket provider builder has a ssl settings directly or via delegate. + */ + public SSLSettings getSSLSettings() + { + SSLSocketProvider.Dependencies depsSSL = f_deps.getSSLDependencies(f_sId); + return depsSSL == null ? null : SocketProviderFactory.createSSLSettings(depsSSL); + } + + // ----- ParameterizedBuilder methods ------------------------------------ + + @Override + public SocketProvider realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return f_provider == null ? f_deps.getSocketProviderFactory().getSocketProvider(f_sId, f_deps, 0) : f_provider; + } + + // ----- constants ------------------------------------------------------- + + /** + * Default id for unnamed socket providers. + */ + public static String UNNAMED_PROVIDER_ID = SocketProviderFactory.UNNAMED_PROVIDER_ID; + + // ----- data members ---------------------------------------------------- + + /** + * SocketProvider definition id + */ + private final String f_sId; + + /** + * Either an anonymous SocketProviderFactory dependencies for an inlined socket-provider or + * the global SocketProviderFactory dependencies initialized from cluster socket-providers definitions. + */ + private final SocketProviderFactory.Dependencies f_deps; + + /** + * A Wrapped SocketProvider. + */ + private final SocketProvider f_provider; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/StaticFactoryInstanceBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/StaticFactoryInstanceBuilder.java new file mode 100644 index 0000000000000..54ebd880e8940 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/StaticFactoryInstanceBuilder.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.SimpleParameterList; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.Base; +import com.tangosol.util.ExternalizableHelper; + +import static com.tangosol.coherence.config.builder.ParameterizedBuilderHelper.getAssignableValue; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import javax.json.bind.annotation.JsonbProperty; + +/** + * A {@link StaticFactoryInstanceBuilder} is a {@link ParameterizedBuilder} + * that has been configured to realize objects based on the properties defined + * by an <instance> configuration element that uses the static + * <class-factory-name> approach. + * + * @author bo 2011.06.24 + * @since Coherence 12.1.2 + */ +public class StaticFactoryInstanceBuilder + implements ParameterizedBuilder, ParameterizedBuilder.ReflectionSupport, + ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link StaticFactoryInstanceBuilder}. + */ + public StaticFactoryInstanceBuilder() + { + m_exprFactoryClassName = new LiteralExpression("undefined"); + m_exprFactoryMethodName = new LiteralExpression("undefined"); + m_listMethodParameters = new SimpleParameterList(); + } + + // ----- StaticFactoryInstanceBuilder methods --------------------------- + + /** + * Sets the {@link Expression} that when evaluated will produce the name of the class + * containing a static factory method that will realize instances + * for this {@link ParameterizedBuilder}. + * + * @param exprFactoryClassName the {@link Expression} + */ + @Injectable("class-factory-name") + public void setFactoryClassName(Expression exprFactoryClassName) + { + m_exprFactoryClassName = exprFactoryClassName; + } + + /** + * Set the {@link Expression} that when evaluated will produce the name of the factory class + * static method that will realize instances for this {@link ParameterizedBuilder}. + * + * @param exprFactoryMethodName the {@link Expression} + */ + @Injectable("method-name") + public void setFactoryMethodName(Expression exprFactoryMethodName) + { + m_exprFactoryMethodName = exprFactoryMethodName; + } + + /** + * Sets the {@link ParameterList} to use to resolve factory method parameters when realizing the class. + * + * @param listParameters the {@link ParameterList} for method parameters + */ + @Injectable("init-params") + public void setFactoryMethodParameters(ParameterList listParameters) + { + if (listParameters != null) + { + m_listMethodParameters = listParameters; + } + } + + /** + * Ensures we have a non-null {@link ClassLoader} that we can use for loading classes. + * + * @param loader the proposed {@link ClassLoader}, which may be null + * + * @return a non-null {@link ClassLoader} + */ + protected ClassLoader ensureClassLoader(ClassLoader loader) + { + return loader == null ? getClass().getClassLoader() : loader; + } + + // ----- ParameterizedBuilder Interface -------------------------------- + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public T realize(ParameterResolver resolver, ClassLoader loader, ParameterList listMethodParameters) + { + try + { + // ensure we have a classloader + loader = ensureClassLoader(loader); + + // load the factory class + String sClassName = m_exprFactoryClassName.evaluate(resolver); + Class clzFactory = loader.loadClass(sClassName); + + // determine which parameter list we should use + ParameterList listParameters = listMethodParameters == null ? m_listMethodParameters : listMethodParameters; + + // find a static method with the required name and compatible parameter types + String sMethodName = m_exprFactoryMethodName.evaluate(resolver); + int cMethodParameters = listParameters.size(); + Method compatibleMethod = null; + Object[] aMethodParameters = cMethodParameters == 0 ? null : new Object[cMethodParameters]; + Method[] aMethods = clzFactory.getMethods(); + + for (int i = 0; i < aMethods.length && compatibleMethod == null; i++) + { + // only consider static methods with the required number of parameters, the required name and a return type + if (aMethods[i].getName().equals(sMethodName) + && aMethods[i].getParameterTypes().length == cMethodParameters + && Modifier.isStatic(aMethods[i].getModifiers()) && (aMethods[i].getReturnType() != null)) + { + // determine the compatible method parameter values + Class[] aMethodParameterTypes = aMethods[i].getParameterTypes(); + boolean fIsCompatible = true; + + try + { + int j = 0; + + for (Parameter parameter : listParameters) + { + aMethodParameters[j] = getAssignableValue(aMethodParameterTypes[j], parameter, resolver, + loader); + j++; + } + } + catch (Exception exception) + { + // this method is incompatible as parameter types don't match + fIsCompatible = false; + } + + if (fIsCompatible) + { + compatibleMethod = aMethods[i]; + } + } + } + + // did we find a compatible method? + if (compatibleMethod == null) + { + throw new NoSuchMethodException(String.format( + "Unable to find a compatible method for [%s] with the parameters [%s]", sMethodName, + listParameters)); + } + else + { + return (T)compatibleMethod.invoke(clzFactory, aMethodParameters); + } + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e); + } + } + + // ----- ParameterizedBuilder.ReflectionSupport interface --------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean realizes(Class clzClass, ParameterResolver resolver, ClassLoader loader) + { + try + { + // ensure we have a classloader + loader = ensureClassLoader(loader); + + // attempt to factory load the class name, but don't initialize it + String sClassName = m_exprFactoryClassName.evaluate(resolver); + Class clzFactory = loader.loadClass(sClassName); + + // determine if there is a static method on the factory that has the correct name + // NOTE: we don't attempt to use the types to find an exact match as the parameters may not be provided) + String sMethodName = m_exprFactoryMethodName.evaluate(resolver); + Method compatibleMethod = null; + + for (Method method : clzFactory.getMethods()) + { + if (method.getName().equals(sMethodName) && Modifier.isStatic(method.getModifiers()) + && method.getReturnType() != null && clzClass.isAssignableFrom(method.getReturnType())) + { + compatibleMethod = method; + + break; + } + } + + return compatibleMethod != null; + } + catch (ClassNotFoundException e) + { + return false; + } + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + m_exprFactoryClassName = (Expression) ExternalizableHelper.readObject(in, null); + m_exprFactoryMethodName = (Expression) ExternalizableHelper.readObject(in, null); + m_listMethodParameters = (ParameterList) ExternalizableHelper.readObject(in, null); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + ExternalizableHelper.writeObject(out, m_exprFactoryClassName); + ExternalizableHelper.writeObject(out, m_exprFactoryMethodName); + ExternalizableHelper.writeObject(out, m_listMethodParameters); + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader reader) throws IOException + { + m_exprFactoryClassName = (Expression) reader.readObject(0); + m_exprFactoryMethodName = (Expression) reader.readObject(1); + m_listMethodParameters = (ParameterList) reader.readObject(2); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter writer) throws IOException + { + writer.writeObject(0, m_exprFactoryClassName); + writer.writeObject(1, m_exprFactoryMethodName); + writer.writeObject(2, m_listMethodParameters); + } + + // ----- data members --------------------------------------------------- + + /** + * An {@link Expression} that when evaluated will produce the name of a class + * providing a factory method to use when realizing instances for this {@link ParameterizedBuilder}. + */ + @JsonbProperty("exprFactoryClassName") + private Expression m_exprFactoryClassName; + + /** + * An {@link Expression} that when evaluated will produce the name of the static + * method on the factory class that can be used to realize instances for this {@link ParameterizedBuilder}. + */ + @JsonbProperty("exprFactoryMethodName") + private Expression m_exprFactoryMethodName; + + /** + * The {@link ParameterList} to use for resolving {@link Parameter}s and calling the factory method. + */ + @JsonbProperty("methodParameters") + private ParameterList m_listMethodParameters; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SubscriberGroupBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SubscriberGroupBuilder.java new file mode 100644 index 0000000000000..36c9eecef8141 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/SubscriberGroupBuilder.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterMacroExpression; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.net.topic.Subscriber; + +/** + * The {@link SubscriberGroupBuilder} builds a {@link Subscriber} group. + * + * @author jf 2016.03.02 + * @since Coherence 14.1.1 + */ +public class SubscriberGroupBuilder + { + // ----- SubscriberGroupBuilder methods --------------------------------- + + /** + * Realize a durable {@link Subscriber} for the named group. + * + * @param topic topic to create subscriber for + * @param resolver resolve values containing parameter macros within this builder + * + * @return {@link Subscriber} + */ + @SuppressWarnings("unchecked") + public Subscriber realize(NamedTopic topic, ParameterResolver resolver) + { + return topic.createSubscriber(Subscriber.Name.of(getSubscriberGroupName(resolver))); + } + + /** + * Set the subscriber group name. + * + * @param sName durable subscriber name, possibly containing parameter macro {topic-name} + */ + @Injectable("name") + public void setSubscriberGroupName(String sName) + { + m_exprGroupName = new ParameterMacroExpression(sName, String.class); + } + + /** + * Get the subscriber group name. + * + * @param resolver used to resolve {topic-name} parameter macro if present. + * + * @return parameter macro expanded durable subscriber name + */ + public String getSubscriberGroupName(ParameterResolver resolver) + { + return m_exprGroupName.evaluate(resolver); + } + + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return "SubscriberGroupBuilder : groupName=" + m_exprGroupName; + } + + // ----- data members --------------------------------------------------- + + /** + * Subscriber group name allowing for parameter macro expansion of {topic-name}. + */ + private ParameterMacroExpression m_exprGroupName; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/UnitCalculatorBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/UnitCalculatorBuilder.java new file mode 100644 index 0000000000000..0cf7fa7078420 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/UnitCalculatorBuilder.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.ParameterResolver; + + +import com.tangosol.net.cache.ConfigurableCacheMap.UnitCalculator; +import com.tangosol.net.cache.LocalCache; + + +/** + * The {@link UnitCalculatorBuilder} class builds a {@link UnitCalculator}. + * + * @author pfm 2012.01.07 + * @since Coherence 12.1.2 + */ +public class UnitCalculatorBuilder + extends DefaultBuilderCustomization + implements ParameterizedBuilder + { + // ----- UnitCalculatorBuilder methods --------------------------------- + + /** + * Return the {@link UnitCalculator} type. + * + * @param resolver the {@link ParameterResolver} + * + * @return the type of {@link UnitCalculator} + */ + public String getUnitCalculatorType(ParameterResolver resolver) + { + return m_exprCalculator.evaluate(resolver); + } + + /** + * Set the {@link UnitCalculator} type. + * + * @param expr the {@link UnitCalculator} type + */ + @Injectable + public void setUnitCalculatorType(Expression expr) + { + m_exprCalculator = expr; + } + + // ----- ParameterizedBuilder methods ---------------------------------- + + /** + * {@inheritDoc} + */ + public boolean realizes(Class clzClass, ParameterResolver resolver, ClassLoader loader) + { + return getClass().isAssignableFrom(clzClass); + } + + /** + * {@inheritDoc} + */ + public UnitCalculator realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + UnitCalculator calculator = null; + ParameterizedBuilder bldr = getCustomBuilder(); + + if (bldr == null) + { + // use a built-in calculator + String sType = getUnitCalculatorType(resolver); + + if (sType.equalsIgnoreCase("FIXED")) + { + calculator = LocalCache.INSTANCE_FIXED; + } + else if (sType.equalsIgnoreCase("BINARY")) + { + calculator = LocalCache.INSTANCE_BINARY; + } + } + else + { + // create a custom calculator + calculator = bldr.realize(resolver, loader, listParameters); + } + + return calculator; + } + + // ----- data members --------------------------------------------------- + + /** + * The UnitCalculator type. + */ + private Expression m_exprCalculator = new LiteralExpression("FIXED"); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/WrapperSocketAddressProviderBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/WrapperSocketAddressProviderBuilder.java new file mode 100644 index 0000000000000..9281356137bbb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/WrapperSocketAddressProviderBuilder.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.AddressProvider; +import com.tangosol.net.SocketAddressProvider; +import com.tangosol.net.internal.WrapperSocketAddressProvider; + +/** + * The WrapperSocketAddressProviderBuilder wraps an AddressProviderBuilder so + * that it can produce a SocketAddressProvider. + * + * @author pfm 2013.09.13 + * @since Coherence 12.1.3 + */ +public class WrapperSocketAddressProviderBuilder + implements ParameterizedBuilder + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a WrapperSocketAddressProviderBuilder. + * + * @param bldr the AddressProviderBuilder to wrap + */ + public WrapperSocketAddressProviderBuilder(AddressProviderBuilder bldr) + { + if (bldr == null) + { + throw new IllegalArgumentException("The AddressProviderBuilder cannot be null"); + } + + m_bldrInner = bldr; + } + + // ----- ParameterizedBuilder interface -------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public SocketAddressProvider realize(ParameterResolver resolver, + ClassLoader loader, ParameterList listParameters) + { + AddressProvider provider = m_bldrInner.realize(resolver, loader, listParameters); + + if (isEphemeral()) + { + return WrapperSocketAddressProvider + .createEphemeralSubPortSocketAddressProvider(provider); + } + else + { + // Must use Wrapper since it forces address to be InetSocketAddress32 (rather than InetSocketAddress). + // Oracle commons MultiplexedSocketProvider will reject the address if it is not InetSocketAddress32. + return provider instanceof WrapperSocketAddressProvider + ? provider + : new WrapperSocketAddressProvider(provider); + } + } + + + /** + * Set the flag indicating that the addresses should be ephemeral. + * + * @param fEphemeral use an ephemeral address + * + * @return this object + */ + public WrapperSocketAddressProviderBuilder setEphemeral(boolean fEphemeral) + { + m_fEphemeral = fEphemeral; + + return this; + } + + /** + * Return the flag indicating that the addresses should be ephemeral. + * + * @return this object + */ + public boolean isEphemeral() + { + return m_fEphemeral; + } + + // ----- data members --------------------------------------------------- + + /** + * The AddressProvider builder. + */ + private AddressProviderBuilder m_bldrInner; + + /** + * True if the address should be wrapped with a ephemeral provider. + */ + private boolean m_fEphemeral = false; + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/package.html new file mode 100644 index 0000000000000..06ba732e51975 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/package.html @@ -0,0 +1,6 @@ + +Defines the typical runtime configuration builders for the Coherence +configuration object model. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/AbstractNioManagerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/AbstractNioManagerBuilder.java new file mode 100644 index 0000000000000..74b1590e20129 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/AbstractNioManagerBuilder.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder.storemanager; + +import com.oracle.coherence.common.util.MemorySize; +import com.oracle.coherence.common.util.MemorySize.Magnitude; + +import com.tangosol.coherence.config.unit.Megabytes; +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.CacheFactory; + + +/** + * The AbstractNioManagerBuilder class is an abstract class used to build + * an NIO file manager or an NIO memory manager. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public abstract class AbstractNioManagerBuilder + extends AbstractStoreManagerBuilder + { + // ----- AbstractNioManagerBuilder methods ------------------------------ + + /** + * Return the initial buffer size in bytes. + * + * @param resolver the ParameterResolver + * + * @return the initial buffer size in bytes + */ + public long getInitialSize(ParameterResolver resolver) + { + return m_exprInitialSize.evaluate(resolver).getByteCount(); + } + + /** + * Set the initial buffer size. + * + * @param expr the initial buffer size + */ + @Injectable + public void setInitialSize(Expression expr) + { + m_exprInitialSize = expr; + } + + /** + * Return the maximum buffer size in bytes. + * + * @param resolver the ParameterResolver + * + * @return the maximum buffer size in bytes + */ + public long getMaximumSize(ParameterResolver resolver) + { + return m_exprMaxSize.evaluate(resolver).getByteCount(); + } + + /** + * Set the maximum buffer size. + * + * @param expr the maximum buffer size + */ + @Injectable + public void setMaximumSize(Expression expr) + { + m_exprMaxSize = expr; + } + + // ----- internal ------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void validate(ParameterResolver resolver) + { + super.validate(resolver); + + long cbMax = getMaximumSize(resolver); + long cbInit = getInitialSize(resolver); + + // bounds check: + // 1 <= cbInitSize <= cbMaxSize <= Integer.MAX_VALUE - 1023 + // (Integer.MAX_VALUE - 1023 is the largest integer multiple of 1024) + int cbMaxSize = (int) Math.min(Math.max(cbMax, 1L), (long) Integer.MAX_VALUE - 1023); + int cbInitSize = (int) Math.min(Math.max(cbInit, 1L), cbMaxSize); + + // warn about changes to configured values + if (cbInitSize != cbInit) + { + CacheFactory.log("Invalid NIO manager initial-size changed to: " + + cbInitSize + " bytes", CacheFactory.LOG_WARN); + } + if (cbMaxSize != cbMax) + { + CacheFactory.log("Invalid NIO manager maximum-size changed to: " + + cbMaxSize + " bytes", CacheFactory.LOG_WARN); + } + + m_exprMaxSize = new LiteralExpression(new Megabytes(new MemorySize(cbMaxSize, Magnitude.BYTES))); + m_exprInitialSize = new LiteralExpression(new Megabytes(new MemorySize(cbInitSize, Magnitude.BYTES))); + } + + // ----- data members --------------------------------------------------- + + /** + * The initial buffer size. + */ + private Expression m_exprInitialSize = new LiteralExpression(new Megabytes(1)); + + /** + * The maximum buffer size. + */ + private Expression m_exprMaxSize = new LiteralExpression(new Megabytes(1024)); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/AbstractStoreManagerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/AbstractStoreManagerBuilder.java new file mode 100755 index 0000000000000..435e43c29cb07 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/AbstractStoreManagerBuilder.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder.storemanager; + +import com.tangosol.coherence.config.builder.BuilderCustomization; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.expression.ParameterResolver; + + +/** + * The AbstractStoreManagerBuilder class builds an instance of a + * BinaryStoreManager. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public abstract class AbstractStoreManagerBuilder + implements BinaryStoreManagerBuilder, BuilderCustomization + { + // ----- BuilderCustomization methods ----------------------------------- + + /** + * {@inheritDoc} + */ + public ParameterizedBuilder getCustomBuilder() + { + return m_bldrCustom; + } + + /** + * {@inheritDoc} + */ + public void setCustomBuilder(ParameterizedBuilder bldr) + { + m_bldrCustom = bldr; + } + + // ----- internal ------------------------------------------------------- + + /** + * Validate the builder. + * + * @param resolver the ParameterResolver + */ + protected void validate(ParameterResolver resolver) + { + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link ParameterizedBuilder} used to build a custom store manager. + */ + private ParameterizedBuilder m_bldrCustom; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/AsyncStoreManagerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/AsyncStoreManagerBuilder.java new file mode 100644 index 0000000000000..d86cb3c3a4a0d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/AsyncStoreManagerBuilder.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder.storemanager; + +import com.oracle.coherence.common.util.MemorySize; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.unit.Bytes; +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.AsyncBinaryStoreManager; +import com.tangosol.io.BinaryStoreManager; + +import com.tangosol.util.Base; + +/** + * The AsyncStoreManagerBuilder class builds and instance of an + * AsyncBinaryStoreManager. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class AsyncStoreManagerBuilder + extends AbstractStoreManagerBuilder + implements BinaryStoreManagerBuilderCustomization + { + // ----- StoreManagerBuilder interface ---------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public BinaryStoreManager realize(ParameterResolver resolver, ClassLoader loader, boolean fPaged) + { + validate(resolver); + + int cbMaxAsync = (int) getAsyncLimit(resolver); + BinaryStoreManager asyncManager = null; + BinaryStoreManager storeManager = getBinaryStoreManagerBuilder().realize(resolver, loader, fPaged); + + ParameterizedBuilder bldrCustom = getCustomBuilder(); + + if (bldrCustom == null) + { + // create the default manager + asyncManager = cbMaxAsync <= 0 + ? new AsyncBinaryStoreManager(storeManager) + : new AsyncBinaryStoreManager(storeManager, cbMaxAsync); + } + else + { + // create the custom object that is implementing AsyncBinaryStoreManager. + ParameterList listArgs = new ResolvableParameterList(); + + listArgs.add(new Parameter("store-manager", storeManager)); + + if (cbMaxAsync <= 0) + { + listArgs.add(new Parameter("async-limit", cbMaxAsync)); + } + + asyncManager = bldrCustom.realize(resolver, loader, listArgs); + } + + return asyncManager; + } + + // ----- BinaryStoreManagerBuilderCustomization interface --------------- + + /** + * {@inheritDoc} + */ + public BinaryStoreManagerBuilder getBinaryStoreManagerBuilder() + { + return m_bldrStoreManager; + } + + /** + * {@inheritDoc} + */ + public void setBinaryStoreManagerBuilder(BinaryStoreManagerBuilder bldr) + { + m_bldrStoreManager = bldr; + } + + // ----- AsyncStoreManagerBuilder methods ------------------------------- + + /** + * Return the maximum number of bytes that are queued to be written + * asynchronously. + * + * @param resolver the ParameterResolver + * + * @return the memory limit + */ + public long getAsyncLimit(ParameterResolver resolver) + { + return m_exprAsyncLimit.evaluate(resolver).getByteCount(); + } + + /** + * Set the maximum number of bytes that are queued to be written + * asynchronously. + * + * @param expr the memory limit + */ + @Injectable + public void setAsyncLimit(Expression expr) + { + m_exprAsyncLimit = expr; + } + + // ----- internal ------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void validate(ParameterResolver resolver) + { + super.validate(resolver); + + Base.checkNotNull(getBinaryStoreManagerBuilder(), "StoreMangerBuilder"); + } + + // ----- data members --------------------------------------------------- + + /** + * The store manager builder being wrapped by this manager. + */ + private BinaryStoreManagerBuilder m_bldrStoreManager; + + /** + * The async limit. + */ + private Expression m_exprAsyncLimit = new LiteralExpression(new Bytes(new MemorySize("4MB"))); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/BdbStoreManagerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/BdbStoreManagerBuilder.java new file mode 100755 index 0000000000000..3add50092cd20 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/BdbStoreManagerBuilder.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder.storemanager; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.bdb.BerkeleyDBBinaryStoreManager; + +import com.tangosol.run.xml.SimpleElement; +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +import java.io.File; + +/** + * The BdbStoreManagerBuilder class builds an instance of a + * BerkeleyDBBinaryStoreManager. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class BdbStoreManagerBuilder + extends AbstractStoreManagerBuilder + { + // ----- StoreManagerBuilder interface ---------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public BerkeleyDBBinaryStoreManager realize(ParameterResolver resolver, ClassLoader loader, boolean fPaged) + { + validate(resolver); + + String sStoreName = getStoreName(resolver); + String sPath = getDirectory(resolver); + File fileDir = sPath.length() == 0 ? null : new File(sPath); + + try + { + ParameterizedBuilder bldrCustom = getCustomBuilder(); + + if (bldrCustom == null) + { + // create the default BDb manager + BerkeleyDBBinaryStoreManager bdbMgr = new BerkeleyDBBinaryStoreManager(fileDir, sStoreName); + + // load XML init params if they exist + ResolvableParameterList listParams = getInitParams(); + if (listParams != null && !listParams.isEmpty()) + { + XmlElement xmlInit = new SimpleElement("config"); + + for (Parameter param : listParams) + { + xmlInit.ensureElement(param.getName()).setString(param.evaluate(resolver).as(String.class)); + } + bdbMgr.setConfig(xmlInit); + } + + return bdbMgr; + } + else + { + // create the custom object that is implementing BinaryStoreManager. + // populate the relevant constructor arguments then create the cache + ParameterList listArgs = new ResolvableParameterList(); + + listArgs.add(new Parameter("fileDir", fileDir)); + listArgs.add(new Parameter("storeName", sStoreName)); + + return bldrCustom.realize(resolver, loader, listArgs); + } + } + catch (NoClassDefFoundError e) + { + String sMsg = "Berkeley DB JE libraries are required to utilize a " + + "'bdb-store-manager', visit www.sleepycat.com for additional information."; + + throw Base.ensureRuntimeException(e, sMsg); + } + } + + // ----- BdbStoreManagerBuilder methods --------------------------------- + + /** + * Return the path name for the root directory that the BDB file manager + * uses to store files in. If not specified or specifies a non-existent + * directory, a temporary file in the default location is used. + * + * @param resolver the ParameterResolver + * + * @return the root directory + */ + public String getDirectory(ParameterResolver resolver) + { + return m_exprDirectory.evaluate(resolver); + } + + /** + * Set the BDB root directory where BDB stores files. + * + * @param expr the directory name + */ + @Injectable + public void setDirectory(Expression expr) + { + m_exprDirectory = expr; + } + + /** + * Specifies the name for a database table that the Berkeley Database JE + * store manager uses to store data in. Specifying this parameter causes + * the bdb-store-manager to use non-temporary (persistent) database instances. + * This is intended only for local caches that are backed by a cache loader + * from a non-temporary store, so that the local cache can be pre-populated + * from the disk on startup. This setting should not be enabled with replicated + * or distributed caches. Normally, the store name should be left unspecified, + * indicating that temporary storage is to be used. + * + * @param resolver the ParameterResolver + * + * @return the store name + */ + public String getStoreName(ParameterResolver resolver) + { + return m_exprStoreName.evaluate(resolver); + } + + /** + * Set the BDB store (database table) name. + * + * @param expr the store name + */ + @Injectable + public void setStoreName(Expression expr) + { + m_exprStoreName = expr; + } + + /** + * Return the BDB init params needed to construct a BerkeleyDBBinaryStoreManager. + * + * @param resolver the ParameterResolver + * + * @return the init params + */ + public String getXmlInitParams(ParameterResolver resolver) + { + return m_exprXmlInitParams.evaluate(resolver); + } + + /** + * Set the BDB init params needed to construct a BerkeleyDBBinaryStoreManager. + * + * @param expr the XML init params + * + * @see com.sleepycat.je.EnvironmentConfig for je.* properties that can be configured. + */ + @Injectable + public void setXmlInitParams(Expression expr) + { + m_exprXmlInitParams = expr; + } + + /** + * Return the BDB init params needed to construct a BerkeleyDBBinaryStoreManager. + * + * @return the init params + */ + public ResolvableParameterList getInitParams() + { + return m_listResolvableInitParameters; + } + + /** + * Set the BDB init params needed to construct a BerkeleyDBBinaryStoreManager. + * + * @param listInitParams list of resolvable init-params + * + * @see com.sleepycat.je.EnvironmentConfig for je.* properties that can be configured. + */ + @Injectable("init-params") + public void setInitParams(ResolvableParameterList listInitParams) + { + m_listResolvableInitParameters = listInitParams; + } + + // ----- data members --------------------------------------------------- + + /** + * The directory. + */ + private Expression m_exprDirectory = new LiteralExpression(String.valueOf("")); + + /** + * The store name. + */ + private Expression m_exprStoreName = new LiteralExpression(String.valueOf("")); + + /** + * The init params in XML format. + */ + private Expression m_exprXmlInitParams = new LiteralExpression(String.valueOf("")); + + /** + * BDB parameters + */ + private ResolvableParameterList m_listResolvableInitParameters; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/BinaryStoreManagerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/BinaryStoreManagerBuilder.java new file mode 100644 index 0000000000000..25b8968a6e047 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/BinaryStoreManagerBuilder.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder.storemanager; + +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.io.BinaryStoreManager; + + +/** + * A {@link BinaryStoreManagerBuilder} is responsible for realizing {@link BinaryStoreManager}s. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public interface BinaryStoreManagerBuilder + { + /** + * Realize a {@link BinaryStoreManager} given the provided parameters. + * + * @param resolver the {@link ParameterResolver} for resolving expressions and runtime parameters + * @param loader the {@link ClassLoader} for loading classes (if necessary) + * @param fPaged the flag indicating whether the map is paged + * + * @return a {@link BinaryStoreManager} + */ + public BinaryStoreManager realize(ParameterResolver resolver, ClassLoader loader, boolean fPaged); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/BinaryStoreManagerBuilderCustomization.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/BinaryStoreManagerBuilderCustomization.java new file mode 100644 index 0000000000000..7ce62faf382a3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/BinaryStoreManagerBuilderCustomization.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder.storemanager; + +import com.tangosol.io.BinaryStoreManager; + +/** + * A {@link BinaryStoreManagerBuilderCustomization} class is one that allows or potentially requires, + * a {@link BinaryStoreManagerBuilder} for the purposes of realizing a {@link BinaryStoreManager}. + * + * @author bo 2012.02.10 + * @since Coherence 12.1.2 + */ +public interface BinaryStoreManagerBuilderCustomization + { + /** + * Obtains the {@link BinaryStoreManagerBuilder} for the {@link BinaryStoreManager}. + * + * @return the {@link BinaryStoreManagerBuilder} + */ + public BinaryStoreManagerBuilder getBinaryStoreManagerBuilder(); + + /** + * Sets the {@link BinaryStoreManagerBuilder} for the {@link BinaryStoreManager}. + * + * @param bldr the {@link BinaryStoreManagerBuilder} + */ + public void setBinaryStoreManagerBuilder(BinaryStoreManagerBuilder bldr); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/CustomStoreManagerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/CustomStoreManagerBuilder.java new file mode 100644 index 0000000000000..ae8570446834e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/CustomStoreManagerBuilder.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder.storemanager; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.BinaryStoreManager; + + +/** + * The CustomStoreManagerBuilder class builds an instance of a custom + * BinaryStoreManager. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class CustomStoreManagerBuilder + extends AbstractStoreManagerBuilder + { + /** + * {@inheritDoc} + */ + @Override + public BinaryStoreManager realize(ParameterResolver resolver, ClassLoader loader, boolean fPaged) + { + validate(resolver); + + ParameterizedBuilder bldr = getCustomBuilder(); + + return bldr.realize(resolver, loader, null); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/NioFileManagerBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/NioFileManagerBuilder.java new file mode 100644 index 0000000000000..17cccf00fd2ab --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/NioFileManagerBuilder.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.builder.storemanager; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.nio.MappedStoreManager; + +import java.io.File; + +/** + * The NioFileManagerBuilder class builds an instance of a MappedStoreManager. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class NioFileManagerBuilder + extends AbstractNioManagerBuilder + { + // ----- StoreManagerBuilder interface ---------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public MappedStoreManager realize(ParameterResolver resolver, ClassLoader loader, boolean fPaged) + { + validate(resolver); + + MappedStoreManager manager = null; + + int cbMaxSize = (int) getMaximumSize(resolver); + int cbInitialSize = (int) getInitialSize(resolver); + String sPath = getDirectory(resolver); + File fileDir = sPath.length() == 0 ? null : new File(sPath); + + ParameterizedBuilder bldrCustom = getCustomBuilder(); + + if (bldrCustom == null) + { + // create the NIO manager + manager = new MappedStoreManager(cbInitialSize, cbMaxSize, fileDir); + } + else + { + // create the custom object that is implementing MappedStoreManager. + ParameterList listArgs = new ResolvableParameterList(); + + listArgs.add(new Parameter("initial-size", cbInitialSize)); + listArgs.add(new Parameter("max-size", cbMaxSize)); + listArgs.add(new Parameter("fileDir", fileDir)); + manager = bldrCustom.realize(resolver, loader, listArgs); + } + + return manager; + } + + // ----- NioFileManagerBuilder methods ---------------------------------- + + /** + * Return the path name for the root directory that the manager uses to + * store files in. If not specified or specifies a non-existent directory, + * a temporary file in the default location is used. + * + * @param resolver the ParameterResolver + * + * @return the root directory + */ + public String getDirectory(ParameterResolver resolver) + { + return m_exprDirectory.evaluate(resolver); + } + + /** + * Set the root directory where the manager stores files. + * + * @param expr the directory name + */ + @Injectable + public void setDirectory(Expression expr) + { + m_exprDirectory = expr; + } + + // ----- data members --------------------------------------------------- + + /** + * The directory. + */ + private Expression m_exprDirectory = new LiteralExpression(String.valueOf("")); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/package.html new file mode 100644 index 0000000000000..7d0e5790ed582 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/builder/storemanager/package.html @@ -0,0 +1,5 @@ + +Defines external Store Manager Builder implementations for Coherence caches + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/package.html new file mode 100644 index 0000000000000..97a6cb6545071 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/package.html @@ -0,0 +1,6 @@ + +Defines the Coherence configuration object model for accessing, customizing +and or modifying configuration. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCachingScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCachingScheme.java new file mode 100644 index 0000000000000..354a99847dad7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCachingScheme.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.NullParameterResolver; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.injection.Injector; +import com.tangosol.config.injection.SimpleInjector; + +import com.tangosol.net.BackingMapManager; +import com.tangosol.net.CacheService; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.ExtensibleConfigurableCacheFactory; +import com.tangosol.net.NamedCache; +import com.tangosol.net.Service; + +import com.tangosol.net.ServiceDependencies; +import com.tangosol.net.cache.BundlingNamedCache; +import com.tangosol.util.Base; +import com.tangosol.util.MapListener; +import com.tangosol.util.ObservableMap; +import com.tangosol.util.ResourceResolver; +import com.tangosol.util.ResourceResolverHelper; + +import java.util.Map; + +/** + * An {@link AbstractCachingScheme} is a base implementation for an + * {@link CachingScheme}. + * + * @author pfm 2011.12.28 + * @since Coherence 12.1.2 + */ +public abstract class AbstractCachingScheme + extends AbstractServiceScheme + implements ObservableCachingScheme + { + // ----- NamedCacheBuilder interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public NamedCache realizeCache(ParameterResolver resolver, Dependencies dependencies) + { + validate(resolver); + + // Call ECFF to ensure the Service. CCF must be used to ensure the service, rather + // than the service builder. This is because ECCF.ensureService provides additional + // logic like injecting a BackingMapManager into the service and starting the Service. + Service service = + ((ExtensibleConfigurableCacheFactory) dependencies.getConfigurableCacheFactory()).ensureService(this); + + if (!(service instanceof CacheService)) + { + throw new IllegalArgumentException("Error: ensureCache is using service " + + service.getInfo().getServiceName() + "that is not a CacheService "); + } + + NamedCache cache = ((CacheService) service).ensureCache(dependencies.getCacheName(), + dependencies.getClassLoader()); + + // appropriately produce a BundlingNamedCache should bundling be supported + if (this instanceof BundlingScheme) + { + BundleManager mgrBundle = ((BundlingScheme) this).getBundleManager(); + + if (mgrBundle != null) + { + BundlingNamedCache cacheBundle = new BundlingNamedCache(cache); + + mgrBundle.ensureBundles(resolver, cacheBundle); + cache = cacheBundle; + } + } + + return cache; + } + + // ----- MapBuilder interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + throw new IllegalStateException("This scheme " + getSchemeName() + " cannot be used to create a Map"); + } + + // ----- BackingMapManagerBuilder interface ----------------------------- + + /** + * {@inheritDoc} + */ + @Override + public BackingMapManager realizeBackingMapManager(ConfigurableCacheFactory ccf) + { + if (ccf instanceof ExtensibleConfigurableCacheFactory) + { + return new ExtensibleConfigurableCacheFactory.Manager((ExtensibleConfigurableCacheFactory) ccf); + } + else + { + throw new IllegalArgumentException("The BackingMapManager cannot be must be instantiated" + + "with a given a ExtensibleConfigurableCacheFactory"); + } + } + + // ----- ObservableCachingScheme interface ------------------------------ + + /** + * {@inheritDoc} + */ + public ParameterizedBuilder getListenerBuilder() + { + return m_bldrListener; + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("rawtypes") + public void establishMapListeners(Map map, ParameterResolver resolver, Dependencies dependencies) + { + if (map instanceof ObservableMap && m_bldrListener != null) + { + Object oListener = m_bldrListener.realize(resolver, dependencies.getClassLoader(), null); + + if (oListener instanceof MapListener) + { + MapListener listener = (MapListener) oListener; + + // this is the ParameterResolver that will be used to resolve + // parameters that themselves reference other parameters + // (this could be the system-level default parameter resolver) + Injector injector = new SimpleInjector(); + ResourceResolver resourceResolver = + ResourceResolverHelper.resourceResolverFrom(ResourceResolverHelper.resourceResolverFrom(resolver, + getDefaultParameterResolver()), ResourceResolverHelper.resourceResolverFrom(dependencies)); + + listener = injector.inject(listener, resourceResolver); + + ObservableMap mapObservable = (ObservableMap) map; + + mapObservable.addMapListener(listener); + + if (dependencies.getMapListenersRegistry() != null) + { + dependencies.getMapListenersRegistry().put(mapObservable, listener); + } + } + else + { + throw new IllegalArgumentException("The specified MapListener [" + oListener + "] for the cache [" + + dependencies.getCacheName() + + "] does not implement the MapListener interface"); + } + } + } + + // ----- internal ------------------------------------------------------- + + /** + * Set the {@link ParameterizedBuilder} that builds a {@link MapListener}. + * + * @param bldr the {@link ParameterizedBuilder} + */ + @Injectable("listener") + public void setListenerBuilder(ParameterizedBuilder bldr) + { + m_bldrListener = bldr; + } + + /** + * Obtains the ParameterResolver to use when resolving parameters + * without one being available in a context. + * + * @return a ParameterResolver + */ + public ParameterResolver getDefaultParameterResolver() + { + return new NullParameterResolver(); + } + + /** + * Validate the properties. + * + * @param resolver the ParameterResolver needed to resolve expressions + */ + protected void validate(ParameterResolver resolver) + { + super.validate(); + + Base.checkNotNull(resolver, "ParameterResolver"); + } + + // ----- data members -------------------------------------------------- + + /** + * The {@link ParameterizedBuilder} for the {@link MapListener}. + */ + private ParameterizedBuilder m_bldrListener; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCompositeScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCompositeScheme.java new file mode 100644 index 0000000000000..d32876350a6ce --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractCompositeScheme.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.Base; + +/** + * The {@link AbstractCompositeScheme} manages a scheme that is used to + * build a composite cache consisting of a front map and a back cache/map. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public abstract class AbstractCompositeScheme + extends AbstractLocalCachingScheme + { + // ----- AbstractCompositeScheme methods -------------------------------- + + /** + * Return the front scheme. + * + * @return the front scheme + */ + public CachingScheme getFrontScheme() + { + return m_schemeFront; + } + + /** + * Set the front scheme. + * + * @param scheme the front scheme + */ + public void setFrontScheme(CachingScheme scheme) + { + m_schemeFront = scheme; + } + + /** + * Return the back scheme. + * + * @return the back scheme + */ + public CachingScheme getBackScheme() + { + return m_schemeBack; + } + + /** + * Set the back scheme. + * + * @param scheme the back scheme + */ + public void setBackScheme(CachingScheme scheme) + { + m_schemeBack = scheme; + } + + // ----- internal ------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void validate(ParameterResolver resolver) + { + super.validate(resolver); + + Base.checkNotNull(getBackScheme(), "BackScheme"); + Base.checkNotNull(getFrontScheme(), "FrontScheme"); + } + + // ----- data members --------------------------------------------------- + + /** + * The front scheme. + */ + private CachingScheme m_schemeFront; + + /** + * The back scheme. + */ + private CachingScheme m_schemeBack; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractJournalScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractJournalScheme.java new file mode 100755 index 0000000000000..f3fb3e03a0d18 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractJournalScheme.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.EvictionPolicyBuilder; +import com.tangosol.coherence.config.builder.UnitCalculatorBuilder; +import com.tangosol.coherence.config.unit.Seconds; +import com.tangosol.coherence.config.unit.Units; + +import com.tangosol.config.annotation.Injectable; + +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.ParameterResolver; + +/** + * The {@link AbstractJournalScheme} contains functionality common to all + * Journal schemes. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public abstract class AbstractJournalScheme + extends AbstractLocalCachingScheme + { + /** + * Return the EvictionPolicyBuilder used to build an EvictionPolicy. + * + * @return the builder + */ + public EvictionPolicyBuilder getEvictionPolicyBuilder() + { + return m_bldrEvictionPolicy; + } + + /** + * Set the EvictionPolicyBuilder. + * + * @param bldr the EvictionPolicyBuilder + */ + @Injectable("eviction-policy") + public void setEvictionPolicyBuilder(EvictionPolicyBuilder bldr) + { + m_bldrEvictionPolicy = bldr; + } + + /** + * Return the amount of time since the last update that entries + * are kept by the cache before being expired. Entries that have expired + * are not accessible and are evicted the next time a client accesses the + * cache. Any attempt to read an expired entry results in a reloading of + * the entry from the CacheStore. + * + * @param resolver the ParameterResolver + * + * @return the expiry delay + */ + public Seconds getExpiryDelay(ParameterResolver resolver) + { + return m_exprExpiryDelay.evaluate(resolver); + } + + /** + * Set the expiry delay. + * + * @param expr the expiry delay expression + */ + @Injectable + public void setExpiryDelay(Expression expr) + { + m_exprExpiryDelay = expr; + } + + /** + * Return the limit of cache size. Contains the maximum number of units + * that can be placed n the cache before pruning occurs. An entry is the + * unit of measurement, unless it is overridden by an alternate unit-calculator. + * When this limit is exceeded, the cache begins the pruning process, + * evicting entries according to the eviction policy. Legal values are + * positive integers or zero. Zero implies no limit. + * + * @param resolver the ParameterResolver + * + * @return the high units + */ + public Units getHighUnits(ParameterResolver resolver) + { + return m_exprHighUnits.evaluate(resolver); + } + + /** + * Set the high units. + * + * @param expr the high units expression + */ + @Injectable + public void setHighUnits(Expression expr) + { + m_exprHighUnits = expr; + } + + /** + * Return the lowest number of units that a cache is pruned down to when + * pruning takes place. A pruning does not necessarily result in a cache + * containing this number of units, however a pruning never results in a + * cache containing less than this number of units. An entry is the unit + * of measurement, unless it is overridden by an alternate unit-calculator. + * When pruning occurs entries continue to be evicted according to the + * eviction policy until this size. Legal values are positive integers or + * zero. Zero implies the default. The default value is 75% of the high-units + * setting (that is, for a high-units setting of 1000 the default low-units + * is 750). + * + * @param resolver the ParameterResolver + * + * @return the low units + */ + public Units getLowUnits(ParameterResolver resolver) + { + return m_exprLowUnits.evaluate(resolver); + } + + /** + * Set the low units. + * + * @param expr the low units + */ + @Injectable + public void setLowUnits(Expression expr) + { + m_exprLowUnits = expr; + } + + /** + * Return the UnitCalculatorBuilder used to build a UnitCalculator. + * + * @return the unit calculator + */ + public UnitCalculatorBuilder getUnitCalculatorBuilder() + { + return m_bldrUnitCalculator; + } + + /** + * Set the UnitCalculatorBuilder. + * + * @param builder the UnitCalculatorBuilder + */ + @Injectable("unit-calculator") + public void setUnitCalculatorBuilder(UnitCalculatorBuilder builder) + { + m_bldrUnitCalculator = builder; + } + + /** + * Return the unit-factor element specifies the factor by which the units, + * low-units and high-units properties are adjusted. Using a BINARY unit + * calculator, for example, the factor of 1048576 could be used to count + * megabytes instead of bytes. + * + * @param resolver the ParameterResolver + * + * @return the unit factor + */ + public int getUnitFactor(ParameterResolver resolver) + { + return m_exprUnitFactor.evaluate(resolver); + } + + /** + * Set the unit factor. + * + * @param expr the unit factor expression + */ + @Injectable + public void setUnitFactor(Expression expr) + { + m_exprUnitFactor = expr; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link UnitCalculatorBuilder}. + */ + private UnitCalculatorBuilder m_bldrUnitCalculator; + + /** + * The {@link EvictionPolicyBuilder}. + */ + private EvictionPolicyBuilder m_bldrEvictionPolicy; + + /** + * The duration that a value will live in the cache, or zero for no timeout. + */ + private Expression m_exprExpiryDelay = new LiteralExpression(new Seconds(0)); + + /** + * The high units. + */ + private Expression m_exprHighUnits = new LiteralExpression(new Units(0)); + + /** + * The low units. + */ + private Expression m_exprLowUnits = new LiteralExpression(new Units(0)); + + /** + * The unit-factor. + */ + private Expression m_exprUnitFactor = new LiteralExpression(Integer.valueOf(1)); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractLocalCachingScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractLocalCachingScheme.java new file mode 100644 index 0000000000000..34cae0ef0805c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractLocalCachingScheme.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.BuilderCustomization; +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.net.CacheService; + +import java.util.Collections; +import java.util.List; + +/** + * The {@link AbstractLocalCachingScheme} is provides common functionality + * for local caching schemes, including local-scheme, external-scheme, etc. + * + * @author pfm 2011.12.28 + * @since Coherence 12.1.2 + */ +public abstract class AbstractLocalCachingScheme + extends AbstractCachingScheme + implements BuilderCustomization + { + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return CacheService.TYPE_LOCAL; + } + + /** + * {@inheritDoc} + */ + @Override + public List getEventInterceptorBuilders() + { + return Collections.EMPTY_LIST; + } + + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunningClusterNeeded() + { + return false; + } + + // ----- BuilderCustomization interface --------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ParameterizedBuilder getCustomBuilder() + { + return m_bldrCustom; + } + + /** + * {@inheritDoc} + */ + public void setCustomBuilder(ParameterizedBuilder bldr) + { + m_bldrCustom = bldr; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link ParameterizedBuilder} used to build the custom instance. + */ + private ParameterizedBuilder m_bldrCustom; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractScheme.java new file mode 100644 index 0000000000000..6da72882f1d12 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractScheme.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.config.annotation.Injectable; + +/** + * The {@link AbstractScheme} is the base implementation of a {@link Scheme}. + * The setters annotated with @Injectable are automatically called by CODI + * during document processing. Non-annotated setters are typically called + * by the CODI element processors. + * + * @author pfm 2011.12.28 + * @since Coherence 12.1.2 + */ +public abstract class AbstractScheme + implements Scheme + { + // ----- Scheme interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getSchemeName() + { + String sSchemeName = m_sSchemeName; + + if (sSchemeName == null) + { + m_sSchemeName = sSchemeName = ""; + } + + return sSchemeName; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isAnonymous() + { + return m_sSchemeName == null || m_sSchemeName.isEmpty(); + } + + // ----- AbstractScheme methods ----------------------------------------- + + /** + * Set the scheme name, trimming the name of starting and ending + * whitespace if necessary. + * + * @param sName the scheme name + */ + @Injectable + public void setSchemeName(String sName) + { + m_sSchemeName = sName == null ? "" : sName.trim(); + } + + // ----- internal ------------------------------------------------------- + + /** + * Validate the properties. + */ + protected void validate() + { + } + + // ----- data members --------------------------------------------------- + + /** + * The scheme name. + */ + private String m_sSchemeName; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractServiceScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractServiceScheme.java new file mode 100644 index 0000000000000..aaadcc237baed --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/AbstractServiceScheme.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.builder.ServiceBuilder; + +import com.tangosol.config.annotation.Injectable; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.Cluster; +import com.tangosol.net.Service; +import com.tangosol.net.ServiceDependencies; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.ClassHelper; + +import java.lang.ref.WeakReference; + +import java.util.Collections; +import java.util.List; + +/** + * The {@link AbstractServiceScheme} provides functionality common to all + * schemes that use services. Some properties, such as listeners, are optional + * and may not apply to every scheme. + * + * @author pfm 2011.12.28 + * @since Coherence 12.1.2 + */ +public abstract class AbstractServiceScheme + extends AbstractScheme + implements ServiceBuilder, ServiceScheme + { + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster) + { + validate(); + + String sService = getScopedServiceName(); + Service service = cluster.ensureService(sService, getServiceType()); + + // configure the service if it isn't running + if (!service.isRunning()) + { + Service servicePrev = m_refService == null ? null : m_refService.get(); + if (servicePrev != null && servicePrev.isRunning()) + { + // if another service instance is using the same dependencies, it + // could lead to unpredictable side effects. For example, two + // different partitioned services sharing an assignment strategy + // instance would most likely behave quite erratically or crash. + String sServicePrev = servicePrev.getInfo().getServiceName(); + + CacheFactory.log("This scheme is used to create an instance " + + "of service \"" + sService + "\", while another service \"" + + sServicePrev + "\" created by the same scheme is already running. " + + "This could lead to extremely dangerous side effects.", + CacheFactory.LOG_ERR); + } + else + { + m_refService = new WeakReference(service); + } + + service.setDependencies(getServiceDependencies()); + } + + return service; + } + + /** + * {@inheritDoc} + */ + @Override + public abstract boolean isRunningClusterNeeded(); + + /** + * {@inheritDoc} + */ + @Override + public String getScopeName() + { + return m_sScopeName; + } + + /** + * Set the scope name. + * + * @param sName the scope name + */ + @Injectable + public void setScopeName(String sName) + { + m_sScopeName = sName; + } + + /** + * Deprecated: Set the XML so that we can create a Service using the SafeCluster.ensureService. + * + * @param element the distributed-scheme XML + */ + @Deprecated + public void setXml(XmlElement element) + { + m_element = element; + } + + /** + * Return the XmlElement that contains the Service configuration. + * + * @return the XmlElement + */ + @Deprecated + public XmlElement getXml() + { + return m_element; + } + + // ----- ServiceScheme interface ---------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean isAutoStart() + { + return m_fAutoStart; + } + + /** + * Set the auto-start enabled flag. + * + * @param fEnabled the auto-start enabled flag + */ + @Injectable("autostart") + public void setAutoStart(boolean fEnabled) + { + m_fAutoStart = fEnabled; + } + + /** + * {@inheritDoc} + */ + @Override + public ServiceBuilder getServiceBuilder() + { + return this; + } + + /** + * Set the service name. + * + * @param sName the service name. + */ + @Injectable + public void setServiceName(String sName) + { + m_sServiceName = sName; + } + + /** + * {@inheritDoc} + */ + @Override + public String getServiceName() + { + String sName = m_sServiceName; + + if (sName == null || sName.trim().isEmpty()) + { + // the service name defaults to the service type string + m_sServiceName = sName = getDefaultServiceName(); + } + + return sName; + } + + /** + * {@inheritDoc} + */ + @Override + public String getScopedServiceName() + { + String sServiceName = getServiceName(); + String sScopeName = getScopeName(); + + sServiceName = sScopeName == null || sScopeName.trim().length() == 0 ? + sServiceName : sScopeName + DELIM_APPLICATION_SCOPE + sServiceName; + + return sServiceName; + } + + /** + * {@inheritDoc} + */ + @Override + public List getEventInterceptorBuilders() + { + return Collections.EMPTY_LIST; + } + + // ----- AbstractServiceScheme methods ---------------------------------- + + /** + * Get the wrapped {@link Service} from the SafeService and invoke setScopeName() + * on the wrapped {@link Service}. + * + * @param service The safe service + */ + protected void injectScopeNameIntoService(Service service) + { + String sScopeName = getScopeName(); + + if (sScopeName != null && sScopeName.length() > 0) + { + try + { + Service innerService = (Service) ClassHelper.invoke(service, "getService", null); + + if (innerService == null) + { + // injectScopeNameIntoService() should only be called with a SafeService + // which has an inner RemoteService that has the setScopeName() method on it. + CacheFactory.log("Unable to pass scope name \"" + sScopeName + "\" to service \"" + service + + "\". The service wrapped by the safe service is null", CacheFactory.LOG_ERR); + + return; + } + + ClassHelper.invoke(innerService, "setScopeName", new Object[] {sScopeName}); + } + catch (ReflectiveOperationException e) + { + // injectScopeNameIntoService() should only be called with a SafeService + // which has an inner RemoteService that has the setScopeName() method on it. + CacheFactory.log("Unable to pass scope name \"" + sScopeName + "\" to service \"" + + service + "\": " + e, CacheFactory.LOG_ERR); + } + } + } + + /** + * Obtains the {@link ServiceDependencies} that will be used to configure + * {@link Service} produced by this scheme. + * + * @return the {@link ServiceDependencies} + */ + @Injectable(".") + public D getServiceDependencies() + { + return m_serviceDependencies; + } + + /** + * Set the {@link ServiceDependencies} to be used by this scheme + * when configuring a newly realized {@link Service}. + * + * @param dependencies the {@link ServiceDependencies} object + */ + public void setServiceDependencies(D dependencies) + { + m_serviceDependencies = dependencies; + } + + /** + * DefaultServiceName to use if none configured. + * + * @return default service name + */ + protected String getDefaultServiceName() + { + return getServiceType(); + } + + // ----- data members --------------------------------------------------- + + /** + * The auto-start flag. + */ + private boolean m_fAutoStart; + + /** + * The service builder XML element. + */ + private XmlElement m_element; + + /** + * The scope name used by the ServiceBuilder (injected from the registry). + */ + private String m_sScopeName; + + /** + * A reference to the Service instance created by this scheme. + */ + private WeakReference m_refService; + + /** + * The service name. + */ + private String m_sServiceName; + + /** + * The {@link ServiceDependencies} to be used to configure the services + * produced by this scheme. + */ + protected D m_serviceDependencies; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BackingMapScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BackingMapScheme.java new file mode 100644 index 0000000000000..8d81b5405f8cc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BackingMapScheme.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.MapBuilder; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.security.StorageAccessAuthorizer; + +import com.tangosol.run.xml.XmlHelper; +import com.tangosol.run.xml.XmlValue; + +import com.tangosol.util.Base; + +import java.util.Map; + +/** + * The {@link BackingMapScheme} class is responsible for building a fully + * configured instance of a backing map. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class BackingMapScheme + extends AbstractLocalCachingScheme + { + // ----- MapBuilder interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + validate(resolver); + + MapBuilder bldrMap = getInnerScheme(); + + return bldrMap.realizeMap(resolver, dependencies); + } + + // ----- BackingMapScheme methods -------------------------------------- + + /** + * Return the inner scheme. + * + * @return the inner scheme + */ + public CachingScheme getInnerScheme() + { + return m_schemeInner; + } + + /** + * Set the inner scheme. + * + * @param scheme the inner scheme + */ + public void setInnerScheme(CachingScheme scheme) + { + m_schemeInner = scheme; + } + + /** + * Return the partitioned flag. + * + * @param resolver the ParameterResolver + * + * @return 'true' or 'observable' if the backing map is partitioned, + * else 'false' + */ + private String getPartitioned(ParameterResolver resolver) + { + return m_exprPartitioned.evaluate(resolver); + } + + /** + * Set the partitioned string. + * + * @param expr the Boolean expression set to 'true' or 'observable' if the + * backing map is partitioned + */ + @Injectable + public void setPartitioned(Expression expr) + { + m_exprPartitioned = expr; + } + + /** + * Return true if the partitioned flag is set explicitly or a journal + * map is used. + * + * @param resolver the ParameterResolver + * @param fDefault the default partitioned flag + * + * @return true if the map is partitioned + */ + public boolean isPartitioned(ParameterResolver resolver, boolean fDefault) + { + boolean fPartitioned = fDefault; + + validate(resolver); + + String sPartitioned = getPartitioned(resolver); + + if (sPartitioned == null || sPartitioned.isEmpty()) + { + // partition value not explicitly specified so figure out default + MapBuilder bldr = getInnerScheme(); + + if (bldr instanceof WrapperCachingScheme) + { + // get underlying storage scheme + bldr = ((WrapperCachingScheme)bldr).getCachingScheme(); + } + + if (bldr instanceof ReadWriteBackingMapScheme) + { + // if this is RWBM then check the internal map builder for journal + bldr = ((ReadWriteBackingMapScheme) bldr).getInternalScheme(); + } + + // the journal schemes are always partitioned + fPartitioned = bldr instanceof AbstractJournalScheme ? true : fPartitioned; + } + else + { + // partition value was explicitly specified + if (sPartitioned.equals("observable")) // do NOT doc! + { + fPartitioned = true; + } + + Boolean BPartitioned = (Boolean) XmlHelper.convert(sPartitioned, XmlValue.TYPE_BOOLEAN); + + if (BPartitioned == null) + { + throw new IllegalArgumentException("Invalid \"partitioned\" value: \"" + sPartitioned + "\""); + } + + fPartitioned = BPartitioned.booleanValue(); + } + + return fPartitioned; + } + + /** + * Return true if the backing map is transient. + * + * @param resolver the ParameterResolver + * + * @return true if the backing map is transient + */ + public boolean isTransient(ParameterResolver resolver) + { + return m_exprTransient.evaluate(resolver); + } + + /** + * Set the transient flag. + * + * @param expr true to make the backing map transient. + */ + @Injectable + public void setTransient(Expression expr) + { + m_exprTransient = expr; + } + + /** + * Return true iff sliding expiry is enabled. + * + * @param resolver the ParameterResolver + * + * @return true iff sliding expiry is enabled + */ + public Boolean isSlidingExpiry(ParameterResolver resolver) + { + return m_exprSlidingExpiry.evaluate(resolver); + } + + /** + * Set the SlidingExpiry flag. + * + * @param expr true to enable sliding expiry for the backing map + */ + @Injectable("sliding-expiry") + public void setSlidingExpiry(Expression expr) + { + m_exprSlidingExpiry = expr; + } + + + /** + * Return true iff received federated changes should be applied locally as synthetic updates. + * + * @param resolver the ParameterResolver + * + * @return true iff received federated changes should be applied locally as synthetic updates + * + * @since 12.2.1.4 + */ + public Boolean isFederateApplySynthetic(ParameterResolver resolver) + { + return m_exprFedApplySynthetic.evaluate(resolver); + } + + /** + * Set whether incoming federated changes should be applied locally as synthetic updates. + * + * @param expr true to apply incoming federated changes as synthetic + * + * @since 12.2.1.4 + */ + @Injectable("federate-apply-synthetic") + public void setFederateApplySynthetic(Expression expr) + { + m_exprFedApplySynthetic = expr; + } + + /** + * Obtains the {@link Expression} defining the name of the {@link StorageAccessAuthorizer}. + * + * @return the name of the {@link StorageAccessAuthorizer} or null if + * one has not been configured. + */ + public Expression getStorageAccessAuthorizer() + { + return m_exprStorageAccessAuthorizer; + } + + /** + * Sets the {@link Expression} defining the name of the {@link StorageAccessAuthorizer}. + * + * @param exprStorageAccessAuthorizer the {@link Expression} + */ + @Injectable("storage-authorizer") + public void setStorageAccessAuthorizer(Expression exprStorageAccessAuthorizer) + { + m_exprStorageAccessAuthorizer = exprStorageAccessAuthorizer; + } + + // ----- internal ------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void validate(ParameterResolver resolver) + { + super.validate(resolver); + + Base.checkNotNull(getInnerScheme(), "inner scheme"); + } + + // ----- constants ------------------------------------------------------ + + /** + * An on-heap backup storage. + */ + public static final int ON_HEAP = 0; + + /** + * An off-heap backup storage. + */ + public static final int OFF_HEAP = 1; + + /** + * A file mapped backup storage. + */ + public static final int FILE_MAPPED = 2; + + /** + * A custom backup storage. + */ + public static final int CUSTOM = 3; + + /** + * A referenced scheme provides backup storage. + */ + public static final int SCHEME = 4; + + /** + * A Flash Journal backup storage. + */ + public static final int FLASHJOURNAL = 5; + + /** + * A Ram Journal backup storage. + */ + public static final int RAMJOURNAL = 6; + + // ----- data members --------------------------------------------------- + + /** + * The partitioned flag. + */ + private Expression m_exprPartitioned = new LiteralExpression(""); + + /** + * The transient flag. + */ + private Expression m_exprTransient = new LiteralExpression(Boolean.FALSE); + + /** + * A flag indicating if sliding expiry is enabled. + */ + private Expression m_exprSlidingExpiry = new LiteralExpression(Boolean.FALSE); + + /** + * A flag indicating if received federated changes should be applied locally as synthetic updates. + * + * @since 12.2.1.4 + */ + private Expression m_exprFedApplySynthetic = new LiteralExpression<>(Boolean.FALSE); + + /** + * The name of the StorageAccessAuthorizer to use. + */ + private Expression m_exprStorageAccessAuthorizer = null; + + /** + * The inner scheme which builds the backing map. + */ + private CachingScheme m_schemeInner; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BackupMapConfig.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BackupMapConfig.java new file mode 100644 index 0000000000000..e93c808c764c2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BackupMapConfig.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import java.util.Map; + +import com.tangosol.coherence.config.builder.BuilderCustomization; +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.config.expression.ParameterResolver; + + +/** + * The {@link BackupMapConfig} interface exposes the configuration needed + * to create an instance of a backup map, which is used by the distributed cache + * to store backup data. + * + * @author pfm 2012.01.11 + * @since Coherence 12.1.2 + */ +public interface BackupMapConfig + extends BuilderCustomization + { + /** + * Resolve the backup map type using the configuration specified + * by the application. The primary map is also need in special cases + * to determine the type. + * + * @param resolver the ParameterResolver + * @param bldrPrimaryMap the primary map builder which may be used to + * determine the backup type + * + * @return the backup map type enumerated in {@link BackingMapScheme} + */ + public int resolveType(ParameterResolver resolver, MapBuilder bldrPrimaryMap); + + /** + * Return the name of the caching scheme to use as a backup map. + * Note that the scheme name is used as a key to lookup the scheme + * in the cache mapping. This is in contrast with the scheme name + * in the base {@link AbstractScheme} class which self-identifies a + * scheme object. + * + * @param resolver the ParameterResolver + * + * @return the scheme name + */ + public String getBackupSchemeName(ParameterResolver resolver); + + /** + * Return the root directory where the disk persistence manager stores files. + * This is only valid for file-mapped type. + * + * @param resolver the ParameterResolver + * + * @return the root directory + */ + public String getDirectory(ParameterResolver resolver); + + /** + * Return the initial buffer size in bytes for off-heap and file-mapped + * backup maps. + * + * @param resolver the ParameterResolver + * + * @return the write maximum batch size + */ + public int getInitialSize(ParameterResolver resolver); + + /** + * Return the maximum buffer size in bytes for off-heap and file-mapped + * backup maps. + * + * @param resolver the ParameterResolver + * + * @return the write maximum buffer size + */ + public int getMaximumSize(ParameterResolver resolver); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BundleManager.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BundleManager.java new file mode 100644 index 0000000000000..e14746216ee87 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BundleManager.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.unit.Millis; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.cache.AbstractBundler; +import com.tangosol.net.cache.BundlingNamedCache; +import com.tangosol.net.cache.ReadWriteBackingMap; + +import com.tangosol.util.Base; + +import java.util.ArrayList; + +/** + * The {@link BundleManager} class is responsible for configuring caches + * to use bundling. This class maintains a list of builders, where each + * builder contains configuration for a single bundle. The builders are + * called to instantiate and configure the bundling within the cache. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class BundleManager + { + // ----- BundleManager methods ----------------------------------------- + + /** + * Add the BundleConfig to the list of bundle configurations. + * + * @param config the BundleConfig + */ + @Injectable + public void addConfig(BundleConfig config) + { + m_listConfig.add(config); + } + + /** + * Create a BundlingNamedCache using the operation-bundling element. + * A bundler is created and maintained internally by the cache. + * + * @param resolver the ParameterResolver + * @param cache the BundlingNamedCache + */ + public void ensureBundles(ParameterResolver resolver, BundlingNamedCache cache) + { + for (BundleConfig config : m_listConfig) + { + config.validate(resolver); + + String sOperation = config.getOperationName(resolver); + int cBundle = config.getPreferredSize(resolver); + + if (sOperation.equals("all")) + { + initializeBundler(resolver, cache.ensureGetBundler(cBundle), config); + initializeBundler(resolver, cache.ensurePutBundler(cBundle), config); + initializeBundler(resolver, cache.ensureRemoveBundler(cBundle), config); + } + else if (sOperation.equals("get")) + { + initializeBundler(resolver, cache.ensureGetBundler(cBundle), config); + } + else if (sOperation.equals("put")) + { + initializeBundler(resolver, cache.ensurePutBundler(cBundle), config); + } + else if (sOperation.equals("remove")) + { + initializeBundler(resolver, cache.ensureRemoveBundler(cBundle), config); + } + else + { + throw new IllegalArgumentException( + "Invalid bundler \"operation-name\" :\n" + sOperation); + } + } + } + + /** + * Create a BundlingNamedCache using the "operation-bundling" element. + * A bundler is created and maintained internally by the cache. + * + * @param resolver the ParameterResolver + * @param wrapperStore the ReadWriteBackingMap.StoreWrapper + */ + public void ensureBundles(ParameterResolver resolver, + ReadWriteBackingMap.StoreWrapper wrapperStore) + { + for (BundleConfig config : m_listConfig) + { + config.validate(resolver); + + String sOperation = config.getOperationName(resolver); + int cBundle = config.getPreferredSize(resolver); + + if (sOperation.equals("all")) + { + initializeBundler(resolver, wrapperStore.ensureLoadBundler(cBundle), config); + initializeBundler(resolver, wrapperStore.ensureStoreBundler(cBundle), config); + initializeBundler(resolver, wrapperStore.ensureEraseBundler(cBundle), config); + } + else if (sOperation.equals("load")) + { + initializeBundler(resolver, wrapperStore.ensureLoadBundler(cBundle), config); + } + else if (sOperation.equals("store")) + { + initializeBundler(resolver, wrapperStore.ensureStoreBundler(cBundle), config); + } + else if (sOperation.equals("erase")) + { + initializeBundler(resolver, wrapperStore.ensureEraseBundler(cBundle), config); + } + else + { + throw new IllegalArgumentException( + "Invalid bundler \"operation-name\" :\n" + sOperation); + } + } + } + + // ----- internal ------------------------------------------------------- + + /** + * Initialize the specified bundler using the BundleConfig. + * + * @param resolver the ParameterResolver + * @param bundler the bundler + * @param config the BundleConfig + */ + protected void initializeBundler(ParameterResolver resolver, + AbstractBundler bundler, BundleConfig config) + { + if (bundler != null) + { + bundler.setThreadThreshold(config.getThreadThreshold(resolver)); + bundler.setDelayMillis(config.getDelayMillis(resolver)); + bundler.setAllowAutoAdjust(config.isAutoAdjust(resolver)); + } + } + + // ----- inner class: BundleConfig -------------------------------------- + + /** + * The BundleConfig class contains the configuration for a Bundle. + */ + public static class BundleConfig + { + // ----- BundleConfig methods --------------------------------------- + + /** + * Return true if the auto adjustment of the preferred size value + * (based on the run-time statistics) is allowed. + * + * @param resolver the ParameterResolver + * + * @return true if auto-adjust is enabled + */ + public boolean isAutoAdjust(ParameterResolver resolver) + { + return m_exprAutoAdjust.evaluate(resolver); + } + + /** + * Set the flag to auto adjust the preferred size value, based on the + * run-time statistics. + * + * @param expr true if auto adjustment is enabled + */ + @Injectable + public void setAutoAdjust(Expression expr) + { + m_exprAutoAdjust = expr; + } + + /** + * Specifies the maximum amount of time that individual execution + * requests are allowed to be deferred for a purpose of "bundling" + * them and passing into a corresponding bulk operation. If the + * preferred-size threshold is reached before the specified delay, + * the bundle is processed immediately. + * + * @param resolver the ParameterResolver + * + * @return the write delay + */ + public long getDelayMillis(ParameterResolver resolver) + { + return m_exprDelay.evaluate(resolver).get(); + } + + /** + * Set the write delay. + * + * @param expr the write delay + */ + @Injectable + public void setDelayMillis(Expression expr) + { + m_exprDelay = expr; + } + + /** + * Return the operation name for which calls performed concurrently + * on multiple threads are "bundled" into a functionally analogous + * "bulk" operation that takes a collection of arguments instead of + * a single one. + * + * @param resolver the ParameterResolver + * + * @return the operation name + */ + public String getOperationName(ParameterResolver resolver) + { + return m_exprOperationName.evaluate(resolver); + } + + /** + * Set the operation name for which calls performed concurrently on + * multiple threads are bundled. + * + * @param expr the operation name + */ + @Injectable + public void setOperationName(Expression expr) + { + m_exprOperationName = expr; + } + + /** + * Return the bundle size threshold. When a bundle size reaches this + * value, the corresponding "bulk" operation is invoked immediately. + * This value is measured in context-specific units. + * + * @param resolver the ParameterResolver + * + * @return the size threshold + */ + public int getPreferredSize(ParameterResolver resolver) + { + return m_exprPreferredSize.evaluate(resolver); + } + + /** + * Set the bundle size threshold. + * + * @param expr the size threshold + */ + @Injectable + public void setPreferredSize(Expression expr) + { + m_exprPreferredSize = expr; + } + + /** + * Return the minimum number of threads that must be concurrently + * executing individual(non-bundled) requests for the bundler to + * switch from a pass-through to a bundling mode. + * + * @param resolver the ParameterResolver + * + * @return the thread threshold + */ + public int getThreadThreshold(ParameterResolver resolver) + { + return m_exprThreadThreshold.evaluate(resolver); + } + + /** + * Set the thread threshold. + * + * @param expr the thread threshold + */ + @Injectable + public void setThreadThreshold(Expression expr) + { + m_exprThreadThreshold = expr; + } + + // ----- BundleConfig methods --------------------------------------- + + /** + * Validate the bundle configuration. + * + * @param resolver the {@link ParameterResolver} for resolving expressions and runtime parameters + */ + protected void validate(ParameterResolver resolver) + { + Base.checkNotEmpty(getOperationName(resolver), "OperationName"); + } + + // ----- data members ----------------------------------------------- + + /** + * The auto-adjust flag. + */ + private Expression m_exprAutoAdjust = + new LiteralExpression(Boolean.FALSE); + + /** + * The delay milliseconds. + */ + private Expression m_exprDelay = + new LiteralExpression(new Millis("1")); + + /** + * The operation name. + */ + private Expression m_exprOperationName = + new LiteralExpression("all"); + + /** + * The preferred size. + */ + private Expression m_exprPreferredSize = + new LiteralExpression(Integer.valueOf(0)); + + /** + * The thread threshold. + */ + private Expression m_exprThreadThreshold = + new LiteralExpression(Integer.valueOf(4)); + } + + // ----- data members --------------------------------------------------- + + private ArrayList m_listConfig = new ArrayList(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BundlingScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BundlingScheme.java new file mode 100644 index 0000000000000..23ee3f0e3f102 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/BundlingScheme.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +/** + * {@link BundlingScheme}s define how the bundling (batching) + * of operations will occur and the {@link BundleManager} used + * to configure said bundling. + * + * @author bko 2013.10.21 + * @since Coherence 12.1.3 + */ +public interface BundlingScheme + { + /** + * Obtains the {@link BundleManager}. + * + * @return the BundleManager + */ + public BundleManager getBundleManager(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/CacheStoreScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/CacheStoreScheme.java new file mode 100644 index 0000000000000..1ad8c8388ab3f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/CacheStoreScheme.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.BuilderCustomization; +import com.tangosol.coherence.config.builder.MapBuilder.Dependencies; +import com.tangosol.coherence.config.builder.NamedCacheBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.CacheService; +import com.tangosol.net.NamedCache; +import com.tangosol.net.Service; + +import com.tangosol.util.ExternalizableHelper; +import com.tangosol.util.NullImplementation; + +/** + * The {@link CacheStoreScheme} class is responsible for building a fully + * configured instance of a CacheStore, CacheLoader or remote NamedCache. + * The remote cache is only used within a ReadWriteBackingMap scheme. Also, + * even though bundling is specified in the CacheStore scheme, it is not used + * here. Rather, it is used by {@link ReadWriteBackingMapScheme}, + * which contains a {@link CacheStoreScheme}. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class CacheStoreScheme + extends AbstractScheme + implements BuilderCustomization, BundlingScheme + { + // ----- CacheStoreScheme methods -------------------------------------- + + /** + * Return an instance of a CacheStore, CacheLoader or a BinaryEntryStore. + * + * @param resolver the {@link ParameterResolver} + * @param dependencies the {@link Dependencies} + * + * @return an instance of CacheStore, CacheLoader or BinaryEntryStore + */ + public Object realizeLocal(ParameterResolver resolver, Dependencies dependencies) + { + ParameterizedBuilder bldr = getCustomBuilder(); + + return bldr == null ? bldr : bldr.realize(resolver, dependencies.getClassLoader(), null); + } + + /** + * Realize (ensure) a remote NamedCache, CacheStore, CacheLoader, or BinaryEntryStore + * as specified by the builder. The returned cache is fully configured and ready to be used. + * + * @param resolver the ParameterResolver + * @param dependencies the Dependencies + * + * @return the NamedCache, CacheStore, CacheLoader, or BinaryEntryStore + */ + public Object realize(ParameterResolver resolver, Dependencies dependencies) + { + Object obj = realizeLocal(resolver, dependencies); + + if (obj == null) + { + RemoteCacheScheme schemeRemote = getRemoteCacheScheme(); + + NamedCacheBuilder bldrRemoteCache = schemeRemote; + + if (bldrRemoteCache != null) + { + Dependencies depRemoteCache = new Dependencies(dependencies.getConfigurableCacheFactory(), null, + NullImplementation.getClassLoader(), dependencies.getCacheName(), + null); + + NamedCache cacheRemote = bldrRemoteCache.realizeCache(resolver, depRemoteCache); + + if (cacheRemote != null) + { + CacheService serviceThis = dependencies.getBackingMapManagerContext().getCacheService(); + + if (!isSerializerCompatible(cacheRemote.getCacheService(), serviceThis)) + { + ExternalizableHelper.reportIncompatibleSerializers(cacheRemote, + serviceThis.getInfo().getServiceName(), serviceThis.getSerializer()); + cacheRemote.release(); + cacheRemote = bldrRemoteCache.realizeCache(resolver, dependencies); + + } + + obj = cacheRemote; + } + } + } + + return obj; + } + + /** + * Set the {@link BundleManager}. + * + * @param mgrBundle the BundleManager + */ + @Injectable("operation-bundling") + public void setBundleManager(BundleManager mgrBundle) + { + m_mgrBundle = mgrBundle; + } + + /** + * Return the {@link RemoteCacheScheme}. + * + * @return the {@link RemoteCacheScheme} + */ + public RemoteCacheScheme getRemoteCacheScheme() + { + return m_schemeRemoteCache; + } + + /** + * Set the {@link RemoteCacheScheme}. + * + * @param bldr the {@link RemoteCacheScheme} + */ + @Injectable("remote-cache-scheme") + public void setRemoteCacheScheme(RemoteCacheScheme bldr) + { + m_schemeRemoteCache = bldr; + } + + // ----- BundlingScheme methods ---------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public BundleManager getBundleManager() + { + return m_mgrBundle; + } + + // ----- BuilderCustomization methods ----------------------------------- + + /** + * {@inheritDoc} + */ + public ParameterizedBuilder getCustomBuilder() + { + return m_bldrCacheStore; + } + + /** + * {@inheritDoc} + */ + public void setCustomBuilder(ParameterizedBuilder bldr) + { + m_bldrCacheStore = bldr; + } + + // ----- helpers -------------------------------------------------------- + + /** + * Determines whether or not the serializers for the specified services are + * compatible. In other words, this method returns true if object + * serialized with the first Serializer can be deserialized by the second + * and visa versa. + * + * @param serviceThis the first Service + * @param serviceThat the second Service + * + * @return true if the two Serializers are stream compatible + */ + protected boolean isSerializerCompatible(Service serviceThis, Service serviceThat) + { + return ExternalizableHelper.isSerializerCompatible(serviceThis.getSerializer(), serviceThat.getSerializer()); + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link ParameterizedBuilder} for the cache store + */ + private ParameterizedBuilder m_bldrCacheStore; + + /** + * The {@link BundleManager}. + */ + private BundleManager m_mgrBundle; + + /** + * The {@link RemoteCacheScheme}. + */ + private RemoteCacheScheme m_schemeRemoteCache; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/CachingScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/CachingScheme.java new file mode 100644 index 0000000000000..666953419fc53 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/CachingScheme.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.BackingMapManagerBuilder; +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.coherence.config.builder.NamedCacheBuilder; + +import com.tangosol.net.BackingMapManager; +import com.tangosol.net.NamedCache; +import com.tangosol.net.Service; + +import java.util.Map; + +/** + * The {@link CachingScheme} is a multi-builder for cache-based infrastructure. + * In particular it defines mechanisms to build {@link NamedCache}s, {@link Map}s + * that are used as the basis for {@link NamedCache}s, and where appropriate, + * {@link Service}s and {@link BackingMapManager}s. + * + * @author pfm 2011.12.28 + * @since Coherence 12.1.2 + */ +public interface CachingScheme + extends ServiceScheme, NamedCacheBuilder, MapBuilder, BackingMapManagerBuilder + { + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ClassScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ClassScheme.java new file mode 100644 index 0000000000000..2cc7437254582 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ClassScheme.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilderHelper; +import com.tangosol.coherence.config.xml.processor.ElementProcessorHelper; + +import com.tangosol.config.expression.ChainedParameterResolver; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.expression.ScopedParameterResolver; +import com.tangosol.config.injection.Injector; +import com.tangosol.config.injection.SimpleInjector; + +import com.tangosol.net.BackingMapManagerContext; +import com.tangosol.net.CacheService; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.ExtensibleConfigurableCacheFactory; +import com.tangosol.net.NamedCache; +import com.tangosol.net.Service; + +import com.tangosol.util.Base; +import com.tangosol.util.ResourceRegistry; +import com.tangosol.util.ResourceResolver; +import com.tangosol.util.ResourceResolverHelper; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * The {@link ClassScheme} class is responsible for building custom {@link CachingScheme}s and + * custom {@link CacheStoreScheme}s. + *

+ * Note: The {@link ParameterizedBuilder} interface is needed by both + * {@link CacheStoreScheme} and {@link ElementProcessorHelper}. + *

+ * This class will automatically inject the following types and + * named values into realized classes that have been annotated with + * @Injectable. + *

    + *
  1. {@link com.tangosol.net.BackingMapManagerContext} (optionally named "manager-context") + *
  2. {@link ConfigurableCacheFactory} + *
  3. Cache Name (as a {@link String}.class named "cache-name") + *
  4. Context {@link ClassLoader} (optionally named "class-loader") + *
  5. {@link ResourceRegistry} + *
  6. {@link CacheConfig} + *
  7. together with any other resource, named or otherwise, available + * in the {@link ResourceRegistry} provided by the + * {@link ConfigurableCacheFactory}. + *
+ * + * @see com.tangosol.config.annotation.Injectable + * + * @author pfm 2012.02.27 + * @since Coherence 12.1.2 + */ +public class ClassScheme + extends AbstractLocalCachingScheme + implements ParameterizedBuilder, ParameterizedBuilder.ReflectionSupport + { + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return CacheService.TYPE_LOCAL; + } + + /** + * {@inheritDoc} + */ + @Override + public List getEventInterceptorBuilders() + { + return Collections.EMPTY_LIST; + } + + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunningClusterNeeded() + { + return false; + } + + // ----- NamedCacheBuilder interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public NamedCache realizeCache(ParameterResolver resolver, Dependencies dependencies) + { + Base.azzert(dependencies.getConfigurableCacheFactory() instanceof ExtensibleConfigurableCacheFactory); + + // Call CFF to ensure the Service. CCF must be used to ensure the service, rather + // than the service builder. This is because CCFF.ensureService provides additional + // logic like injecting a BackingMapManager into the service and starting the Service. + Service service = + ((ExtensibleConfigurableCacheFactory) dependencies.getConfigurableCacheFactory()).ensureService(this); + + if (!(service instanceof CacheService)) + { + throw new IllegalArgumentException("Error: ensureCache is using service " + + service.getInfo().getServiceName() + "that is not a CacheService "); + } + + Parameter param = resolver.resolve("manager-context"); + BackingMapManagerContext ctx = (BackingMapManagerContext) param.evaluate(resolver).get(); + if (ctx == null) + { + // patch manager-context in resolver with manager-context that was just created above. + ParameterResolver resolverManagerContext = sParam -> + "manager-context".equals(sParam) + ? new Parameter("manager-context", ((CacheService)service).getBackingMapManager().getContext()) + : null; + resolver = new ChainedParameterResolver(resolverManagerContext, resolver); + } + + // Normally, the LocalCache service creates all local NamedCache(s). However, + // since this is a custom NamedCache, simply instantiate the object now. Note that + // the service is still needed (see ensureService above) since it contains the + // BackingMapManager, among other things. Furthermore, this same custom class + // will be used to create the backing map for the cache. + NamedCache cache = (NamedCache) realize(resolver, dependencies.getClassLoader(), null); + + // prepare an injector to inject @Injectables into the realized cache + Injector injector = new SimpleInjector(); + ResourceResolver resourceResolver = + ResourceResolverHelper.resourceResolverFrom(ResourceResolverHelper.resourceResolverFrom(resolver, + getDefaultParameterResolver()), ResourceResolverHelper.resourceResolverFrom(dependencies)); + + return injector.inject(cache, resourceResolver); + } + + // ----- MapBuilder interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + // prepare an injector to inject @Injectables into the realized cache + Injector injector = new SimpleInjector(); + ResourceResolver resourceResolver = + ResourceResolverHelper.resourceResolverFrom(ResourceResolverHelper.resourceResolverFrom(resolver, + getDefaultParameterResolver()), ResourceResolverHelper.resourceResolverFrom(dependencies)); + + return injector.inject((Map) realize(resolver, dependencies.getClassLoader(), null), resourceResolver); + } + + // ----- ParameterizedBuilder interface --------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + validate(); + + ParameterizedBuilder bldr = getCustomBuilder(); + + // NOTE: we don't attempt to inject into a raw builder here as there's + // no real context from which to inject. Everything that is available + // is already being provided to the builder. + + return bldr.realize(resolver, loader, listParameters); + } + + // ----- ParameterizedBuilder.ReflectionSupport interface --------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean realizes(Class clzClass, ParameterResolver resolver, ClassLoader loader) + { + validate(); + + ParameterizedBuilder bldr = getCustomBuilder(); + + return ParameterizedBuilderHelper.realizes(bldr, clzClass, resolver, loader); + } + + // ----- internal ------------------------------------------------------- + + /** + * Validate the ClassScheme properties. + */ + protected void validate() + { + Base.checkNotNull(getCustomBuilder(), "Custom map builder"); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ClusteredCachingScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ClusteredCachingScheme.java new file mode 100644 index 0000000000000..40f289078a10d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ClusteredCachingScheme.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +/** + * The ClusteredCachingScheme interface represents schemes that are used + * for clustered caches. + * + * @author pfm 2012.02.27 + * @since Coherence 12.1.2 + */ +public interface ClusteredCachingScheme + { + /** + * Return the {@link BackingMapScheme} used to create a backing map. + * + * @return the {@link BackingMapScheme} + */ + public BackingMapScheme getBackingMapScheme(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ContinuousQueryCacheScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ContinuousQueryCacheScheme.java new file mode 100644 index 0000000000000..fbc0c45f6221c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ContinuousQueryCacheScheme.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.annotation.Injectable; + +import com.tangosol.net.cache.ContinuousQueryCache; + +import com.tangosol.util.Filter; +import com.tangosol.util.ValueExtractor; + +/** + * This scheme is internally used to provide the {@link ParameterizedBuilder} that constructs the {@code view-filter} + * for the {@link ViewScheme}. + * + * @author rlubke + * @since 12.2.1.4 + */ +public class ContinuousQueryCacheScheme + extends AbstractLocalCachingScheme + { + // ----- accessor methods ------------------------------------------------- + + /** + * Set the {@link ParameterizedBuilder} used to construct the {@link Filter} to be used by the + * {@link ContinuousQueryCache}. + * + * @param filterBuilder the {@link ParameterizedBuilder} used to construct the {@link ValueExtractor} to + * be used as a transformer by the {@link ContinuousQueryCache} + */ + @SuppressWarnings("unused") + @Injectable("view-filter") + public void setFilterBuilder(ParameterizedBuilder filterBuilder) + { + m_filterBuilder = filterBuilder; + } + + /** + * Return the {@link ParameterizedBuilder} used to construct the {@link Filter} to be used by the + * {@link ContinuousQueryCache}. + * + * @return the {@link ParameterizedBuilder} used to construct the {@link Filter} to be used by the + * {@link ContinuousQueryCache} + */ + public ParameterizedBuilder getFilterBuilder() + { + return m_filterBuilder; + } + + /** + * Set the {@link ParameterizedBuilder} used to construct the {@link ValueExtractor} to be used as a transformer + * by the {@link ContinuousQueryCache}. + * + * @param transformerBuilder the {@link ParameterizedBuilder} used to construct the {@link ValueExtractor} to + * be used as a transformer by the {@link ContinuousQueryCache} + */ + @SuppressWarnings("unused") + @Injectable("transformer") + public void setTransformerBuilder(ParameterizedBuilder transformerBuilder) + { + m_transformerBuilder = transformerBuilder; + } + + /** + * Return the {@link ParameterizedBuilder} used to construct the {@link ValueExtractor} to be used as a + * transformer by the {@link ContinuousQueryCache}. + * + * @return the {@link ParameterizedBuilder} used to construct the {@link ValueExtractor} to be used as a + * transformer by the {@link ContinuousQueryCache}. + */ + public ParameterizedBuilder getTransformerBuilder() + { + return m_transformerBuilder; + } + + /** + * See {@link ContinuousQueryCache#setReconnectInterval(long)}. + * + * @param ldtReconnectInterval reconnect interval in milliseconds + */ + @SuppressWarnings("unused") + @Injectable("reconnect-interval") + public void setReconnectInterval(long ldtReconnectInterval) + { + m_cReconnectMillis = ldtReconnectInterval; + } + + /** + * See {@link ContinuousQueryCache#getReconnectInterval()}. + * + * @return reconnect interval in milliseconds + */ + public long getReconnectInterval() + { + return m_cReconnectMillis; + } + + /** + * See {@link ContinuousQueryCache#setReadOnly(boolean)}. + * + * @param fReadOnly pass true to prohibit clients from making + * modifications to this cache + */ + @Injectable("read-only") + public void setReadOnly(boolean fReadOnly) + { + m_fReadOnly = fReadOnly; + } + + /** + * See {@link ContinuousQueryCache#isReadOnly()}. + * + * @return true if this ContinuousQueryCache has been configured as + * read-only + */ + public boolean isReadOnly() + { + return m_fReadOnly; + } + + // ----- data members --------------------------------------------------- + + /** + * The reconnect interval to pass to the {@link ContinuousQueryCache}. + */ + protected long m_cReconnectMillis; + + /** + * The {@link ParameterizedBuilder} used to construct the {@link ValueExtractor} the {@link ContinuousQueryCache} + * will use to transform values retrieved from the underlying cache before storing them locally. + */ + private ParameterizedBuilder m_transformerBuilder; + + /** + * The {@link ParameterizedBuilder} used to construct the filter to be used by the + * {@link ContinuousQueryCache}. + */ + private ParameterizedBuilder m_filterBuilder; + + /** + * The read-only flag to pass to the {@link ContinuousQueryCache}. + */ + protected boolean m_fReadOnly; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/CustomScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/CustomScheme.java new file mode 100644 index 0000000000000..c128c559d4954 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/CustomScheme.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.injection.Injector; +import com.tangosol.config.injection.SimpleInjector; + +import com.tangosol.net.Cluster; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.NamedCache; +import com.tangosol.net.Service; + +import com.tangosol.util.Base; +import com.tangosol.util.ChainedResourceResolver; +import com.tangosol.util.ResourceRegistry; +import com.tangosol.util.ResourceResolver; +import com.tangosol.util.ResourceResolverHelper; + +import static com.tangosol.util.ResourceResolverHelper.resourceResolverFrom; + +import java.util.Map; + +/** + * A {@link CustomScheme} is an adapter for a {@link ParameterizedBuilder} that + * builds a {@link Map}. + *

+ * This class will automatically inject the following types and + * named values into realized classes that have been annotated with + * @Injectable. + *

    + *
  1. {@link com.tangosol.net.BackingMapManagerContext} (optionally named "manager-context") + *
  2. {@link ConfigurableCacheFactory} + *
  3. Cache Name (as a {@link String}.class named "cache-name") + *
  4. Context {@link ClassLoader} (optionally named "class-loader") + *
  5. {@link ResourceRegistry} + *
  6. {@link CacheConfig} + *
  7. together with any other resource, named or otherwise, available + * in the {@link ResourceRegistry} provided by the + * {@link ConfigurableCacheFactory}. + *
+ * + * @see com.tangosol.config.annotation.Injectable + * + * @author pfm 2011.12.06 + * @since Coherence 12.1.2 + */ +public class CustomScheme + extends AbstractLocalCachingScheme + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link CustomScheme}. + * + * @param bldr the InstanceBuilder to wrap + */ + public CustomScheme(ParameterizedBuilder bldr) + { + setCustomBuilder(bldr); + } + + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster) + { + throw new IllegalStateException("Custom scheme does not support services"); + } + + // ----- NamedCacheBuilder interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public NamedCache realizeCache(ParameterResolver resolver, Dependencies dependencies) + { + throw new IllegalStateException("Custom scheme does not support caches"); + } + + // ----- MapBuilder methods --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + validate(); + + ParameterizedBuilder bldr = getCustomBuilder(); + + Map map = bldr.realize(resolver, dependencies.getClassLoader(), null); + + // prepare an injector to inject values into the map + Injector injector = new SimpleInjector(); + ResourceResolver resourceResolver = + ResourceResolverHelper.resourceResolverFrom(ResourceResolverHelper.resourceResolverFrom(resolver, + getDefaultParameterResolver()), ResourceResolverHelper.resourceResolverFrom(dependencies)); + + return injector.inject(map, resourceResolver); + } + + // ----- internal ------------------------------------------------------- + + /** + * Validate the builder properties. + */ + protected void validate() + { + Base.checkNotNull(getCustomBuilder(), "Custom map builder"); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/DistributedScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/DistributedScheme.java new file mode 100644 index 0000000000000..67920ba09245f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/DistributedScheme.java @@ -0,0 +1,433 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.oracle.coherence.common.util.MemorySize; + +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.net.service.grid.DefaultPartitionedCacheDependencies; + +import com.tangosol.internal.net.service.grid.PartitionedCacheDependencies; +import com.tangosol.net.CacheService; + +import java.util.List; +import java.util.Map; + +/** + * The {@link DistributedScheme} class builds a distributed cache. + * + * @author pfm 2011.12.06 + * @since Coherence 12.1.2 + */ +public class DistributedScheme + extends AbstractCachingScheme + implements ClusteredCachingScheme, BundlingScheme + { + // ----- constructors -------------------------------------------------- + + /** + * Constructs a {@link DistributedScheme}. + */ + public DistributedScheme() + { + m_serviceDependencies = new DefaultPartitionedCacheDependencies(); + m_mgrBundle = null; + + // the default BackingMapScheme is a LocalScheme + m_schemeBackingMap = new BackingMapScheme(); + m_schemeBackingMap.setInnerScheme(new LocalScheme()); + } + + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return CacheService.TYPE_DISTRIBUTED; + } + + /** + * {@inheritDoc} + */ + public List getEventInterceptorBuilders() + { + return m_listEventInterceptorBuilders; + } + + // ----- ServiceBuilder interface -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunningClusterNeeded() + { + return true; + } + + // ----- BundlingScheme interface -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public BundleManager getBundleManager() + { + return m_mgrBundle; + } + + // ----- ClusteredCachingScheme interface ------------------------------- + + /** + * Return the {@link BackingMapScheme} which builds the backing map for + * the clustered scheme. + * + * @return the scheme + */ + public BackingMapScheme getBackingMapScheme() + { + return m_schemeBackingMap; + } + + /** + * Set the {@link BackingMapScheme} which builds the backing map for + * the clustered scheme. + * + * @param scheme the scheme builder + */ + @Injectable("backing-map-scheme") + public void setBackingMapScheme(BackingMapScheme scheme) + { + m_schemeBackingMap = scheme; + } + + // ----- DistributedScheme methods -------------------------------------- + + /** + * Return the {@link BackupMapConfig} which which is used to configure + * the backup map. + * + * @return the backup map configuration + */ + public BackupMapConfig getBackupMapConfig() + { + BackupMapConfig config = m_configBackup; + + if (config == null) + { + m_configBackup = config = new BackupConfig(); + } + + return config; + } + + /** + * Set the {@link BundleManager}. + * + * @param mgrBundle the BundleManager + */ + @Injectable("operation-bundling") + public void setBundleManager(BundleManager mgrBundle) + { + m_mgrBundle = mgrBundle; + } + + /** + * Set the {@link BackupMapConfig} which which is used to configure + * a backup map. + * + * @param config the backup map configuration + */ + @Injectable("backup-storage") + public void setBackupMapConfig(BackupMapConfig config) + { + m_configBackup = config; + } + + /** + * Sets the {@link List} of {@link NamedEventInterceptorBuilder}s for the {@link DistributedScheme}. + * + * @param listBuilders the {@link List} of {@link NamedEventInterceptorBuilder}s + */ + @Injectable("interceptors") + public void setEventInterceptorBuilders(List listBuilders) + { + m_listEventInterceptorBuilders = listBuilders; + } + + // ----- inner class BackupConfig --------------------------------------- + + /** + * The {@link BackupConfig} class manages configuration for the partitioned + * cache backup map. + */ + public static class BackupConfig + extends AbstractScheme + implements BackupMapConfig + { + // ----- BackupMapConfig interface ---------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int resolveType(ParameterResolver resolver, MapBuilder bldrPrimaryMap) + { + String sType = getType(resolver); + int nType = BackingMapScheme.ON_HEAP; + + if (sType == null || sType.isEmpty()) + { + // COH-7138 default to flash if the backing map is ram or flash + if (bldrPrimaryMap instanceof FlashJournalScheme || bldrPrimaryMap instanceof RamJournalScheme) + { + nType = BackingMapScheme.FLASHJOURNAL; + } + } + else + { + nType = translateType(sType); + } + + return nType; + } + + /** + * {@inheritDoc} + */ + @Override + public String getDirectory(ParameterResolver resolver) + { + return m_exprDirectory.evaluate(resolver); + } + + /** + * Set the root directory where the disk persistence manager stores files. + * This is only valid for file-mapped type. + * + * @param expr the directory name + */ + @Injectable + public void setDirectory(Expression expr) + { + m_exprDirectory = expr; + } + + /** + * {@inheritDoc} + */ + @Override + public int getInitialSize(ParameterResolver resolver) + { + return (int) m_exprInitialSize.evaluate(resolver).getByteCount(); + } + + /** + * Return the initial buffer size in bytes for off-heap and file-mapped + * backup maps. + * + * @param expr the initial buffer size + */ + @Injectable + public void setInitialSize(Expression expr) + { + m_exprInitialSize = expr; + } + + /** + * {@inheritDoc} + */ + @Override + public int getMaximumSize(ParameterResolver resolver) + { + return (int) m_exprMaximumSize.evaluate(resolver).getByteCount(); + } + + /** + * Set the maximum buffer size in bytes for off-heap and file-mapped + * backup maps. + * + * @param expr the maximum buffer size + */ + @Injectable + public void setMaximumSize(Expression expr) + { + m_exprMaximumSize = expr; + } + + /** + * {@inheritDoc} + */ + @Override + public String getBackupSchemeName(ParameterResolver resolver) + { + return m_exprBackupSchemeName.evaluate(resolver); + } + + /** + * Set the name of the caching scheme to use as a backup map. + * + * @param expr the scheme name + */ + @Injectable("scheme-name") + public void setBackupSchemeName(Expression expr) + { + m_exprBackupSchemeName = expr; + } + + /** + * Return the type of storage to hold the backup data. NOTE: this + * is private, public access must be through resolveType. + * + * @param resolver the ParameterResolver + * + * @return the write maximum buffer size + */ + private String getType(ParameterResolver resolver) + { + return m_exrpType.evaluate(resolver); + } + + /** + * Set the type of storage to hold the backup data. + * + * @param expr the maximum buffer size + */ + @Injectable + public void setType(Expression expr) + { + m_exrpType = expr; + } + + // ----- BuilderCustomization interface ----------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ParameterizedBuilder getCustomBuilder() + { + return m_bldrCustom; + } + + /** + * Set the InstanceBuilder that builds the custom instance. + * + * @param bldr the InstanceBuilder + */ + public void setCustomBuilder(ParameterizedBuilder bldr) + { + m_bldrCustom = bldr; + } + + // ----- internal --------------------------------------------------- + + /** + * Translate the backup map type string. + * + * @param sType the map type + * + * @return the translated type enumerated in {@link BackingMapScheme} + */ + protected int translateType(String sType) + { + int nType = -1; + + if (sType.equalsIgnoreCase("custom")) + { + nType = BackingMapScheme.CUSTOM; + } + else if (sType.equalsIgnoreCase("off-heap")) + { + nType = BackingMapScheme.OFF_HEAP; + } + else if (sType.equalsIgnoreCase("on-heap")) + { + nType = BackingMapScheme.ON_HEAP; + } + else if (sType.equalsIgnoreCase("file-mapped")) + { + nType = BackingMapScheme.FILE_MAPPED; + } + else if (sType.equalsIgnoreCase("scheme")) + { + nType = BackingMapScheme.SCHEME; + } + else + { + throw new IllegalArgumentException("Invalid backup storage type " + sType); + } + + return nType; + } + + // ----- data members ----------------------------------------------- + + /** + * The directory. + */ + private Expression m_exprDirectory = new LiteralExpression(""); + + /** + * The initial backup map size. + */ + private Expression m_exprInitialSize = new LiteralExpression(new MemorySize("1M")); + + /** + * The maximum backup map size. + */ + private Expression m_exprMaximumSize = new LiteralExpression(new MemorySize("1024M")); + + /** + * The backup scheme name. + */ + private Expression m_exprBackupSchemeName = new LiteralExpression(""); + + /** + * The backup map type. + */ + private Expression m_exrpType = new LiteralExpression(""); + + /** + * The {@link ParameterizedBuilder} used to build the custom instance. + */ + private ParameterizedBuilder m_bldrCustom; + } + + // ----- data members --------------------------------------------------- + + /** + * The backing map scheme. + */ + private BackingMapScheme m_schemeBackingMap; + + /** + * The backup map configuration. + */ + private BackupMapConfig m_configBackup; + + /** + * The {@link BundleManager}. + */ + private BundleManager m_mgrBundle; + + /** + * The {@link List} of {@link NamedEventInterceptorBuilder}s associated with {@link DistributedScheme}. + */ + private List m_listEventInterceptorBuilders; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ExternalScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ExternalScheme.java new file mode 100755 index 0000000000000..0c98528fc66d3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ExternalScheme.java @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.oracle.coherence.common.util.Duration.Magnitude; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.UnitCalculatorBuilder; +import com.tangosol.coherence.config.builder.storemanager.BinaryStoreManagerBuilder; +import com.tangosol.coherence.config.builder.storemanager.BinaryStoreManagerBuilderCustomization; +import com.tangosol.coherence.config.unit.Seconds; +import com.tangosol.coherence.config.unit.Units; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.BinaryStore; +import com.tangosol.io.BinaryStoreManager; +import com.tangosol.io.nio.BinaryMapStore; + +import com.tangosol.net.cache.ConfigurableCacheMap; +import com.tangosol.net.cache.ConfigurableCacheMap.UnitCalculator; +import com.tangosol.net.cache.LocalCache; +import com.tangosol.net.cache.SerializationCache; +import com.tangosol.net.cache.SerializationMap; +import com.tangosol.net.cache.SimpleSerializationMap; + +import com.tangosol.util.Base; + +import java.util.Map; + +/** + * The {@link ExternalScheme} class is responsible for building + * a fully configured instance of a ExternalCache. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class ExternalScheme + extends AbstractLocalCachingScheme + implements BinaryStoreManagerBuilderCustomization + { + // ----- MapBuilder interface ------------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + validate(resolver); + + Units highUnits = getHighUnits(resolver); + ClassLoader loader = dependencies.getClassLoader(); + BinaryStoreManager storeManager = getBinaryStoreManagerBuilder().realize(resolver, loader, false); + + // auto scale units to integer range + long cHighUnits = highUnits.getUnitCount(); + int nUnitFactor = getUnitFactor(resolver); + while (cHighUnits >= Integer.MAX_VALUE) + { + cHighUnits /= 1024; + nUnitFactor *= 1024; + } + + Map map = instantiateSerializationMap(resolver, storeManager.createBinaryStore(), dependencies.isBinary(), + loader, (int) cHighUnits, (int) getExpiryDelay(resolver).as(Magnitude.MILLI)); + + if (map instanceof ConfigurableCacheMap) + { + ((ConfigurableCacheMap) map).setUnitFactor(nUnitFactor); + + // if this is a partitioned cache backing map then default to BINARY if the user + // explicitly used a memory size in the high-units setting (e.g. 10M). + UnitCalculator defaultCalculator = highUnits.isMemorySize() && dependencies.isBinary() + ? LocalCache.INSTANCE_BINARY : null; + UnitCalculatorBuilder bldrUnitCalculator = getUnitCalculatorBuilder(); + + ((ConfigurableCacheMap) map).setUnitCalculator(bldrUnitCalculator == null + ? defaultCalculator : bldrUnitCalculator.realize(resolver, loader, null)); + } + + return map; + } + + // ----- BinaryStoreManagerBuilderCustomization interface --------------- + + /** + * {@inheritDoc} + */ + public BinaryStoreManagerBuilder getBinaryStoreManagerBuilder() + { + return m_bldrBinaryStoreManager; + } + + /** + * {@inheritDoc} + */ + public void setBinaryStoreManagerBuilder(BinaryStoreManagerBuilder bldr) + { + m_bldrBinaryStoreManager = bldr; + } + + // ----- ExternalScheme methods ---------------------------------------- + + /** + * Return the amount of time since the last update that entries + * are kept by the cache before being expired. Entries that have expired + * are not accessible and are evicted the next time a client accesses the + * cache. Any attempt to read an expired entry results in a reloading of + * the entry from the CacheStore. + * + * @param resolver the ParameterResolver + * + * @return the expiry delay + */ + public Seconds getExpiryDelay(ParameterResolver resolver) + { + return m_exprExpiryDelay.evaluate(resolver); + } + + /** + * Set the expiry delay. + * + * @param expr the expiry delay expression + */ + @Injectable + public void setExpiryDelay(Expression expr) + { + m_exprExpiryDelay = expr; + } + + /** + * Return the limit of cache size. Contains the maximum number of units + * that can be placed n the cache before pruning occurs. An entry is the + * unit of measurement, unless it is overridden by an alternate unit-calculator. + * When this limit is exceeded, the cache begins the pruning process, + * evicting entries according to the eviction policy. Legal values are + * positive integers or zero. Zero implies no limit. + * + * @param resolver the ParameterResolver + * + * @return the high units + */ + public Units getHighUnits(ParameterResolver resolver) + { + return m_exprHighUnits.evaluate(resolver); + } + + /** + * Set the high units. + * + * @param expr the high units expression + */ + @Injectable + public void setHighUnits(Expression expr) + { + m_exprHighUnits = expr; + } + + /** + * Return the UnitCalculatorBuilder used to build a UnitCalculator. + * + * @return the unit calculator + */ + public UnitCalculatorBuilder getUnitCalculatorBuilder() + { + return m_bldrUnitCalculator; + } + + /** + * Set the UnitCalculatorBuilder. + * + * @param bldr the UnitCalculatorBuilder + */ + @Injectable("unit-calculator") + public void setUnitCalculatorBuilder(UnitCalculatorBuilder bldr) + { + m_bldrUnitCalculator = bldr; + } + + /** + * Return the unit-factor element specifies the factor by which the units, + * low-units and high-units properties are adjusted. Using a BINARY unit + * calculator, for example, the factor of 1048576 could be used to count + * megabytes instead of bytes. + * + * @param resolver the ParameterResolver + * + * @return the unit factor + */ + public int getUnitFactor(ParameterResolver resolver) + { + return m_exprUnitFactor.evaluate(resolver); + } + + /** + * Set the unit factor. + * + * @param expr the unit factor expression + */ + @Injectable + public void setUnitFactor(Expression expr) + { + m_exprUnitFactor = expr; + } + + // ----- internal ------------------------------------------------------ + + /** + * Instantiate a SerializationMap, SerializationCache, + * SimpleSerializationMap, or any sub-class thereof. + * + * @param resolver the ParmeterResolver + * @param store a BinaryStore to use to write serialized data to + * @param fBinaryMap true if the only data written to the Map will + * already be in Binary form + * @param loader the ClassLoader to use (if not a Binary map) + * @param cHighUnits the max size in units for the serialization cache + * @param cExpiryMillis the expiry time in milliseconds for the cache + * + * @return a BinaryMap, SerializationMap, SerializationCache, + * SimpleSerializationMap, or a subclass thereof + */ + protected Map instantiateSerializationMap(ParameterResolver resolver, BinaryStore store, boolean fBinaryMap, + ClassLoader loader, int cHighUnits, int cExpiryMillis) + { + // create the cache, which is either internal or custom + ParameterizedBuilder bldrCustom = getCustomBuilder(); + + if (bldrCustom == null) + { + if (cHighUnits > 0 || cExpiryMillis > 0) + { + SerializationCache cache = fBinaryMap + ? instantiateSerializationCache(store, cHighUnits, true) + : instantiateSerializationCache(store, cHighUnits, loader); + + if (cExpiryMillis > 0) + { + cache.setExpiryDelay(cExpiryMillis); + } + + return cache; + } + else if (fBinaryMap && store.getClass() == BinaryMapStore.class) + { + // optimization: instead of taking binary objects, writing + // them through a serialization map that knows that they are + // binary into a BinaryStore that wraps a BinaryMap, we just + // use the BinaryMap directly + return ((BinaryMapStore) store).getBinaryMap(); + } + else + { + return fBinaryMap + ? instantiateSerializationMap(store, true) : instantiateSerializationMap(store, loader); + } + } + else + { + ParameterList listArgs = new ResolvableParameterList(); + + listArgs.add(new Parameter("store", store)); + + if (cHighUnits > 0 || cExpiryMillis > 0) + { + // create the custom object that is extending LocalCache. First + // populate the relevant constructor arguments then create the cache + listArgs.add(new Parameter("high-units", cHighUnits)); + + if (fBinaryMap) + { + listArgs.add(new Parameter("fBinary", fBinaryMap)); + } + else + { + listArgs.add(new Parameter("loader", loader)); + } + + SerializationCache cache = (SerializationCache) bldrCustom.realize(resolver, loader, listArgs); + + if (cExpiryMillis > 0) + { + cache.setExpiryDelay(cExpiryMillis); + } + + return cache; + } + else + { + if (fBinaryMap) + { + listArgs.add(new Parameter("fBinary", fBinaryMap)); + } + else + { + listArgs.add(new Parameter("loader", loader)); + } + + // the custom class may subclass one of the following: + // + // (1) SerializationMap + // (2) SimpleSerializationMap + // + // the common ancestor of these classes is AbstractKeyBasedMap + Map map = (Map) bldrCustom.realize(resolver, loader, listArgs); + + if (map instanceof SerializationMap || map instanceof SimpleSerializationMap) + { + return map; + } + + throw new IllegalArgumentException("Custom external cache does not extend either " + + SerializationMap.class.getName() + " or " + + SimpleSerializationMap.class.getName()); + } + } + } + + /** + * Construct an SerializationCache using the specified parameters. + *

+ * This method exposes a corresponding SerializationCache + * {@link SerializationCache#SerializationCache(BinaryStore, + * int, ClassLoader) constructor} and is provided for the express purpose + * of allowing its override. + * + * @param store the BinaryStore to use to write the serialized objects to + * @param cMax the maximum number of items to store in the binary store + * @param loader the ClassLoader to use for deserialization + * + * @return the instantiated {@link SerializationCache} + */ + protected SerializationCache instantiateSerializationCache(BinaryStore store, int cMax, ClassLoader loader) + { + return new SerializationCache(store, cMax, loader); + } + + /** + * Construct an SerializationCache using the specified parameters. + *

+ * This method exposes a corresponding SerializationCache + * {@link SerializationCache#SerializationCache(BinaryStore, + * int, boolean) constructor} and is provided for the express purpose of + * allowing its override. + * + * @param store the BinaryStore to use to write the serialized objects to + * @param cMax the maximum number of items to store in the binary store + * @param fBinaryMap true indicates that this map will only manage + * binary keys and values + * + * @return the instantiated {@link SerializationCache} + */ + protected SerializationCache instantiateSerializationCache(BinaryStore store, int cMax, boolean fBinaryMap) + { + return new SerializationCache(store, cMax, fBinaryMap); + } + + /** + * Construct an SerializationMap using the specified parameters. + *

+ * This method exposes a corresponding SerializationMap + * {@link SerializationMap#SerializationMap(BinaryStore, + * ClassLoader) constructor} and is provided for the express purpose of + * allowing its override. + * + * @param store the BinaryStore to use to write the serialized objects to + * @param loader the ClassLoader to use for deserialization + * + * @return the instantiated {@link SerializationMap} + */ + protected SerializationMap instantiateSerializationMap(BinaryStore store, ClassLoader loader) + { + return new SerializationMap(store, loader); + } + + /** + * Construct an SerializationMap using the specified parameters. + *

+ * This method exposes a corresponding SerializationMap + * {@link SerializationMap#SerializationMap(BinaryStore, boolean) constructor} + * and is provided for the express purpose of allowing its override. + * + * @param store the BinaryStore to use to write the serialized objects to + * @param fBinaryMap true indicates that this map will only manage + * binary keys and values + * + * @return the instantiated {@link SerializationMap} + */ + protected SerializationMap instantiateSerializationMap(BinaryStore store, boolean fBinaryMap) + { + return new SerializationMap(store, fBinaryMap); + } + + /** + * Construct a SimpleSerializationMap using the specified parameters. + *

+ * This method exposes a corresponding SerializationMap {@link + * SimpleSerializationMap#SimpleSerializationMap(BinaryStore, ClassLoader) + * constructor} and is provided for the express purpose of allowing its + * override. + * + * @param store the BinaryStore to use to write the serialized objects to + * @param loader the ClassLoader to use for deserialization + * + * @return the instantiated {@link SimpleSerializationMap} + * @since Coherence 3.7 + */ + protected SimpleSerializationMap instantiateSimpleSerializationMap(BinaryStore store, ClassLoader loader) + { + return new SimpleSerializationMap(store, loader); + } + + /** + * Construct a SimpleSerializationMap using the specified parameters. + *

+ * This method exposes a corresponding SerializationMap {@link + * SimpleSerializationMap#SimpleSerializationMap(BinaryStore, boolean) + * constructor} and is provided for the express purpose of allowing its + * override. + * + * @param store the BinaryStore to use to write the serialized objects to + * @param fBinaryMap true indicates that this map will only manage + * binary keys and values + * + * @return the instantiated {@link SimpleSerializationMap} + * @since Coherence 3.7 + */ + protected SimpleSerializationMap instantiateSimpleSerializationMap(BinaryStore store, boolean fBinaryMap) + { + return new SimpleSerializationMap(store, fBinaryMap); + } + + /** + * {@inheritDoc} + */ + @Override + protected void validate(ParameterResolver resolver) + { + super.validate(resolver); + + Base.checkNotNull(m_bldrBinaryStoreManager, "BinaryStoreManager"); + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link UnitCalculatorBuilder}. + */ + private UnitCalculatorBuilder m_bldrUnitCalculator; + + /** + * The StoreManager builder. + */ + private BinaryStoreManagerBuilder m_bldrBinaryStoreManager; + + /** + * The duration that a value will live in the cache. + * Zero indicates no timeout. + */ + private Expression m_exprExpiryDelay = new LiteralExpression(new Seconds(0)); + + /** + * The high units. + */ + private Expression m_exprHighUnits = new LiteralExpression(new Units(0)); + + /** + * The unit factor. + */ + private Expression m_exprUnitFactor = new LiteralExpression(Integer.valueOf(1)); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/FlashJournalScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/FlashJournalScheme.java new file mode 100755 index 0000000000000..311177a150e96 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/FlashJournalScheme.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.Map; + +/** + * The {@link FlashJournalScheme} is used to create an instance of a + * Flash Journal map. + * + * @author pfm 2011.10.30 + * @since Coherence 12.1.2 + */ +public class FlashJournalScheme + extends AbstractJournalScheme + { + // ----- MapBuilder interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + throw new UnsupportedOperationException("Elastic Data features are not supported in Coherence CE"); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/InvocationScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/InvocationScheme.java new file mode 100644 index 0000000000000..9c3dfc2459ed9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/InvocationScheme.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.internal.net.service.grid.DefaultInvocationServiceDependencies; +import com.tangosol.internal.net.service.grid.DefaultReplicatedCacheDependencies; +import com.tangosol.internal.net.service.grid.InvocationServiceDependencies; +import com.tangosol.internal.net.service.grid.LegacyXmlGridHelper; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.InvocationService; +import com.tangosol.net.OperationalContext; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Base; + +/** + * The {@link InvocationScheme} class builds an Invocation service. + * + * @author pfm 2011.12.06 + * @since Coherence 12.1.2 + */ +public class InvocationScheme + extends AbstractServiceScheme + { + // ----- constructors -------------------------------------------------- + + /** + * Constructs a {@link InvocationScheme}. + */ + public InvocationScheme() + { + m_serviceDependencies = new DefaultInvocationServiceDependencies(); + } + + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return InvocationService.TYPE_DEFAULT; + } + + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunningClusterNeeded() + { + return true; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/LocalScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/LocalScheme.java new file mode 100755 index 0000000000000..d857a5ec1290b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/LocalScheme.java @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.oracle.coherence.common.util.Duration.Magnitude; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.builder.EvictionPolicyBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.UnitCalculatorBuilder; +import com.tangosol.coherence.config.unit.Seconds; +import com.tangosol.coherence.config.unit.Units; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.injection.Injector; +import com.tangosol.config.injection.SimpleInjector; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.cache.CacheLoader; +import com.tangosol.net.cache.ConfigurableCacheMap.EvictionPolicy; +import com.tangosol.net.cache.ConfigurableCacheMap.UnitCalculator; +import com.tangosol.net.cache.LocalCache; +import com.tangosol.net.cache.OldCache; + +import com.tangosol.util.Base; +import com.tangosol.util.ResourceRegistry; +import com.tangosol.util.ResourceResolver; +import com.tangosol.util.ResourceResolverHelper; + +import static com.tangosol.util.ResourceResolverHelper.resourceResolverFrom; + +/** + * The {@link LocalScheme} class is responsible for building a fully + * configured instance of a LocalCache. Note that a LocalCache may be used as + * a stand-alone cache or as part of a backing map. + *

+ * This class will automatically inject the following types and + * named values into realized classes that have been annotated with + * @Injectable. + *

    + *
  1. {@link com.tangosol.net.BackingMapManagerContext} (optionally named "manager-context") + *
  2. {@link ConfigurableCacheFactory} + *
  3. Cache Name (as a {@link String}.class named "cache-name") + *
  4. Context {@link ClassLoader} (optionally named "class-loader") + *
  5. {@link ResourceRegistry} + *
  6. {@link CacheConfig} + *
  7. together with any other resource, named or otherwise, available + * in the {@link ResourceRegistry} provided by the + * {@link ConfigurableCacheFactory}. + *
+ * + * @see Injectable + * + * @author pfm 2011.10.30 + * @since Coherence 12.1.2 + */ +public class LocalScheme + extends AbstractLocalCachingScheme + { + // ----- MapBuilder interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public LocalCache realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + validate(resolver); + + Units highUnits = getHighUnits(resolver); + long cHighUnits = highUnits.getUnitCount(); + long cLowUnits = getLowUnits(resolver).getUnitCount(); + int nUnitFactor = getUnitFactor(resolver); + int cExpiryDelayMillis = (int) getExpiryDelay(resolver).as(Magnitude.MILLI); + + // auto scale units to integer range + while (cHighUnits >= Integer.MAX_VALUE) + { + cHighUnits /= 1024; + cLowUnits /= 1024; + nUnitFactor *= 1024; + } + + // check and default all of the Cache options + if (cHighUnits <= 0) + { + cHighUnits = Integer.MAX_VALUE; + } + + if (cLowUnits <= 0) + { + cLowUnits = (long) (cHighUnits * LocalCache.DEFAULT_PRUNE); + } + + if (cExpiryDelayMillis < 0) + { + cExpiryDelayMillis = 0; + } + + // create the cache, which is either internal or custom + LocalCache cache = null; + ClassLoader loader = dependencies.getClassLoader(); + ParameterizedBuilder bldrCustom = getCustomBuilder(); + + if (bldrCustom == null) + { + // create the default internal LocalCache + cache = new LocalCache((int) cHighUnits, cExpiryDelayMillis); + } + else + { + // create the custom object that is extending LocalCache. First + // populate the relevant constructor arguments then create the cache + ParameterList listArgs = new ResolvableParameterList(); + + listArgs.add(new Parameter("high-units", cHighUnits)); + listArgs.add(new Parameter("expiry-delay", cExpiryDelayMillis)); + cache = bldrCustom.realize(resolver, loader, listArgs); + + // prepare an injector to inject values into the cache + Injector injector = new SimpleInjector(); + ResourceResolver resourceResolver = + ResourceResolverHelper.resourceResolverFrom(ResourceResolverHelper.resourceResolverFrom(resolver, + getDefaultParameterResolver()), ResourceResolverHelper.resourceResolverFrom(dependencies)); + + injector.inject(cache, resourceResolver); + } + + // we can only be called by the ECCF, at which point the Cluster + // object has already been created + if (CacheFactory.getCluster().isRunning()) + { + cache.setOptimizeGetTime(true); + } + + // if this is a partitioned cache backing map then default to BINARY if the user + // explicitly used a memory size in the high-units setting (e.g. 10M). + UnitCalculator defaultCalculator = highUnits.isMemorySize() && dependencies.isBinary() + ? LocalCache.INSTANCE_BINARY : null; + UnitCalculatorBuilder bldrUnitCalculator = getUnitCalculatorBuilder(); + + cache.setLowUnits((int) cLowUnits); + cache.setUnitFactor(nUnitFactor); + cache.setUnitCalculator(bldrUnitCalculator == null + ? defaultCalculator : bldrUnitCalculator.realize(resolver, loader, null)); + + // Create the CacheLoader. The cache store scheme can specify a remote + // cache but don't allow that for LocalCache. + CacheStoreScheme schemeCacheStore = getCacheStoreScheme(); + Object loaderObj = schemeCacheStore == null ? null : schemeCacheStore.realizeLocal(resolver, dependencies); + + if (loaderObj instanceof CacheLoader) + { + cache.setCacheLoader((CacheLoader) loaderObj); + } + else if (loaderObj != null) + { + throw new IllegalArgumentException("The LocalCache cache-store " + "scheme does not specify a CacheLoader"); + } + + // if the eviction policy is internal then just set the type + // otherwise set the policy (the legacy API expects this) + EvictionPolicyBuilder bldrPolicy = getEvictionPolicyBuilder(); + + EvictionPolicy policy = bldrPolicy == null ? null : bldrPolicy.realize(resolver, loader, null); + + if (policy instanceof OldCache.InternalEvictionPolicy) + { + cache.setEvictionType(((OldCache.InternalEvictionPolicy) policy).getEvictionType()); + } + else + { + cache.setEvictionPolicy(policy); + } + + if (isPreLoad(resolver)) + { + try + { + cache.loadAll(); + } + catch (Throwable e) + { + String sText = "An exception occurred while pre-loading the \"" + dependencies.getCacheName() + + "\" cache:" + '\n' + Base.indentString(Base.getStackTrace(e), " "); + + if (!(e instanceof Error)) + { + sText += "\n(The exception has been logged and will be ignored.)"; + } + + CacheFactory.log(sText, CacheFactory.LOG_WARN); + + if (e instanceof Error) + { + throw(Error) e; + } + } + } + + return cache; + } + + // ----- LocalScheme methods ------------------------------------------- + + /** + * Return the {@link CacheStoreScheme} which builds a CacheStore or CacheLoader. + * + * @return the CacheStoreScheme + */ + public CacheStoreScheme getCacheStoreScheme() + { + return m_schemeCacheStore; + } + + /** + * Set the {@link CacheStoreScheme} which builds a CacheStore or CacheLoader. + * + * @param scheme the CacheStoreScheme + */ + @Injectable("cachestore-scheme") + public void setCacheStoreScheme(CacheStoreScheme scheme) + { + m_schemeCacheStore = scheme; + } + + /** + * Return the EvictionPolicyBuilder used to build an EvictionPolicy. + * + * @return the builder + */ + public EvictionPolicyBuilder getEvictionPolicyBuilder() + { + return m_bldrEvictionPolicy; + } + + /** + * Set the EvictionPolicyBuilder. + * + * @param bldr the EvictionPolicyBuilder + */ + @Injectable("eviction-policy") + public void setEvictionPolicyBuilder(EvictionPolicyBuilder bldr) + { + m_bldrEvictionPolicy = bldr; + } + + /** + * Return the amount of time since the last update that entries + * are kept by the cache before being expired. Entries that have expired + * are not accessible and are evicted the next time a client accesses the + * cache. Any attempt to read an expired entry results in a reloading of + * the entry from the CacheStore. + * + * @param resolver the ParameterResolver + * + * @return the expiry delay + */ + public Seconds getExpiryDelay(ParameterResolver resolver) + { + return m_exprExpiryDelay.evaluate(resolver); + } + + /** + * Set the expiry delay. + * + * @param expr the expiry delay expression + */ + @Injectable + public void setExpiryDelay(Expression expr) + { + m_exprExpiryDelay = expr; + } + + /** + * Return the limit of cache size. Contains the maximum number of units + * that can be placed n the cache before pruning occurs. An entry is the + * unit of measurement, unless it is overridden by an alternate unit-calculator. + * When this limit is exceeded, the cache begins the pruning process, + * evicting entries according to the eviction policy. Legal values are + * positive integers or zero. Zero implies no limit. + * + * @param resolver the ParameterResolver + * + * @return the high units + */ + public Units getHighUnits(ParameterResolver resolver) + { + return m_exprHighUnits.evaluate(resolver); + } + + /** + * Set the high units. + * + * @param expr the high units expression + */ + @Injectable + public void setHighUnits(Expression expr) + { + m_exprHighUnits = expr; + } + + /** + * Return the lowest number of units that a cache is pruned down to when + * pruning takes place. A pruning does not necessarily result in a cache + * containing this number of units, however a pruning never results in a + * cache containing less than this number of units. An entry is the unit + * of measurement, unless it is overridden by an alternate unit-calculator. + * When pruning occurs entries continue to be evicted according to the + * eviction policy until this size. Legal values are positive integers or + * zero. Zero implies the default. The default value is 75% of the high-units + * setting (that is, for a high-units setting of 1000 the default low-units + * is 750). + * + * @param resolver the ParameterResolver + * + * @return the low units + */ + public Units getLowUnits(ParameterResolver resolver) + { + return m_exprLowUnits.evaluate(resolver); + } + + /** + * Set the low units. + * + * @param expr the low units + */ + @Injectable + public void setLowUnits(Expression expr) + { + m_exprLowUnits = expr; + } + + /** + * Return true if a cache pre-loads data from its CacheLoader. + * + * @param resolver the ParameterResolver + * + * @return true if pre-load is enabled + */ + public boolean isPreLoad(ParameterResolver resolver) + { + return m_exprPreload.evaluate(resolver); + } + + /** + * Set the pre-load enabled flag. + * + * @param expr true to enable pre-load + */ + @Injectable + public void setPreLoad(Expression expr) + { + m_exprPreload = expr; + } + + /** + * Return the UnitCalculatorBuilder used to build a UnitCalculator. + * + * @return the unit calculator + */ + public UnitCalculatorBuilder getUnitCalculatorBuilder() + { + return m_bldrUnitCalculator; + } + + /** + * Set the UnitCalculatorBuilder. + * + * @param builder the UnitCalculatorBuilder + */ + @Injectable("unit-calculator") + public void setUnitCalculatorBuilder(UnitCalculatorBuilder builder) + { + m_bldrUnitCalculator = builder; + } + + /** + * Return the unit-factor element specifies the factor by which the units, + * low-units and high-units properties are adjusted. Using a BINARY unit + * calculator, for example, the factor of 1048576 could be used to count + * megabytes instead of bytes. + * + * @param resolver the ParameterResolver + * + * @return the unit factor + */ + public int getUnitFactor(ParameterResolver resolver) + { + return m_exprUnitFactor.evaluate(resolver); + } + + /** + * Set the unit factor. + * + * @param expr the unit factor expression + */ + @Injectable + public void setUnitFactor(Expression expr) + { + m_exprUnitFactor = expr; + } + + // ----- internal ------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void validate(ParameterResolver resolver) + { + super.validate(resolver); + + if (getExpiryDelay(resolver).as(Magnitude.MILLI) > Integer.MAX_VALUE) + { + throw new ConfigurationException("Illegal value specified for for local scheme '" + + getSchemeName() + + "'", "The expiry delay cannot exceed 2147483 seconds or ~24 days."); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link CacheStoreScheme}. + */ + private CacheStoreScheme m_schemeCacheStore; + + /** + * The {@link UnitCalculatorBuilder}. + */ + private UnitCalculatorBuilder m_bldrUnitCalculator; + + /** + * The {@link EvictionPolicyBuilder}. + */ + private EvictionPolicyBuilder m_bldrEvictionPolicy; + + /** + * The duration that a value will live in the cache. + * Zero indicates no timeout. + */ + private Expression m_exprExpiryDelay = new LiteralExpression(new Seconds(0)); + + /** + * The high units. + */ + private Expression m_exprHighUnits = new LiteralExpression(new Units(0)); + + /** + * The low units. + */ + private Expression m_exprLowUnits = new LiteralExpression(new Units(0)); + + /** + * The pre-load flag. + */ + private Expression m_exprPreload = new LiteralExpression(Boolean.FALSE); + + /** + * The unit factor. + */ + private Expression m_exprUnitFactor = new LiteralExpression(Integer.valueOf(1)); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/NamedTopicScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/NamedTopicScheme.java new file mode 100644 index 0000000000000..051828002f224 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/NamedTopicScheme.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.net.CacheService; + +import com.tangosol.net.topic.NamedTopic; + +/** + * The {@link TopicScheme} class is responsible for building a fully + * configured instance of a {@link NamedTopic}. + * + * @author jk 2015.05.21 + * @since Coherence 14.1.1 + */ +public interface NamedTopicScheme + extends TopicScheme + { + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/NearScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/NearScheme.java new file mode 100644 index 0000000000000..75d7dce906614 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/NearScheme.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.builder.NamedCacheBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.ServiceBuilder; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.BackingMapManager; +import com.tangosol.net.CacheFactory; +import com.tangosol.net.CacheService; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.NamedCache; +import com.tangosol.net.cache.NearCache; + +import java.util.Map; + +/** + * The {@link NearScheme} is used to realize (create) an instance of a NearCache. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class NearScheme + extends AbstractCompositeScheme + implements NamedCacheBuilder + { + // ----- ServiceScheme interface ---------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ServiceBuilder getServiceBuilder() + { + return getBackScheme().getServiceBuilder(); + } + + // ----- BackingMapManagerBuilder interface ----------------------------- + + /** + * {@inheritDoc} + */ + @Override + public BackingMapManager realizeBackingMapManager(ConfigurableCacheFactory ccf) + { + return getBackScheme().realizeBackingMapManager(ccf); + } + + // ----- NamedCacheBuilder interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public NamedCache realizeCache(ParameterResolver resolver, Dependencies dependencies) + { + validate(resolver); + + String sStrategy = getInvalidationStrategy(resolver); + + int nStrategy = sStrategy.equalsIgnoreCase("none") + ? NearCache.LISTEN_NONE + : sStrategy.equalsIgnoreCase("present") + ? NearCache.LISTEN_PRESENT + : sStrategy.equalsIgnoreCase("all") + ? NearCache.LISTEN_ALL + : sStrategy.equalsIgnoreCase("auto") + ? NearCache.LISTEN_AUTO + : sStrategy.equalsIgnoreCase("logical") + ? NearCache.LISTEN_LOGICAL + : Integer.MIN_VALUE; + + if (nStrategy == Integer.MIN_VALUE) + { + CacheFactory.log("Invalid invalidation strategy of '" + sStrategy + "'; proceeding with default of 'auto'", + CacheFactory.LOG_WARN); + nStrategy = NearCache.LISTEN_AUTO; + } + + // create the front map + Dependencies depFront = new Dependencies(dependencies.getConfigurableCacheFactory(), null, + dependencies.getClassLoader(), dependencies.getCacheName(), + CacheService.TYPE_LOCAL); + + Map mapFront = getFrontScheme().realizeMap(resolver, depFront); + + // create the back cache + CachingScheme schemeBack = getBackScheme(); + NamedCache cacheBack = schemeBack.realizeCache(resolver, dependencies); + + // create the near cache + NearCache cacheNear; + ParameterizedBuilder bldrCustom = getCustomBuilder(); + + if (bldrCustom == null) + { + // create the default internal NearCache + cacheNear = new NearCache(mapFront, cacheBack, nStrategy); + } + else + { + // create the custom object that is extending NearCache. First + // populate the relevant constructor arguments then create the cache + ParameterList listArgs = new ResolvableParameterList(); + + listArgs.add(new Parameter("front-map", mapFront)); + listArgs.add(new Parameter("back-cache", cacheBack)); + listArgs.add(new Parameter("strategy", nStrategy)); + cacheNear = bldrCustom.realize(resolver, dependencies.getClassLoader(), listArgs); + } + + cacheNear.setRegistrationContext("tier=front,loader=" + dependencies.getClassLoader().hashCode()); + + return cacheNear; + } + + // ----- ObservableCachingScheme interface ------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public void establishMapListeners(Map map, ParameterResolver resolver, Dependencies dependencies) + { + // establish the map listener for the cache + super.establishMapListeners(map, resolver, dependencies); + + // establish the map listener for the front map if it's observable + if (getFrontScheme() instanceof ObservableCachingScheme && map instanceof NearCache) + { + ((ObservableCachingScheme) getFrontScheme()).establishMapListeners(((NearCache) map).getFrontMap(), + resolver, dependencies); + } + } + + // ----- NearScheme methods --------------------------------------------- + + /** + * Return the invalidation strategy. + * + * @param resolver the ParameterResolver + * + * @return the invalidation strategy + */ + public String getInvalidationStrategy(ParameterResolver resolver) + { + return m_exprInvalidationStrategy.evaluate(resolver); + } + + /** + * Set the invalidation strategy. + * + * @param expr the invalidation strategy + */ + @Injectable + public void setInvalidationStrategy(Expression expr) + { + m_exprInvalidationStrategy = expr; + } + + // ----- data members --------------------------------------------------- + + /** + * The invalidation strategy. + */ + private Expression m_exprInvalidationStrategy = new LiteralExpression(String.valueOf("auto")); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ObservableCachingScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ObservableCachingScheme.java new file mode 100644 index 0000000000000..0051d3dadfe83 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ObservableCachingScheme.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.NamedCache; + +import com.tangosol.util.MapListener; +import com.tangosol.util.ObservableMap; +import com.tangosol.util.ResourceRegistry; + +import java.util.Map; + +/** + * An {@link ObservableCachingScheme} is a {@link CachingScheme} that supports + * defining and adding {@link MapListener}s to realized {@link Map}s and + * {@link NamedCache}s. + * + * @author bo 2012.11.06 + * @since Coherence 12.1.2 + */ +public interface ObservableCachingScheme + extends CachingScheme + { + /** + * Obtains a {@link ParameterizedBuilder} for a {@link MapListener} that + * can be used for building {@link MapListener}s those of which may be + * later added to the {@link Map}s or {@link NamedCache}s realized by + * the {@link CachingScheme}. + * + * @return a {@link ParameterizedBuilder} for {@link MapListener}s + */ + public ParameterizedBuilder getListenerBuilder(); + + /** + * Establishes an appropriate {@link MapListener} (provided by the + * {@link #getListenerBuilder()}) on the {@link ObservableMap} + * that was produced by the {@link ObservableCachingScheme}. + *

+ * This method will automatically inject the following types and + * named values into realized classes that have been annotated with + * @Injectable. + *

    + *
  1. {@link com.tangosol.net.BackingMapManagerContext} (optionally named "manager-context") + *
  2. {@link ConfigurableCacheFactory} + *
  3. Cache Name (as a {@link String}.class named "cache-name") + *
  4. Context {@link ClassLoader} (optionally named "class-loader") + *
  5. {@link ResourceRegistry} + *
  6. {@link CacheConfig} + *
  7. together with any other resource, named or otherwise, available + * in the {@link ResourceRegistry} provided by the + * {@link ConfigurableCacheFactory}. + *
+ * + * @see com.tangosol.config.annotation.Injectable + * + * @param map an {@link ObservableMap} to which to add a {@link MapListener} + * (if the map is not observable, no listeners are added) + * @param resolver the {@link ParameterResolver} to use for resolving + * builder parameters + * @param dependencies the {@link MapBuilder} dependencies from which to + * obtain builder information + */ + public void establishMapListeners(Map map, ParameterResolver resolver, MapBuilder.Dependencies dependencies); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/OptimisticScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/OptimisticScheme.java new file mode 100644 index 0000000000000..71f84a989a20a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/OptimisticScheme.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.net.CacheService; + +/** + * The {@link OptimisticScheme} class builds an optimistic cache. + * + * @author pfm 2011.12.06 + * @since Coherence 12.1.2 + */ +public class OptimisticScheme + extends ReplicatedScheme + { + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return CacheService.TYPE_OPTIMISTIC; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/OverflowScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/OverflowScheme.java new file mode 100755 index 0000000000000..fc8710ad199da --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/OverflowScheme.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.oracle.coherence.common.util.Duration.Magnitude; + +import com.tangosol.coherence.config.SimpleParameterList; +import com.tangosol.coherence.config.builder.InstanceBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.unit.Seconds; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.cache.OverflowMap; +import com.tangosol.net.cache.SimpleOverflowMap; + +import com.tangosol.util.ObservableMap; + +import java.util.Map; + +/** + * The {@link OverflowScheme} is used to create an instance of an {@link OverflowMap} or a {@link SimpleOverflowMap}. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class OverflowScheme + extends AbstractCompositeScheme + { + // ----- MapBuilder interface ------------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + ParameterizedBuilder bldrCustom = getCustomBuilder(); + ClassLoader loader = dependencies.getClassLoader(); + boolean fExpiryEnabled = isExpiryEnabled(resolver); + + // determine the builder to use for the map + ParameterizedBuilder bldrMap; + + if (bldrCustom == null) + { + // no custom builder was specified so use an appropriate internal builder + bldrMap = new InstanceBuilder(OverflowMap.class); + } + else + { + bldrMap = bldrCustom; + } + + // realize the front, back and misses maps + Map mapFront = getFrontScheme().realizeMap(resolver, dependencies); + Map mapBack = getBackScheme().realizeMap(resolver, dependencies); + Map mapMisses = getMissCacheScheme() == null ? null : getMissCacheScheme().realizeMap(resolver, dependencies); + + // validate that the front map is observable + if (!(mapFront instanceof ObservableMap)) + { + throw new IllegalArgumentException("FrontMap is not observable: " + mapFront.getClass()); + } + + // establish constructor parameters for realizing the map + SimpleParameterList listParameters = new SimpleParameterList(mapFront, mapBack); + + // realize the map + Map map = bldrMap.realize(resolver, loader, listParameters); + + if (SimpleOverflowMap.class.isAssignableFrom(map.getClass()) && mapMisses != null) + { + ((SimpleOverflowMap) map).setMissCache(mapMisses); + } + + // configure the map + String sCacheName = dependencies.getCacheName(); + + if (map instanceof OverflowMap) + { + OverflowMap mapOverflow = (OverflowMap) map; + + mapOverflow.setExpiryEnabled(fExpiryEnabled); + + // set the expiry time (if valid) + int cExpiryMillis = (int) getExpiryDelay(resolver).as(Magnitude.MILLI); + + if (cExpiryMillis > 0) + { + mapOverflow.setExpiryDelay(cExpiryMillis); + } + + if (mapMisses != null) + { + CacheFactory.log("Cache " + sCacheName + " of scheme " + getSchemeName() + + " has a \"miss-cache-scheme\" configured; since" + + " the default OverflowMap implementation has been" + + " selected, the miss cache will not be used.", CacheFactory.LOG_WARN); + } + } + else if (map instanceof SimpleOverflowMap) + { + if (fExpiryEnabled) + { + CacheFactory.log("Cache " + sCacheName + " of scheme " + getSchemeName() + + " has \"expiry-enabled\" set to true or" + + " \"expiry-delay\" configured; these settings will" + + " have no effect, and expiry will not work," + + " because the scheme explicitly ues a" + + " SimpleOverflowMap.", CacheFactory.LOG_WARN); + } + + if (mapBack instanceof ObservableMap) + { + CacheFactory.log("Cache " + sCacheName + " of scheme " + getSchemeName() + + " has a \"back-scheme\" that is observable;" + + " the events from the back map will be ignored" + + " because the scheme explicitly uses a" + + " SimpleOverflowMap, and this could result in" + + " missing events if the back map actively expires" + + " and/or evicts its entries.", CacheFactory.LOG_WARN); + } + } + else + { + throw new IllegalArgumentException(bldrCustom + + " will not realize a sub-class of either OverflowMap or SimpleOverflowMap"); + } + + return map; + } + + // ----- ObservableCachingScheme interface ------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public void establishMapListeners(Map map, ParameterResolver resolver, Dependencies dependencies) + { + super.establishMapListeners(map, resolver, dependencies); + + if (map instanceof OverflowMap) + { + if (getFrontScheme() instanceof ObservableCachingScheme) + { + ((ObservableCachingScheme) getFrontScheme()).establishMapListeners(((OverflowMap) map).getFrontMap(), + resolver, dependencies); + } + + if (getBackScheme() instanceof ObservableCachingScheme) + { + ((ObservableCachingScheme) getBackScheme()).establishMapListeners(((OverflowMap) map).getBackMap(), + resolver, dependencies); + } + } + + if (map instanceof SimpleOverflowMap) + { + if (getFrontScheme() instanceof ObservableCachingScheme) + { + ((ObservableCachingScheme) getFrontScheme()) + .establishMapListeners(((SimpleOverflowMap) map).getFrontMap(), resolver, dependencies); + } + + if (getBackScheme() instanceof ObservableCachingScheme) + { + ((ObservableCachingScheme) getBackScheme()) + .establishMapListeners(((SimpleOverflowMap) map).getBackMap(), resolver, dependencies); + } + } + } + + // ----- OverflowScheme methods ----------------------------------------- + + /** + * Return the amount of time since the last update that entries + * are kept by the cache before being expired. Entries that have expired + * are not accessible and are evicted the next time a client accesses the + * cache. Any attempt to read an expired entry results in a reloading of + * the entry from the CacheStore. + * + * @param resolver the ParameterResolver + * + * @return the expiry delay + */ + public Seconds getExpiryDelay(ParameterResolver resolver) + { + return m_exprExpiryDelay.evaluate(resolver); + } + + /** + * Set the expiry delay. + * + * @param expr the expiry delay expression + */ + @Injectable + public void setExpiryDelay(Expression expr) + { + m_exprExpiryDelay = expr; + } + + /** + * Return the expiry enabled flag. + * + * @param resolver the ParameterResolver + * + * @return true if expiry delay is enabled + */ + public boolean isExpiryEnabled(ParameterResolver resolver) + { + return m_exprExpiryEnabled.evaluate(resolver); + } + + /** + * Set the expiry enabled flag. + * + * @param expr the Boolean expression set to true if expiry delay is enabled + */ + @Injectable + public void setExpiryEnabled(Expression expr) + { + m_exprExpiryEnabled = expr; + } + + /** + * Return the scheme for the cache used to maintain information on cache + * misses. The miss-cache is used track keys which were not found in the + * cache store. The knowledge that a key is not in the cache store allows + * some operations to perform faster, as they can avoid querying the + * potentially slow cache store. A size-limited scheme may be used to + * control how many misses are cached. If unspecified no cache-miss data + * is maintained. + * + * @return the miss cache scheme + */ + public LocalScheme getMissCacheScheme() + { + return m_schemeMissCache; + } + + /** + * Set the miss cache scheme. + * + * @param scheme the miss cache scheme + */ + @Injectable("miss-cache-scheme") + public void setMissCacheScheme(LocalScheme scheme) + { + m_schemeMissCache = scheme; + } + + // ----- data members --------------------------------------------------- + + /** + * The miss cache. + */ + private LocalScheme m_schemeMissCache; + + /** + * The duration that a value will live in the cache. + * Zero indicates no timeout. + */ + private Expression m_exprExpiryDelay = new LiteralExpression(new Seconds(0)); + + /** + * The expiry enabled flag. + */ + private Expression m_exprExpiryEnabled = new LiteralExpression(Boolean.FALSE); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/PagedExternalScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/PagedExternalScheme.java new file mode 100755 index 0000000000000..ecbafd05f4d9f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/PagedExternalScheme.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.storemanager.BinaryStoreManagerBuilder; +import com.tangosol.coherence.config.builder.storemanager.BinaryStoreManagerBuilderCustomization; +import com.tangosol.coherence.config.unit.Seconds; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.BinaryStoreManager; + +import com.tangosol.net.cache.SerializationPagedCache; + +import com.tangosol.util.Base; + +import java.util.Map; + +/** + * The {@link PagedExternalScheme} class is responsible for building a + * fully configured instance of a PagedExternalCache. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class PagedExternalScheme + extends AbstractLocalCachingScheme + implements BinaryStoreManagerBuilderCustomization + { + // ----- MapBuilder interface ------------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + validate(resolver); + + ClassLoader loader = dependencies.getClassLoader(); + int cPages = getPageLimit(resolver); + int cPageSecs = (int) getPageDurationSeconds(resolver).get(); + boolean fBackup = dependencies.isBackup(); + boolean fBinaryMap = dependencies.isBinary(); + + BinaryStoreManager storeManager = getBinaryStoreManagerBuilder().realize(resolver, loader, true); + + // create the cache, which is either internal or custom + ParameterizedBuilder bldrCustom = getCustomBuilder(); + + if (bldrCustom == null) + { + return fBinaryMap + ? instantiateSerializationPagedCache(storeManager, cPages, cPageSecs, true, fBackup) + : instantiateSerializationPagedCache(storeManager, cPages, cPageSecs, loader); + } + else + { + ParameterList listArgs = new ResolvableParameterList(); + + listArgs.add(new Parameter("store-manager", storeManager)); + listArgs.add(new Parameter("pages", cPages)); + listArgs.add(new Parameter("page-duration", cPageSecs)); + + if (fBinaryMap) + { + listArgs.add(new Parameter("fBinary", Boolean.TRUE)); + listArgs.add(new Parameter("fBackup", fBackup)); + } + else + { + listArgs.add(new Parameter("loader", loader)); + } + + return bldrCustom.realize(resolver, loader, listArgs); + } + } + + // ----- BinaryStoreManagerBuilderCustomization interface --------------- + + /** + * {@inheritDoc} + */ + public BinaryStoreManagerBuilder getBinaryStoreManagerBuilder() + { + return m_bldrStoreManager; + } + + /** + * {@inheritDoc} + */ + public void setBinaryStoreManagerBuilder(BinaryStoreManagerBuilder bldr) + { + m_bldrStoreManager = bldr; + } + + // ----- PagedExternalScheme methods ----------------------------------- + + /** + * Return the length of time that a page in the cache is current. + * After the duration is exceeded, the page is closed and a new current + * page is created. Legal values are zero or values between 5 and + * 604800 seconds (one week). + * + * @param resolver the ParameterResolver + * + * @return the page duration + */ + public Seconds getPageDurationSeconds(ParameterResolver resolver) + { + return m_exprPageDurationSeconds.evaluate(resolver); + } + + /** + * Set the page duration. + * + * @param expr the page duration expression + */ + @Injectable("page-duration") + public void setPageDurationSeconds(Expression expr) + { + m_exprPageDurationSeconds = expr; + } + + /** + * Return the maximum number of pages that the cache manages before older + * pages are destroyed. Legal values are zero or positive integers between + * 2 and 3600. + * + * @param resolver the ParameterResolver + * + * @return the page limit + */ + public int getPageLimit(ParameterResolver resolver) + { + return m_exprPageLimit.evaluate(resolver); + } + + /** + * Set the page limit. + * + * @param expr the page limit expression + */ + @Injectable + public void setPageLimit(Expression expr) + { + m_exprPageLimit = expr; + } + + // ----- internal ------------------------------------------------------- + + /** + * Construct an SerializationPagedCache using the specified parameters. + *

+ * This method exposes a corresponding SerializationPagedCache + * {@link SerializationPagedCache#SerializationPagedCache(BinaryStoreManager, + * int, int, ClassLoader) constructor} + * and is provided for the express purpose of allowing its override. + * + * @param storeMgr the BinaryStoreManager that provides BinaryStore + * objects that the serialized objects are written to + * @param cPages the maximum number of pages to have active at a time + * @param cPageSecs the length of time, in seconds, that a 'page' is + * current + * @param loader the ClassLoader to use for deserialization + * + * @return the instantiated {@link SerializationPagedCache} + */ + protected SerializationPagedCache instantiateSerializationPagedCache(BinaryStoreManager storeMgr, int cPages, + int cPageSecs, ClassLoader loader) + { + return new SerializationPagedCache(storeMgr, cPages, cPageSecs, loader); + } + + /** + * Construct an SerializationPagedCache using the specified parameters. + *

+ * This method exposes a corresponding SerializationPagedCache + * {@link SerializationPagedCache#SerializationPagedCache(BinaryStoreManager, + * int, int, boolean, boolean) constructor} + * and is provided for the express purpose of allowing its override. + * + * @param storeMgr the BinaryStoreManager that provides BinaryStore + * objects that the serialized objects are written to + * @param cPages the maximum number of pages to have active at a time + * @param cPageSecs the length of time, in seconds, that a 'page' is + * current + * @param fBinaryMap true indicates that this map will only manage + * binary keys and values + * @param fPassive true indicates that this map is a passive cache, + * which means that it is just a backup of the cache + * and does not actively expire data + * + * @return the instantiated {@link SerializationPagedCache} + */ + protected SerializationPagedCache instantiateSerializationPagedCache(BinaryStoreManager storeMgr, int cPages, + int cPageSecs, boolean fBinaryMap, boolean fPassive) + { + return new SerializationPagedCache(storeMgr, cPages, cPageSecs, fBinaryMap, fPassive); + } + + /** + * {@inheritDoc} + */ + @Override + protected void validate(ParameterResolver resolver) + { + super.validate(resolver); + + int cPages = getPageLimit(resolver); + int cPageSecs = (int) getPageDurationSeconds(resolver).get(); + + Base.checkRange(cPages, MIN_PAGE_LIMIT, MAX_PAGE_LIMIT, "Page limit"); + Base.checkRange(cPageSecs, MIN_PAGE_SECONDS, MAX_PAGE_SECONDS, "Page seconds"); + + Base.checkNotNull(m_bldrStoreManager, "StoreManager"); + } + + // ----- constants ----------------------------------------------------- + + /** + * The default page seconds. See the DefaultConfigurableCacheFactory for the initialization. + */ + private static final int DEFAULT_PAGE_SECONDS = 5; + + /** + * The minimum page limit. See {@link SerializationPagedCache} for the usage. + */ + private static final int MIN_PAGE_SECONDS = 5; + + /** + * The maximum page limit. See {@link SerializationPagedCache} for the usage. + */ + private static final int MAX_PAGE_SECONDS = 604800; + + /** + * The default page limit. See {@link SerializationPagedCache} for the initialization. + */ + private static final int DEFAULT_PAGE_LIMIT = 0; + + /** + * The minimum page limit. See {@link SerializationPagedCache} for the usage. + */ + private static final int MIN_PAGE_LIMIT = 2; + + /** + * The maximum page limit. See {@link SerializationPagedCache} for the usage. + */ + private static final int MAX_PAGE_LIMIT = 3600; + + // ----- data members --------------------------------------------------- + + /** + * The StoreManager builder. + */ + private BinaryStoreManagerBuilder m_bldrStoreManager; + + /** + * The page duration. + */ + private Expression m_exprPageDurationSeconds = + new LiteralExpression(new Seconds(DEFAULT_PAGE_SECONDS)); + + /** + * The page limit. + */ + private Expression m_exprPageLimit = new LiteralExpression(DEFAULT_PAGE_LIMIT); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/PagedTopicScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/PagedTopicScheme.java new file mode 100644 index 0000000000000..62fe34b25c8b9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/PagedTopicScheme.java @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.oracle.coherence.common.net.InetAddresses; +import com.oracle.coherence.common.util.Duration; +import com.oracle.coherence.common.util.MemorySize; + +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.builder.UnitCalculatorBuilder; +import com.tangosol.coherence.config.unit.Seconds; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.NullParameterResolver; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.injection.SimpleInjector; + +import com.tangosol.internal.net.topic.impl.paged.Configuration; +import com.tangosol.internal.net.topic.impl.paged.PagedTopic; +import com.tangosol.internal.net.topic.impl.paged.PagedTopicCaches; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.CacheService; +import com.tangosol.net.Cluster; +import com.tangosol.net.ExtensibleConfigurableCacheFactory; +import com.tangosol.net.NamedCollection; +import com.tangosol.net.Service; +import com.tangosol.net.ValueTypeAssertion; + +import com.tangosol.net.cache.LocalCache; + +import com.tangosol.net.security.StorageAccessAuthorizer; + +import com.tangosol.net.topic.NamedTopic; + +import com.tangosol.util.Base; +import com.tangosol.util.ResourceRegistry; +import com.tangosol.util.ResourceResolver; +import com.tangosol.util.ResourceResolverHelper; + +import java.util.List; + +/** + * A {@link PagedTopicScheme} is responsible for building a topic. + * + * @author jk 2015.05.21 + * @since Coherence 14.1.1 + */ +public class PagedTopicScheme + extends DistributedScheme + implements NamedTopicScheme + { + // ----- AbstractServiceScheme methods ---------------------------------- + + /** + * DefaultServiceName to use if none configured. + * + * @return default service name + */ + @Override + protected String getDefaultServiceName() + { + // override default service type name of "DistributedCache" + return DEFAULT_SERVICE_NAME; + } + + // ----- TopicScheme methods -------------------------------------------- + + @Override + public boolean realizes(Class type) + { + return NamedTopic.class.equals(type); + } + + /** + * Return the {@link BackingMapScheme} used for the storage of this scheme. + + * @param resolver potentially override default unit-calculator of BINARY + * + * @return the scheme + */ + public CachingScheme getStorageScheme(ParameterResolver resolver) + { + if (m_schemeBackingMap == null) + { + // default storage scheme + LocalScheme scheme = new LocalScheme(); + + // NOTE: we don't set the scheme's high-units as topic data isn't subject to eviction + // but we do set the calculator and factor so that the topic impl can evaluate the size + // and throttle publishers accordingly + scheme.setUnitCalculatorBuilder(getUnitCalculatorBuilder(resolver)); + long cbHigh = getHighUnits(resolver); + if (cbHigh >= Integer.MAX_VALUE) + { + scheme.setUnitFactor((r) -> 1024); + } + m_schemeBackingMap = scheme; + } + return m_schemeBackingMap; + } + + /** + * Return the {@link BackingMapScheme} used for the storage of this scheme. + * + * @return the scheme + */ + public CachingScheme getStorageScheme() + { + return getStorageScheme(NULL_PARAMETER_RESOLVER); + } + + /** + * Set the {@link BackingMapScheme} which builds the backing map for + * the internal caches used to implement this scheme. + * + * @param scheme the scheme builder + */ + @Injectable("storage") + public void setStorageScheme(CachingScheme scheme) + { + m_schemeBackingMap = scheme; + } + + /** + * Return the binary limit of the size of a page in a topic. Contains the target number + * of bytes that can be placed in a page before the page is deemed to be full. + * Legal values are positive integers. + * + * @param resolver the ParameterResolver + * + * @return the page size + */ + public int getPageSize(ParameterResolver resolver) + { + return (int) m_exprPageSize.evaluate(resolver).getByteCount(); + } + + /** + * Set the page size. + * + * @param expr the page high units expression + */ + @Injectable("page-size") + public void setPageSize(Expression expr) + { + m_exprPageSize = expr; + } + + /** + * Return the high-units + * + * @param resolver the ParameterResolver + * + * @return the unit factor + */ + public long getHighUnits(ParameterResolver resolver) + { + return m_exprHighUnits.evaluate(resolver).getByteCount(); + } + + /** + * Set the high-units + * + * @param expr the high-units expression + */ + @Injectable("high-units") + public void setHighUnits(Expression expr) + { + m_exprHighUnits = expr; + } + + /** + * Return the {@link Expression} transient. to use to determine + * whether the backing map is transient. + * + * @return the {@link Expression} transient. to use to determine + * whether the backing map is transient + */ + public Expression getTransientExpression() + { + return m_exprTransient; + } + + /** + * Set the transient flag. + * + * @param expr true to make the backing map transient. + */ + @Injectable + public void setTransient(Expression expr) + { + m_exprTransient = expr; + } + + /** + * Obtains the {@link Expression} defining the name of the {@link StorageAccessAuthorizer}. + * + * @return the name of the {@link StorageAccessAuthorizer} or null if + * one has not been configured. + */ + public Expression getStorageAccessAuthorizer() + { + return m_exprStorageAccessAuthorizer; + } + + /** + * Sets the {@link Expression} defining the name of the {@link StorageAccessAuthorizer}. + * + * @param exprStorageAccessAuthorizer the {@link Expression} + */ + @Injectable("storage-authorizer") + public void setStorageAccessAuthorizer(Expression exprStorageAccessAuthorizer) + { + m_exprStorageAccessAuthorizer = exprStorageAccessAuthorizer; + + BackingMapScheme scheme = getBackingMapScheme(); + + if (scheme != null) + { + scheme.setStorageAccessAuthorizer(m_exprStorageAccessAuthorizer); + } + } + + /** + * Return the amount of time that elements offered to the queue remain + * visible to consumers. + * + * @param resolver the ParameterResolver + * + * @return the amount of time that elements offered to the queue remain + * visible to consumers + */ + public Seconds getExpiryDelay(ParameterResolver resolver) + { + return m_exprExpiryDelay.evaluate(resolver); + } + + /** + * Set the amount of time that elements offered to the queue + * remain visible to consumers. + * + * @param expr the element expiry delay expression + */ + @Injectable + public void setExpiryDelay(Expression expr) + { + m_exprExpiryDelay = expr; + } + + /** + * Determine whether to retain consumed values. + * + * @param resolver the ParameterResolver + * + * @return {@code true} if the topic should retain consumed values + */ + public boolean isRetainConsumed(ParameterResolver resolver) + { + Boolean fRetain = m_exprRetainConsumed.evaluate(resolver); + + return fRetain != null && fRetain; + } + + /** + * Set whether to retain consumed values. + * + * @param expr the retain consumed values expression + */ + @Injectable("retain-consumed") + public void setRetainConsumed(Expression expr) + { + m_exprRetainConsumed = expr; + } + + @Override + @Injectable("interceptors") + public void setEventInterceptorBuilders(List listBuilders) + { + super.setEventInterceptorBuilders(listBuilders); + } + + // ----- ServiceScheme methods ------------------------------------------ + + @Override + public NamedTopic realize(ValueTypeAssertion typeConstraint, + ParameterResolver resolver, Dependencies deps) + { + String sQueueName = deps.getCacheName(); + CacheService service = ensureConfiguredService(resolver, deps); + PagedTopicCaches topicCaches = new PagedTopicCaches(sQueueName, service); + + return new PagedTopic<>(topicCaches); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Ensure service and its topic configuration. + * + * PagedTopicConfiguration is registered in corresponding service's resource registry. + * + * @param resolver the ParameterResolver + * @param deps the {@link MapBuilder} dependencies + * + * @return corresponding CacheService for this scheme + */ + public CacheService ensureConfiguredService(ParameterResolver resolver, Dependencies deps) + { + ClassLoader loader = deps.getClassLoader(); + String sTopicName = PagedTopicCaches.Names.getTopicName(deps.getCacheName()); + CacheService service = getOrEnsureService(deps); + ResourceRegistry registry = service.getResourceRegistry(); + Configuration configuration = registry.getResource(Configuration.class, sTopicName); + + if (configuration == null) + { + configuration = createConfiguration(resolver, loader); + + registry.registerResource(Configuration.class, sTopicName, configuration); + } + + return service; + } + + /** + * Get or ensure service corresponding to this scheme. + * + * Optimized to avoid ensureService synchronization on cluster and service + * when possible. This behavoir is required on server side. Intermittent deadlock occurs + * calling ensureService on server side from inside service implementation. + * + * @return {@link CacheService} + */ + private CacheService getOrEnsureService(Dependencies deps) + { + ExtensibleConfigurableCacheFactory eccf = + (ExtensibleConfigurableCacheFactory) deps.getConfigurableCacheFactory(); + + Service service = CacheFactory.getCluster().getService(getScopedServiceName()); + + if (service == null) + { + service = eccf.ensureService(this); + } + + if (service instanceof CacheService) + { + return (CacheService) service; + } + else + { + throw new IllegalArgumentException("Error: the configured service " + service.getInfo().getServiceName() + + " is not a CacheService"); + } + } + + + /** + * Create a {@link Configuration} based on the values contained in this scheme. + * + * @param resolver the {@link ParameterResolver} to use to resolve configuration values + * @param loader the {@link ClassLoader} to use + * + * @return a {@link Configuration} based on the values contained in this scheme + */ + public Configuration createConfiguration(ParameterResolver resolver, ClassLoader loader) + { + // enable topic-mapping init-params to be injected into PagedTopicScheme. + // still need to support DistributedTopics Service Parameters in future. + SimpleInjector injector = new SimpleInjector(); + ResourceResolver resourceResolver = ResourceResolverHelper.resourceResolverFrom(PagedTopicScheme.class, this); + + injector.inject(this, ResourceResolverHelper.resourceResolverFrom(resourceResolver, resourceResolver)); + + long cbServer = getHighUnits(resolver); + int cbPage = getPageSize(resolver); + long expiryDelayMillis = LocalCache.DEFAULT_EXPIRE; + Seconds expiryDelaySeconds = getExpiryDelay(resolver); + boolean fRetainConsumed = isRetainConsumed(resolver); + + if (expiryDelaySeconds != null) + { + expiryDelayMillis = expiryDelaySeconds.as(Duration.Magnitude.MILLI); + } + + Cluster cluster = CacheFactory.getCluster(); + int nMTU = InetAddresses.getLocalMTU(cluster.getLocalMember().getAddress()); + + if (nMTU == 0) + { + nMTU = 1500; + } + + int nMaxBatchSizeBytes; + try + { + // Detect Overflow.InetAddresses.getLocalMTU(NetworkInterface) returns Integer.MAX_VALUE on windows/jdk11 for loopback. + nMaxBatchSizeBytes = Math.multiplyExact(nMTU, cluster.getDependencies().getPublisherCloggedCount()); + } + catch (ArithmeticException e) + { + nMaxBatchSizeBytes = Integer.MAX_VALUE; + } + + + Configuration configuration = new Configuration(); + + configuration.setServerCapacity(cbServer); + configuration.setPageCapacity(cbPage); + configuration.setElementExpiryMillis(expiryDelayMillis); + configuration.setMaxBatchSizeBytes(Math.min(cbPage, nMaxBatchSizeBytes)); + configuration.setRetainConsumed(fRetainConsumed); + CacheFactory.log("PagedTopicScheme configuration: " + configuration, Base.LOG_QUIET); + return configuration; + } + + /** + * Obtain the builder for the unit calculator. + * + * @param resolver the {@link ParameterResolver} to use + * + * @return the builder for the unit calculator + */ + private UnitCalculatorBuilder getUnitCalculatorBuilder(ParameterResolver resolver) + { + UnitCalculatorBuilder bldr = new UnitCalculatorBuilder(); + Parameter parm = resolver.resolve("unit-calculator"); + Expression expr = parm == null ? new LiteralExpression<>("BINARY") : parm.evaluate(resolver).as(Expression.class); + + bldr.setUnitCalculatorType(expr); + + return bldr; + } + + // ----- constants ------------------------------------------------------ + + /** + * An empty {@link ParameterResolver}. + */ + private static final ParameterResolver NULL_PARAMETER_RESOLVER = new NullParameterResolver(); + + /** + * Default service name for PagedTopicScheme, overrides PagedTopicScheme service type which is DistributedCache. + */ + public static final String DEFAULT_SERVICE_NAME = "DistributedTopic"; + + // ----- data members --------------------------------------------------- + + /** + * The page capacity + */ + private Expression m_exprPageSize = new LiteralExpression<>(new MemorySize(Configuration.DEFAULT_PAGE_CAPACITY_BYTES)); + + /** + * The high-units + */ + private Expression m_exprHighUnits = new LiteralExpression<>(new MemorySize(0)); + + /** + * The partitioned flag. + */ + private Expression m_exprTransient = new LiteralExpression<>(Boolean.FALSE); + + /** + * The name of the StorageAccessAuthorizer to use. + */ + private Expression m_exprStorageAccessAuthorizer = null; + + /** + * The backing map scheme for all internal caches used to implement topic. + */ + private CachingScheme m_schemeBackingMap; + + /** + * The retain consumed elements flag. + */ + private Expression m_exprRetainConsumed = new LiteralExpression<>(Boolean.FALSE); + + /** + * The duration that a value will live in the cache. + * Zero indicates no timeout. + */ + private Expression m_exprExpiryDelay = new LiteralExpression<>(new Seconds(0)); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/PagedTopicStorageScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/PagedTopicStorageScheme.java new file mode 100644 index 0000000000000..76736f4ea2c07 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/PagedTopicStorageScheme.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.NamedCache; + +import java.util.Map; + +/** + * A scheme that builds the inner scheme of the backing map scheme of a topic. + * + * @author jk 2015.05.29 + * @since Coherence 14.1.1 + */ +public class PagedTopicStorageScheme + extends WrapperCachingScheme + { + // ----- constructors --------------------------------------------------- + + /** + * Create a {@link PagedTopicStorageScheme}. + * + * @param schemeStorage the {@link CachingScheme} defining the storage scheme for the topic + * @param topicScheme the {@link PagedTopicScheme} defining the topic + */ + public PagedTopicStorageScheme(CachingScheme schemeStorage, PagedTopicScheme topicScheme) + { + super(schemeStorage); + f_schemeTopic = topicScheme; + } + + // ----- CachingScheme methods ------------------------------------------ + + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + f_schemeTopic.ensureConfiguredService(resolver, dependencies); + + return super.realizeMap(resolver, dependencies); + } + + @Override + public NamedCache realizeCache(ParameterResolver resolver, Dependencies dependencies) + { + f_schemeTopic.ensureConfiguredService(resolver, dependencies); + + return super.realizeCache(resolver, dependencies); + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link PagedTopicStorageScheme} defining the topic configuration. + */ + private final PagedTopicScheme f_schemeTopic; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ProxyScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ProxyScheme.java new file mode 100644 index 0000000000000..d6911f35c33d9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ProxyScheme.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.internal.net.service.grid.DefaultProxyServiceDependencies; +import com.tangosol.internal.net.service.grid.DefaultReplicatedCacheDependencies; +import com.tangosol.internal.net.service.grid.LegacyXmlProxyServiceHelper; + +import com.tangosol.internal.net.service.grid.ProxyServiceDependencies; +import com.tangosol.net.CacheFactory; +import com.tangosol.net.OperationalContext; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Base; + +/** + * The {@link ProxyScheme} class builds a Proxy scheme. + * + * @author pfm 2011.12.06 + * @since Coherence 12.1.2 + */ +public class ProxyScheme + extends AbstractCachingScheme + { + // ----- constructors -------------------------------------------------- + + /** + * Constructs a {@link ProxyScheme}. + */ + public ProxyScheme() + { + m_serviceDependencies = new DefaultProxyServiceDependencies(); + } + + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return "Proxy"; + } + + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunningClusterNeeded() + { + return true; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RamJournalScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RamJournalScheme.java new file mode 100755 index 0000000000000..fbc6c52e884cd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RamJournalScheme.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.Map; + + +/** + * The {@link RamJournalScheme} is used to create an instance of a Ram Journal map. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class RamJournalScheme + extends AbstractJournalScheme + { + // ----- MapBuilder interface ------------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + throw new UnsupportedOperationException("Elastic Data features are not supported in Coherence CE"); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ReadWriteBackingMapScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ReadWriteBackingMapScheme.java new file mode 100755 index 0000000000000..a25c9f7f3c068 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ReadWriteBackingMapScheme.java @@ -0,0 +1,789 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.oracle.coherence.common.util.Duration.Magnitude; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.unit.Millis; +import com.tangosol.coherence.config.unit.Seconds; + +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.io.ClassLoaderAware; + +import com.tangosol.net.BackingMapManagerContext; +import com.tangosol.net.NamedCache; +import com.tangosol.net.cache.BinaryEntryStore; +import com.tangosol.net.cache.CacheLoader; +import com.tangosol.net.cache.LocalCache; +import com.tangosol.net.cache.ReadWriteBackingMap; +import com.tangosol.net.partition.PartitionAwareBackingMap; +import com.tangosol.net.partition.ReadWriteSplittingBackingMap; + +import com.tangosol.util.Base; +import com.tangosol.util.NullImplementation; +import com.tangosol.util.ObservableMap; + +import java.util.Map; + +/** + * The {@link RemoteCacheScheme} is responsible for creating a fully + * configured ReadWriteBackingMap. The setters are annotated so that CODI + * can automatically configure the builder After the builder is configured, + * the realize method can be called to create either a custom ReadWriteBackingMap + * or the internal Coherence ReadWriteBackingMap. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class ReadWriteBackingMapScheme + extends AbstractLocalCachingScheme + { + // ----- MapBuilder interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ReadWriteBackingMap realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + validate(resolver); + + ReadWriteBackingMap rwbm = null; + ClassLoader loader = dependencies.getClassLoader(); + CacheStoreScheme bldrCacheStore = getCacheStoreScheme(); + MapBuilder bldrInternalMap = getInternalScheme(); + LocalScheme bldrMissCache = getMissCacheScheme(); + ObservableMap mapInternal = getInternalMap(); + BackingMapManagerContext contextBmm = dependencies.getBackingMapManagerContext(); + ParameterizedBuilder bldrCustom = getCustomBuilder(); + boolean fReadOnly = isReadOnly(resolver); + boolean fSplitting = mapInternal instanceof PartitionAwareBackingMap; + + // create the internal map if it hasn't been set already. For example, + // for partitioned backing maps, the BackingMapManager will set the + // internal map. + if (mapInternal == null) + { + mapInternal = bldrInternalMap == null + ? null : (ObservableMap) bldrInternalMap.realizeMap(resolver, dependencies); + } + + // create the miss cache + LocalCache mapMisses = bldrMissCache == null + ? null : (LocalCache) bldrMissCache.realizeMap(resolver, dependencies); + + // create the cache store + Object store = bldrCacheStore == null ? null : bldrCacheStore.realize(resolver, dependencies); + + // init the binary store variable + BinaryEntryStore storeBinary = null; + + if (store instanceof BinaryEntryStore) + { + // If the store implements the BinaryEntryStore interface, use it. + // The only exception from that rule is the SCHEME_REMOTE_CACHE case, + // (which always returns the SafeNamedCache), that was de-optimized + // due to the Serializers incompatibility + if (!(store instanceof NamedCache && store instanceof ClassLoaderAware + && ((ClassLoaderAware) store).getContextClassLoader() != NullImplementation.getClassLoader())) + { + storeBinary = (BinaryEntryStore) store; + } + } + + // get the write behind delay; if the "write-delay" element exists, try + // to parse it; otherwise, parse the "write-delay-seconds" element + long cWriteBehindMillis = getWriteDelay(resolver).as(Magnitude.MILLI); + + if (cWriteBehindMillis == 0) + { + cWriteBehindMillis = 1000L * getWriteDelaySeconds(resolver); + } + + int cWriteBehindSec = cWriteBehindMillis == 0 ? 0 : Math.max(1, (int) (cWriteBehindMillis / 1000)); + + double dflRefreshAheadFactor = getRefreshAheadFactor(resolver); + + // create the internal ReadWriteBackingMap or a custom map. + if (bldrCustom == null) + { + if (storeBinary == null) + { + CacheLoader storeObject = (CacheLoader) store; + + rwbm = fSplitting + ? instantiateReadWriteSplittingBackingMap(contextBmm, (PartitionAwareBackingMap) mapInternal, + mapMisses, storeObject, fReadOnly, cWriteBehindSec, dflRefreshAheadFactor) + : instantiateReadWriteBackingMap(contextBmm, mapInternal, mapMisses, storeObject, fReadOnly, + cWriteBehindSec, dflRefreshAheadFactor); + } + else + { + rwbm = fSplitting + ? instantiateReadWriteSplittingBackingMap(contextBmm, (PartitionAwareBackingMap) mapInternal, + mapMisses, storeBinary, fReadOnly, cWriteBehindSec, dflRefreshAheadFactor) + : instantiateReadWriteBackingMap(contextBmm, mapInternal, mapMisses, storeBinary, fReadOnly, + cWriteBehindSec, dflRefreshAheadFactor); + } + } + else + { + ParameterList listArgs = new ResolvableParameterList(); + + listArgs.add(new Parameter("contextBmm", contextBmm)); + listArgs.add(new Parameter("mapInternal", mapInternal)); + listArgs.add(new Parameter("mapMisses", mapMisses)); + listArgs.add(new Parameter("storeBinary", storeBinary == null ? store : storeBinary)); + listArgs.add(new Parameter("readOnly", Boolean.valueOf(fReadOnly))); + listArgs.add(new Parameter("writeBehindSec", Integer.valueOf(cWriteBehindSec))); + listArgs.add(new Parameter("refreshAheadFactory", Double.valueOf(dflRefreshAheadFactor))); + rwbm = bldrCustom.realize(resolver, loader, listArgs); + } + + // Read/Write Threads will have the cache name appended to the thread name + rwbm.setCacheName(dependencies.getCacheName()); + rwbm.setRethrowExceptions(isRollbackCacheStoreFailures(resolver)); + rwbm.setWriteBatchFactor(getWriteBatchFactor(resolver)); + rwbm.setWriteRequeueThreshold(getWriteRequeueThreshold(resolver)); + rwbm.setWriteMaxBatchSize(getWriteMaxBatchSize(resolver)); + + if (cWriteBehindMillis != 1000L * cWriteBehindSec) + { + rwbm.setWriteBehindMillis(cWriteBehindMillis); + } + + rwbm.setCacheStoreTimeoutMillis(getCacheStoreTimeout(resolver).as(Magnitude.MILLI)); + + BundleManager managerBundle = bldrCacheStore == null ? null : bldrCacheStore.getBundleManager(); + + if (managerBundle != null) + { + managerBundle.ensureBundles(resolver, rwbm.getCacheStore()); + } + + return rwbm; + } + + // ----- ObservableCachingScheme interface ------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public void establishMapListeners(Map map, ParameterResolver resolver, Dependencies dependencies) + { + super.establishMapListeners(map, resolver, dependencies); + + if (getInternalScheme() instanceof ObservableCachingScheme && map instanceof ReadWriteBackingMap) + { + ((ObservableCachingScheme) getInternalScheme()) + .establishMapListeners(((ReadWriteBackingMap) map).getInternalCache(), resolver, dependencies); + } + + } + + // ----- ReadWriteBackingMapScheme methods ------------------------------ + + /** + * Return the {@link CacheStoreScheme} used to create a CacheStore + * or CacheLoader. + * + * @return the builder + */ + public CacheStoreScheme getCacheStoreScheme() + { + return m_schemeCacheStore; + } + + /** + * Set the {@link CacheStoreScheme} builder. + * + * @param bldr the builder + */ + @Injectable("cachestore-scheme") + public void setCacheStoreScheme(CacheStoreScheme bldr) + { + m_schemeCacheStore = bldr; + } + + /** + * Return the timeout interval to use for CacheStore read and write + * operations. If a CacheStore operation times out, the executing thread + * is interrupted and may ultimately lead to the termination of the cache + * service. Timeouts of asynchronous CacheStore operations (for example, + * refresh-ahead, write-behind) do not result in service termination. + * + * @param resolver the ParameterResolver + * + * @return the timeout + */ + public Millis getCacheStoreTimeout(ParameterResolver resolver) + { + return m_exprCacheStoreTimeout.evaluate(resolver); + } + + /** + * Set the timeout interval to use for CacheStore/CacheLoader read and + * write operations. + * + * @param expr the timeout interval expression + */ + @Injectable("cachestore-timeout") + public void setCacheStoreTimeout(Expression expr) + { + m_exprCacheStoreTimeout = expr; + } + + /** + * Return the scheme which the specifies the map used to cache entries. + * + * @return the scheme for the internal map + */ + public CachingScheme getInternalScheme() + { + return m_schemeInternal; + } + + /** + * Set the internal scheme. + * + * @param scheme the internal scheme + */ + @Injectable("internal-cache-scheme") + public void setInternalScheme(CachingScheme scheme) + { + m_schemeInternal = scheme; + } + + /** + * Return the internal map which is set by the backing map manager when + * the partitioned flag is true. Otherwise the map will be null. + * + * @return the internal map + */ + public ObservableMap getInternalMap() + { + return m_mapInternal; + } + + /** + * Set the internal map. + * + * @param map the internal map + */ + public void setInternalMap(ObservableMap map) + { + m_mapInternal = map; + } + + /** + * Return the {@link Scheme} for the cache used to maintain information on cache + * misses. The miss-cache is used track keys which were not found in the + * cache store. The knowledge that a key is not in the cache store allows + * some operations to perform faster, as they can avoid querying the potentially + * slow cache store. A size-limited scheme may be used to control how many + * misses are cached. If unspecified no cache-miss data is maintained. + * + * @return the miss cache scheme + */ + public LocalScheme getMissCacheScheme() + { + return m_schemeMissCache; + } + + /** + * Set the miss cache {@link Scheme}. + * + * @param scheme the miss cache scheme + */ + @Injectable("miss-cache-scheme") + public void setMissCacheScheme(LocalScheme scheme) + { + m_schemeMissCache = scheme; + } + + /** + * Returns true if the cache is read only. A read-only cache loads data + * from cache store for read operations and does not perform any writing + * to the cache store when the cache is updated. + * + * @param resolver the ParameterResolver + * + * @return true if the cache is read only + */ + public boolean isReadOnly(ParameterResolver resolver) + { + return m_exprReadOnly.evaluate(resolver); + } + + /** + * Set the read-only flag. + * + * @param expr true if the cache is read-only + */ + @Injectable + public void setReadOnly(Expression expr) + { + m_exprReadOnly = expr; + } + + /** + * Return refresh-ahead-factor used to calculate the "soft-expiration" + * time for cache entries. Soft-expiration is the point in time before + * the actual expiration after which any access request for an entry + * schedules an asynchronous load request for the entry. This attribute + * is only applicable if the internal cache is a LocalCache, with a + * non-zero expiry delay. The value is expressed as a percentage of the + * internal LocalCache expiration interval. If zero, refresh-ahead scheduling + * is disabled. If 1.0, then any get operation immediately triggers an + * asynchronous reload. Legal values are nonnegative doubles less than + * or equal to 1.0. + * + * @param resolver the ParameterResolver + * + * @return the refresh-ahead factor + */ + public double getRefreshAheadFactor(ParameterResolver resolver) + { + return m_exprRefreshAheadFactor.evaluate(resolver); + } + + /** + * Set the refresh ahead factor. + * + * @param expr the refresh ahead factor + */ + @Injectable + public void setRefreshAheadFactor(Expression expr) + { + m_exprRefreshAheadFactor = expr; + } + + /** + * Return true if exceptions caught during synchronous cachestore operations + * are rethrown to the calling thread (possibly over the network to a remote + * member). Legal values are true or false. If the value of this element is + * false, an exception caught during a synchronous cachestore operation is + * logged locally and the internal cache is updated. If the value is true, + * the exception is rethrown to the calling thread and the internal cache is + * not changed. If the operation was called within a transactional context, + * this would have the effect of rolling back the current transaction. + * + * @param resolver the ParameterResolver + * + * @return the rollback cachestore failures flag + */ + public boolean isRollbackCacheStoreFailures(ParameterResolver resolver) + { + return m_exprfRollbackCacheStoreFailures.evaluate(resolver); + } + + /** + * Set the flag to indicate that cache store failures should be rolled back. + * + * @param expr true if failures should be rolled back + */ + @Injectable("rollback-cachestore-failures") + public void setRollbackCacheStoreFailures(Expression expr) + { + m_exprfRollbackCacheStoreFailures = expr; + } + + /** + * Return the write-batch-factor element is used to calculate the "soft-ripe" + * time for write-behind queue entries. A queue entry is considered to be + * "ripe" for a write operation if it has been in the write-behind queue + * for no less than the write-delay interval. The "soft-ripe" time is the + * point in time before the actual ripe time after which an entry is included + * in a batched asynchronous write operation to the CacheStore (along with + * all other ripe and soft-ripe entries). + * + * @param resolver the ParameterResolver + * + * @return the write batch factor + */ + public double getWriteBatchFactor(ParameterResolver resolver) + { + return m_exprWriteBatchFactor.evaluate(resolver); + } + + /** + * Set the write batch factor. + * + * @param expr the write batch factor + */ + @Injectable + public void setWriteBatchFactor(Expression expr) + { + m_exprWriteBatchFactor = expr; + } + + /** + * Return the time interval to defer asynchronous writes to the cache store + * for a write-behind queue. If zero, synchronous writes to the cache store + * (without queuing) take place, otherwise the writes are asynchronous and + * deferred by specified time interval after the last update to the value + * in the cache. + * + * @param resolver the ParameterResolver + * + * @return the write behind delay + */ + public Seconds getWriteDelay(ParameterResolver resolver) + { + return m_exprWriteDelay.evaluate(resolver); + } + + /** + * Set the write behind delay. + * + * @param expr the write behind delay + */ + @Injectable + public void setWriteDelay(Expression expr) + { + m_exprWriteDelay = expr; + } + + /** + * Return the write behind delay in seconds. + * + * @param resolver the ParameterResolver + * + * @return the write behind delay in seconds + */ + public int getWriteDelaySeconds(ParameterResolver resolver) + { + return m_exprWriteDelaySeconds.evaluate(resolver); + } + + /** + * Set the write behind delay seconds. + * + * @param expr the write behind delay in seconds + */ + @Injectable + public void setWriteDelaySeconds(Expression expr) + { + m_exprWriteDelaySeconds = expr; + } + + /** + * Return the maximum number of entries to write in a single storeAll + * operation. Valid values are positive integers or zero. The default + * value is 128 entries. This value has no effect if write behind is disabled. + * + * @param resolver the ParameterResolver + * + * @return the write maximum batch size + */ + public int getWriteMaxBatchSize(ParameterResolver resolver) + { + return m_exprWriteMaxBatchSize.evaluate(resolver); + } + + /** + * Set the write max batch size. + * + * @param expr the write max batch size + */ + @Injectable + public void setWriteMaxBatchSize(Expression expr) + { + m_exprWriteMaxBatchSize = expr; + } + + /** + * Return the size of the write-behind queue at which additional actions + * could be taken. If zero, write-behind re-queuing is disabled. Otherwise, + * this value controls the frequency of the corresponding log messages. + * For example, a value of 100 produces a log message every time the size + * of the write queue is a multiple of 100. Legal values are positive + * integers or zero. + * + * @param resolver the ParameterResolver + * + * @return the write re-queue threshold + */ + public int getWriteRequeueThreshold(ParameterResolver resolver) + { + return m_exprWriteRequeueThreshold.evaluate(resolver); + } + + /** + * Set the size of the write-behind queue at which additional actions + * could be taken. + * + * @param expr the write re-queue threshold + */ + @Injectable + public void setWriteRequeueThreshold(Expression expr) + { + m_exprWriteRequeueThreshold = expr; + } + + // ----- internal ------------------------------------------------------- + + /** + * Construct a ReadWriteBackingMap using the specified parameters. + *

+ * This method exposes a corresponding ReadWriteBackingMap + * {@link ReadWriteBackingMap#ReadWriteBackingMap(BackingMapManagerContext, + * ObservableMap, Map, CacheLoader, boolean, int, double) constructor} + * and is provided for the express purpose of allowing its override. + * + * @param context the context provided by the CacheService + * which is using this backing map + * @param mapInternal the ObservableMap used to store the data + * internally in this backing map + * @param mapMisses the Map used to cache CacheStore misses + * (optional) + * @param store the object responsible for the persistence + * of the cached data (optional) + * @param fReadOnly pass true is the specified loader is in fact + * a CacheStore that needs to be used only for + * read operations; changes to the cache will + * not be persisted + * @param cWriteBehindSeconds number of seconds to write if there is a + * CacheStore; zero disables write-behind + * caching, which (combined with !fReadOnly) + * implies write-through + * @param dflRefreshAheadFactor the interval before an entry expiration time + * (expressed as a percentage of the internal + * cache expiration interval) during which an + * asynchronous load request for the + * entry will be scheduled; zero disables + * refresh-ahead; only applicable when + * the mapInternal parameter is an + * instance of {@link com.tangosol.net.cache.ConfigurableCacheMap} + * + * @return the instantiated {@link ReadWriteBackingMap} + */ + protected ReadWriteBackingMap instantiateReadWriteBackingMap(BackingMapManagerContext context, + ObservableMap mapInternal, Map mapMisses, CacheLoader store, boolean fReadOnly, int cWriteBehindSeconds, + double dflRefreshAheadFactor) + { + return new ReadWriteBackingMap(context, mapInternal, mapMisses, store, fReadOnly, cWriteBehindSeconds, + dflRefreshAheadFactor); + } + + /** + * Construct a ReadWriteBackingMap using the specified parameters. + *

+ * This method exposes a corresponding ReadWriteBackingMap + * {@link ReadWriteBackingMap#ReadWriteBackingMap(BackingMapManagerContext, + * ObservableMap, Map, BinaryEntryStore, boolean, int, double) constructor} + * and is provided for the express purpose of allowing its override. + * + * @param context the context provided by the CacheService + * which is using this backing map + * @param mapInternal the ObservableMap used to store the data + * internally in this backing map + * @param mapMisses the Map used to cache CacheStore misses + * (optional) + * @param storeBinary the BinaryEntryStore responsible for the + * persistence of the cached data (optional) + * @param fReadOnly pass true is the specified loader is in fact + * a CacheStore that needs to be used only for + * read operations; changes to the cache will + * not be persisted + * @param cWriteBehindSeconds number of seconds to write if there is a + * CacheStore; zero disables write-behind + * caching, which (combined with !fReadOnly) + * implies write-through + * @param dflRefreshAheadFactor the interval before an entry expiration time + * (expressed as a percentage of the internal + * cache expiration interval) during which an + * asynchronous load request for the + * entry will be scheduled; zero disables + * refresh-ahead; only applicable when + * the mapInternal parameter is an + * instance of {@link com.tangosol.net.cache.ConfigurableCacheMap} + * + * @return the instantiated {@link ReadWriteBackingMap} + */ + protected ReadWriteBackingMap instantiateReadWriteBackingMap(BackingMapManagerContext context, + ObservableMap mapInternal, Map mapMisses, BinaryEntryStore storeBinary, boolean fReadOnly, + int cWriteBehindSeconds, double dflRefreshAheadFactor) + { + return new ReadWriteBackingMap(context, mapInternal, mapMisses, storeBinary, fReadOnly, cWriteBehindSeconds, + dflRefreshAheadFactor); + } + + /** + * Construct a ReadWriteSplittingBackingMap using the specified parameters. + *

+ * This method exposes a corresponding ReadWriteSplittingBackingMap + * {@link ReadWriteSplittingBackingMap#ReadWriteSplittingBackingMap(BackingMapManagerContext, + * PartitionAwareBackingMap, Map, CacheLoader, boolean, int, double) constructor} + * and is provided for the express purpose of allowing its override. + * + * @param context the context provided by the CacheService + * which is using this backing map + * @param mapInternal the ObservableMap used to store the data + * internally in this backing map + * @param mapMisses the Map used to cache CacheStore misses + * (optional) + * @param store the object responsible for the persistence + * of the cached data (optional) + * @param fReadOnly pass true is the specified loader is in fact + * a CacheStore that needs to be used only for + * read operations; changes to the cache will + * not be persisted + * @param cWriteBehindSeconds number of seconds to write if there is a + * CacheStore; zero disables write-behind + * caching, which (combined with !fReadOnly) + * implies write-through + * @param dflRefreshAheadFactor the interval before an entry expiration time + * (expressed as a percentage of the internal + * cache expiration interval) during which an + * asynchronous load request for the + * entry will be scheduled; zero disables + * refresh-ahead; only applicable when + * the mapInternal parameter is an + * instance of {@link com.tangosol.net.cache.ConfigurableCacheMap} + * + * @return the instantiated {@link ReadWriteSplittingBackingMap} + */ + protected ReadWriteSplittingBackingMap instantiateReadWriteSplittingBackingMap(BackingMapManagerContext context, + PartitionAwareBackingMap mapInternal, Map mapMisses, CacheLoader store, boolean fReadOnly, + int cWriteBehindSeconds, double dflRefreshAheadFactor) + { + return new ReadWriteSplittingBackingMap(context, mapInternal, mapMisses, store, fReadOnly, cWriteBehindSeconds, + dflRefreshAheadFactor); + } + + /** + * Construct a ReadWriteSplittingBackingMap using the specified parameters. + *

+ * This method exposes a corresponding ReadWriteSplittingBackingMap + * {@link ReadWriteSplittingBackingMap#ReadWriteSplittingBackingMap(BackingMapManagerContext, + * PartitionAwareBackingMap, Map, BinaryEntryStore, boolean, int, double) constructor} + * and is provided for the express purpose of allowing its override. + * + * @param context the context provided by the CacheService + * which is using this backing map + * @param mapInternal the ObservableMap used to store the data + * internally in this backing map + * @param mapMisses the Map used to cache CacheStore misses + * (optional) + * @param storeBinary the BinaryEntryStore responsible for the + * persistence of the cached data (optional) + * @param fReadOnly pass true is the specified loader is in fact + * a CacheStore that needs to be used only for + * read operations; changes to the cache will + * not be persisted + * @param cWriteBehindSeconds number of seconds to write if there is a + * CacheStore; zero disables write-behind + * caching, which (combined with !fReadOnly) + * implies write-through + * @param dflRefreshAheadFactor the interval before an entry expiration time + * (expressed as a percentage of the internal + * cache expiration interval) during which an + * asynchronous load request for the + * entry will be scheduled; zero disables + * refresh-ahead; only applicable when + * the mapInternal parameter is an + * instance of {@link com.tangosol.net.cache.ConfigurableCacheMap} + * + * @return the instantiated {@link ReadWriteSplittingBackingMap} + */ + protected ReadWriteSplittingBackingMap instantiateReadWriteSplittingBackingMap(BackingMapManagerContext context, + PartitionAwareBackingMap mapInternal, Map mapMisses, BinaryEntryStore storeBinary, boolean fReadOnly, + int cWriteBehindSeconds, double dflRefreshAheadFactor) + { + return new ReadWriteSplittingBackingMap(context, mapInternal, mapMisses, storeBinary, fReadOnly, + cWriteBehindSeconds, dflRefreshAheadFactor); + } + + /** + * {@inheritDoc} + */ + @Override + protected void validate(ParameterResolver resolver) + { + super.validate(resolver); + + Base.checkNotNull(getInternalScheme(), "Internal scheme"); + } + + // ----- data members --------------------------------------------------- + + /** + * The CacheStore or CacheLoader scheme. + */ + private CacheStoreScheme m_schemeCacheStore; + + /** + * The internal scheme. + */ + private CachingScheme m_schemeInternal; + + /** + * The miss cache scheme. + */ + private LocalScheme m_schemeMissCache; + + /** + * The CacheStore or CacheLoader timeout. + */ + private Expression m_exprCacheStoreTimeout = new LiteralExpression(new Millis("0")); + + /** + * The flag that specifies if the cache is read-only. + */ + private Expression m_exprReadOnly = new LiteralExpression(Boolean.FALSE); + + /** + * The refresh ahead factor. + */ + private Expression m_exprRefreshAheadFactor = new LiteralExpression(0.0); + + /** + * The rollback CacheStore failures flag. + */ + private Expression m_exprfRollbackCacheStoreFailures = new LiteralExpression(Boolean.TRUE); + + /** + * The write batch factor. + */ + private Expression m_exprWriteBatchFactor = new LiteralExpression(0.0); + + /** + * The write-delay value. + */ + private Expression m_exprWriteDelay = new LiteralExpression(new Seconds("0")); + + /** + * The write-delay-seconds value. + */ + private Expression m_exprWriteDelaySeconds = new LiteralExpression(Integer.valueOf(0)); + + /** + * The write maximum batch size. + */ + private Expression m_exprWriteMaxBatchSize = new LiteralExpression(Integer.valueOf(128)); + + /** + * The write re-queue threshold. + */ + private Expression m_exprWriteRequeueThreshold = new LiteralExpression(Integer.valueOf(0)); + + /** + * The internal map. + */ + private ObservableMap m_mapInternal; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteCacheScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteCacheScheme.java new file mode 100644 index 0000000000000..6f947ff28c9ac --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteCacheScheme.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.net.service.extend.remote.DefaultRemoteCacheServiceDependencies; +import com.tangosol.internal.net.service.extend.remote.LegacyXmlRemoteCacheServiceHelper; +import com.tangosol.internal.net.service.extend.remote.RemoteCacheServiceDependencies; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.CacheService; +import com.tangosol.net.Cluster; +import com.tangosol.net.NamedCache; +import com.tangosol.net.OperationalContext; +import com.tangosol.net.Service; + +import com.tangosol.net.cache.BundlingNamedCache; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Base; + +/** + * The {@link RemoteCacheScheme} is responsible for building a remote cache. + * + * @author pfm 2011.10.04 + * @since Coherence 12.1.2 + */ +public class RemoteCacheScheme + extends AbstractCachingScheme + implements BundlingScheme + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link RemoteCacheScheme}. + */ + public RemoteCacheScheme() + { + m_serviceDependencies = new DefaultRemoteCacheServiceDependencies(); + m_mgrBundle = null; + } + + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return CacheService.TYPE_REMOTE; + } + + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunningClusterNeeded() + { + return false; + } + + /** + * {@inheritDoc} + */ + public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster) + { + Service service = super.realizeService(resolver, loader, cluster); + + injectScopeNameIntoService(service); + + return service; + } + + // ----- BundlingScheme methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public BundleManager getBundleManager() + { + return m_mgrBundle; + } + + // ----- RemoteCacheScheme methods -------------------------------------- + + /** + * Set the {@link BundleManager}. + * + * @param mgrBundle the BundleManager + */ + @Injectable("operation-bundling") + public void setBundleManager(BundleManager mgrBundle) + { + m_mgrBundle = mgrBundle; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link BundleManager}. + */ + private BundleManager m_mgrBundle; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteInvocationScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteInvocationScheme.java new file mode 100644 index 0000000000000..c3f31d2998b9f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/RemoteInvocationScheme.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.net.service.extend.remote.DefaultRemoteInvocationServiceDependencies; +import com.tangosol.internal.net.service.extend.remote.RemoteInvocationServiceDependencies; + +import com.tangosol.net.Cluster; +import com.tangosol.net.InvocationService; +import com.tangosol.net.Service; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +/** + * The {@link RemoteInvocationScheme} class builds a remote invocation service. + * + * @author pfm 2011.12.06 + * @since Coherence 12.1.2 + */ +public class RemoteInvocationScheme + extends AbstractServiceScheme + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link RemoteCacheScheme}. + */ + public RemoteInvocationScheme() + { + m_serviceDependencies = new DefaultRemoteInvocationServiceDependencies(); + } + + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return InvocationService.TYPE_REMOTE; + } + + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunningClusterNeeded() + { + return false; + } + + /** + * {@inheritDoc} + */ + public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster) + { + Service service = super.realizeService(resolver, loader, cluster); + + injectScopeNameIntoService(service); + + return service; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ReplicatedScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ReplicatedScheme.java new file mode 100644 index 0000000000000..96912d0778e69 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ReplicatedScheme.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.config.annotation.Injectable; + +import com.tangosol.internal.net.service.grid.DefaultPartitionedCacheDependencies; +import com.tangosol.internal.net.service.grid.DefaultReplicatedCacheDependencies; +import com.tangosol.internal.net.service.grid.LegacyXmlReplicatedCacheHelper; + +import com.tangosol.internal.net.service.grid.ReplicatedCacheDependencies; +import com.tangosol.net.CacheFactory; +import com.tangosol.net.CacheService; +import com.tangosol.net.OperationalContext; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Base; + +/** + * The {@link ReplicatedScheme} class builds replicated cache. + * + * @author pfm 2011.12.06 + * @since Coherence 12.1.2 + */ +public class ReplicatedScheme + extends AbstractCachingScheme + implements ClusteredCachingScheme + { + // ----- constructors -------------------------------------------------- + + /** + * Constructs a {@link ReplicatedScheme}. + */ + public ReplicatedScheme() + { + m_serviceDependencies = new DefaultReplicatedCacheDependencies(); + } + + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return CacheService.TYPE_REPLICATED; + } + + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunningClusterNeeded() + { + return true; + } + + // ----- ClusteredCachingScheme interface ------------------------------- + + /** + * Return the {@link BackingMapScheme} which builds the backing map for + * the clustered scheme. + * + * @return the scheme + */ + public BackingMapScheme getBackingMapScheme() + { + return m_schemeBackingMap; + } + + /** + * Set the {@link BackingMapScheme} which builds the backing map for + * the clustered scheme. + * + * @param scheme the scheme builder + */ + @Injectable + public void setBackingMapScheme(BackingMapScheme scheme) + { + m_schemeBackingMap = scheme; + } + + // ----- data members --------------------------------------------------- + + /** + * The backing map scheme. + */ + private BackingMapScheme m_schemeBackingMap; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/Scheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/Scheme.java new file mode 100644 index 0000000000000..63aea2c769add --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/Scheme.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +/** + * A {@link Scheme} defines the configuration information and necessary + * builders for the construction of various well-known and identifiable + * structures used by Coherence at runtime. + *

+ * Coherence {@link Scheme}s are best thought of as "templates" or "plans" that + * are used for constructing runtime infrastructure. Common examples include: + * services, caches, backing maps and cache stores. + *

+ * Some Coherence {@link Scheme}s, such as backing-map-schemes, are only used to + * create maps, whereas as others, such as distributed-scheme are used to create + * services, caches, and backing maps (as required by an inner scheme). In + * addition, Coherence also provides {@link Scheme}s that are unrelated to + * caches and maps, such as invocation-scheme and cache-store scheme. + * + * @author pfm 2011.12.30 + * @since Coherence 12.1.2 + */ +public interface Scheme + { + /** + * Obtains the name of the {@link Scheme}. + * + * @return the scheme name + */ + public String getSchemeName(); + + /** + * Determines if the {@link Scheme} is a defined and thus useful name. + * + * @return if the {@link Scheme} has a name. + */ + public boolean isAnonymous(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ServiceScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ServiceScheme.java new file mode 100644 index 0000000000000..804617d5a13de --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ServiceScheme.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import java.util.List; + +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.builder.ServiceBuilder; + +/** + * This interface exposes service related scheme information. Other schemes, + * such as {@link CachingScheme}, extend this class to add support for caches + * and maps. + * + * @author pfm 2011.12.30 + * @since Coherence 12.1.2 + */ +public interface ServiceScheme + extends Scheme + { + /** + * Return true if the service has auto-start enabled. + * + * @return the auto-start flag. + */ + public boolean isAutoStart(); + + /** + * Return the service name. + * + * @return the service name + */ + public String getServiceName(); + + /** + * Return the service name with any scoping applied. The scoped name in + * general has the following format: + *

+     *   [<domain-partition-name>'/'] [<application-scope>':'] <service-name>
+     * 
+ * + * @return the scoped service name + */ + public String getScopedServiceName(); + + /** + * Return the service type. + * + * @return the service type + */ + public String getServiceType(); + + /** + * Return the {@link ServiceBuilder} that is needed to build a service. + * + * @return the {@link ServiceBuilder} or null if the scheme does not support + * services. + */ + public ServiceBuilder getServiceBuilder(); + + /** + * Obtains the {@link List} of {@link NamedEventInterceptorBuilder}s that have been + * defined for the {@link ServiceScheme}. + *

+ * Note: For those {@link ServiceScheme}s don't support event interceptors, + * the returned value must be an empty list. + * + * @return an {@link List} over {@link NamedEventInterceptorBuilder}s + */ + public List getEventInterceptorBuilders(); + + /** + * Delimiter for the Domain Partition name in the {@link #getScopedServiceName() + * scoped service name} + */ + public final static String DELIM_DOMAIN_PARTITION = "/"; + + /** + * Delimiter for the Application Scope in the {@link #getScopedServiceName() + * scoped service name} + */ + public final static String DELIM_APPLICATION_SCOPE = ":"; + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TopicScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TopicScheme.java new file mode 100644 index 0000000000000..ba9386c981b39 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TopicScheme.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.coherence.config.builder.NamedCollectionBuilder; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.net.NamedCollection; + +/** + * The {@link TopicScheme} class is responsible for building a fully + * configured instance of a topic. + * + * @author jk 2015.06.27 + * @since Coherence 14.1.1 + */ +public interface TopicScheme + extends NamedCollectionBuilder, ServiceScheme + { + /** + * Obtain a configured topic service. + * + * @param resolver the {@link ParameterResolver} to use to resolve the service parameters + * @param deps the dependencies to use to configure the service + * + * @return a configured topic service + */ + public S ensureConfiguredService(ParameterResolver resolver, MapBuilder.Dependencies deps); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TransactionalScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TransactionalScheme.java new file mode 100644 index 0000000000000..35a1f775230dd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/TransactionalScheme.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.CacheService; +import com.tangosol.net.Cluster; +import com.tangosol.net.NamedCache; +import com.tangosol.net.Service; + +/** + * The {@link TransactionalScheme} class builds a transactional cache. The + * transactional cache is a logical cache backed by a set of distributed caches. + * A distributed service is used to handle the internal txn caches, since they are + * all distributed caches. Because the transactional cache is logical, it + * implements the realizeNamedCache used by ECCF.ensureCache. There is no backing + * map for transactional caches (because they are logical) so realizeMap is not + * needed. However, the internal distributed txn caches do have backing maps + * which are handled by the normal DistributedScheme code. + * + * @author pfm 2011.12.06 + * @since Coherence 12.1.2 + */ +public class TransactionalScheme + extends AbstractCachingScheme + { + // ----- ServiceScheme interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getServiceType() + { + return CacheService.TYPE_DISTRIBUTED; + } + + // ----- ServiceBuilder interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster) + { + throw new UnsupportedOperationException("Transactions are not supported in Coherence CE"); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isRunningClusterNeeded() + { + return true; + } + + // ----- NamedCacheBuilder interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public NamedCache realizeCache(ParameterResolver resolver, Dependencies dependencies) + { + throw new UnsupportedOperationException("Transactions are not supported in Coherence CE"); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ViewScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ViewScheme.java new file mode 100644 index 0000000000000..51039b9ae3cde --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/ViewScheme.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.oracle.coherence.common.collections.ConcurrentHashMap; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.net.service.DefaultViewDependencies; + +import com.tangosol.net.BackingMapManager; +import com.tangosol.net.CacheService; +import com.tangosol.net.Cluster; +import com.tangosol.net.ConfigurableCacheFactory; + +import com.tangosol.net.Service; + +import com.tangosol.net.cache.ContinuousQueryCache; + +import com.tangosol.net.internal.ViewCacheService; + +import com.tangosol.util.RegistrationBehavior; +import com.tangosol.util.ResourceRegistry; + +/** + * A Scheme that realizes both services and caches for Coherence 12.2.1.4 feature + * named 'views'. + * + * @author hr 2019.06.11 + * @since 12.2.1.4 + */ +// Internal Notes: +// The approach is to allow CQC's to be defined in the cache configuration and +// surfaced via CCF.ensureCache. To that end we define a CacheServie that can +// hold references to NamedCaches that represent views of other caches. This +// CacheService can be started by DCS like any regular CacheService and ensures +// caches are primed eagerly with data to have similar behavior semantics as +// replicated caches. Unlike regular services this service does not have its +// own type and is *not* instantiated or maintained by the cluster 'consciously' +// (we use the cluster's resource registry to store these services). +public class ViewScheme + extends AbstractCompositeScheme + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a ViewScheme. + */ + public ViewScheme() + { + m_serviceDependencies = new DefaultViewDependencies(); + + setFrontScheme(NO_SCHEME); + + DistributedScheme schemeBack = new DistributedScheme(); + schemeBack.setServiceName(getServiceType()); + setBackScheme(schemeBack); + } + + // ----- ServiceBuilder interface --------------------------------------- + + @Override + public Service realizeService(ParameterResolver resolver, ClassLoader loader, Cluster cluster) + { + validate(); + + // see if we've already created the appropriate CacheService + ResourceRegistry registry = cluster.getResourceRegistry(); + + ConcurrentHashMap mapServices = + registry.getResource(ConcurrentHashMap.class, ViewCacheService.KEY_CLUSTER_REGISTRY); + + if (mapServices == null) + { + registry.registerResource( + ConcurrentHashMap.class, + ViewCacheService.KEY_CLUSTER_REGISTRY, + ConcurrentHashMap::new, + RegistrationBehavior.IGNORE, + null); + + mapServices = registry.getResource(ConcurrentHashMap.class, ViewCacheService.KEY_CLUSTER_REGISTRY); + } + + String sServiceName = getScopedServiceName(); + Service service = mapServices.get(sServiceName); + if (service != null) + { + return service; + } + + // ensure the back service; the service is either already started or + // will be started as a part of the ViewCacheService + CachingScheme schemeBack = getBackScheme(); + if (schemeBack instanceof AbstractScheme) + { + ((AbstractScheme) schemeBack).validate(); + } + CacheService serviceBack = (CacheService) schemeBack.getServiceBuilder() + .realizeService(resolver, loader, cluster); + + DefaultViewDependencies deps = (DefaultViewDependencies) m_serviceDependencies; + + deps.setBackService(serviceBack); + + service = new ViewCacheService(serviceBack); + service.setDependencies(deps); + + if (mapServices.putIfAbsent(sServiceName, service) != null) + { + service = mapServices.get(sServiceName); // highly unlikely path + } + + return service; + } + + // ----- BackingMapManagerBuilder interface ----------------------------- + + @Override + public BackingMapManager realizeBackingMapManager(ConfigurableCacheFactory ccf) + { + return getBackScheme().realizeBackingMapManager(ccf); + } + + @Override + public String getServiceType() + { + // Note: this is the default service name used the for the back scheme + return ViewCacheService.TYPE_VIEW; + } + + // ----- constants ------------------------------------------------------ + + /** + * A CachingScheme that represents NO_VALUE. + */ + private static final CachingScheme NO_SCHEME = new ClassScheme(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/WrapperCachingScheme.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/WrapperCachingScheme.java new file mode 100644 index 0000000000000..156e617769e32 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/WrapperCachingScheme.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.scheme; + +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.builder.ServiceBuilder; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.BackingMapManager; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.NamedCache; + +import java.util.List; +import java.util.Map; + +/** + * A simple implementation of the {@link CachingScheme} interface + * built as a wrapper around another CachingScheme implementation. + * + * @author jk 2015.05.29 + * @since Coherence 14.1.1 + */ +public class WrapperCachingScheme + implements CachingScheme + { + // ----- constructors --------------------------------------------------- + + /** + * Create a {@link WrapperCachingScheme} that wraps a specified {@link CachingScheme}. + * + * @param innerScheme the {@link CachingScheme} being wrapped + */ + public WrapperCachingScheme(CachingScheme innerScheme) + { + f_innerScheme = innerScheme; + } + + // ----- accessor methods ----------------------------------------------- + + /** + * Obtain the wrapped {@link CachingScheme}. + * + * @return the wrapped {@link CachingScheme} + */ + public CachingScheme getCachingScheme() + { + return f_innerScheme; + } + + // ----- CachingScheme methods ------------------------------------------ + + @Override + public BackingMapManager realizeBackingMapManager(ConfigurableCacheFactory ccf) + { + return f_innerScheme.realizeBackingMapManager(ccf); + } + + @Override + public Map realizeMap(ParameterResolver resolver, Dependencies dependencies) + { + return f_innerScheme.realizeMap(resolver, dependencies); + } + + @Override + public NamedCache realizeCache(ParameterResolver resolver, Dependencies dependencies) + { + return f_innerScheme.realizeCache(resolver, dependencies); + } + + @Override + public boolean isAutoStart() + { + return f_innerScheme.isAutoStart(); + } + + @Override + public String getServiceName() + { + return f_innerScheme.getServiceName(); + } + + @Override + public String getScopedServiceName() + { + return f_innerScheme.getScopedServiceName(); + } + + @Override + public String getServiceType() + { + return f_innerScheme.getServiceType(); + } + + @Override + public ServiceBuilder getServiceBuilder() + { + return f_innerScheme.getServiceBuilder(); + } + + @Override + public List getEventInterceptorBuilders() + { + return f_innerScheme.getEventInterceptorBuilders(); + } + + @Override + public String getSchemeName() + { + return f_innerScheme.getSchemeName(); + } + + @Override + public boolean isAnonymous() + { + return f_innerScheme.isAnonymous(); + } + + // ----- data members --------------------------------------------------- + + /** + * The wrapped {@link CachingScheme}. + */ + private final CachingScheme f_innerScheme; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/package.html new file mode 100644 index 0000000000000..237b604c09383 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/scheme/package.html @@ -0,0 +1,6 @@ + +Defines the Coherence configuration object model classes and interfaces for +Caching and Service Schemes. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Bytes.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Bytes.java new file mode 100644 index 0000000000000..b128aa7abeb5a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Bytes.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.unit; + +import com.oracle.coherence.common.util.MemorySize; + +/** + * {@link Bytes} is a specialized {@link MemorySize} whose default constructor + * assumes that the specified units (when a magnitude is not specified) are always bytes. + *

+ * Note: This class is provided to simplify and support backwards compatibility + * during the injection of {@link MemorySize}s into appropriate classes. This + * class is not designed for general purpose representation of + * capacity. For general purpose representations of memory capacity, please use the + * {@link MemorySize} class. + * + * @author bo 2012.01.19 + * @since Coherence 12.1.2 + */ +public class Bytes + extends MemorySize + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link Bytes} representing a {@link MemorySize} measured in bytes. + * + * @param c the number of bytes + */ + public Bytes(int c) + { + super(c, Magnitude.BYTES); + } + + /** + * Constructs a {@link Bytes} based on another {@link MemorySize}. + * + * @param m the {@link MemorySize} + */ + public Bytes(MemorySize m) + { + super(m); + } + + /** + * Constructs a {@link Bytes} representing a {@link MemorySize} measured in bytes. + * + * @param s the number of bytes or other {@link MemorySize} when magnitudes are specified + * + * @see MemorySize#MemorySize(String, Magnitude) + */ + public Bytes(String s) + { + super(s, Magnitude.BYTES); + } + + // ----- Millis methods ------------------------------------------------- + + /** + * Obtain the {@link MemorySize} in bytes. + * + * @return the number of bytes in the {@link MemorySize} + */ + public long get() + { + return getByteCount(); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Megabytes.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Megabytes.java new file mode 100644 index 0000000000000..0fd08d3775aa5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Megabytes.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.unit; + +import com.oracle.coherence.common.util.MemorySize; + +/** + * {@link Megabytes} is a specialized {@link MemorySize} whose default constructor + * assumes that the specified units (when a they are not specified) are + * measured in megabytes. + *

+ * Note: This class is provided to simplify and support backwards compatibility + * during the injection of {@link MemorySize}s into appropriate classes. This + * class is not designed for general purpose representation of + * capacity. For general purpose representations of memory capacity, please use the + * {@link MemorySize} class. + * + * @author bo 2012.01.19 + * @since Coherence 12.1.2 + */ +public class Megabytes + extends MemorySize + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link Megabytes} representing a {@link MemorySize} measured in megabytes. + * + * @param c the number of megabytes + */ + public Megabytes(int c) + { + super(c, Magnitude.MB); + } + + /** + * Constructs a {@link Megabytes} based on another {@link MemorySize}. + * + * @param m the {@link MemorySize} + */ + public Megabytes(MemorySize m) + { + super(m); + } + + /** + * Constructs a {@link Megabytes} representing a {@link MemorySize} measured in megabytes. + * + * @param s the number of megabytes or other {@link MemorySize} when magnitudes are specified + * + * @see MemorySize#MemorySize(String, Magnitude) + */ + public Megabytes(String s) + { + super(s, Magnitude.MB); + } + + // ----- Megabytes methods ---------------------------------------------- + + /** + * Obtain the {@link MemorySize} in megabytes. + * + * @return the number of megabytes in the {@link MemorySize} + */ + public long get() + { + return (long) as(Magnitude.MB); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Millis.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Millis.java new file mode 100644 index 0000000000000..00ae34c698a38 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Millis.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.unit; + +import com.oracle.coherence.common.util.Duration; + +/** + * {@link Millis} is a specialized {@link Duration} whose default constructor + * assumes that the specified units of time (when the unit magnitude is not + * specified) are milliseconds. + *

+ * Note: This class is provided to simplify and support backwards compatibility + * during the injection of millisecond-based units of time into appropriate classes. + * This class is not designed for general purpose representation + * of units of time. For general purpose units of time, please use the + * {@link Duration} class. + * + * @author bo 2012.01.18 + * @since Coherence 12.1.2 + */ +public class Millis + extends Duration + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link Millis} based on another {@link Duration}. + * + * @param d the {@link Duration} + */ + public Millis(Duration d) + { + super(d); + } + + /** + * Constructs a {@link Millis} representing a {@link Duration} measured in milliseconds. + * + * @param s the number of milliseconds or other duration when magnitudes are specified + * + * @see Duration#Duration(String, Magnitude) + */ + public Millis(String s) + { + super(s, Magnitude.MILLI); + } + + // ----- Millis methods ------------------------------------------------- + + /** + * Obtain the {@link Duration} in units of milliseconds. + * + * @return the number of milliseconds in the {@link Duration} + */ + public long get() + { + return as(Magnitude.MILLI); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Seconds.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Seconds.java new file mode 100644 index 0000000000000..36d882e9cfadd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Seconds.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.unit; + +import com.oracle.coherence.common.util.Duration; + +/** + * {@link Seconds} is a specialized {@link Duration} whose default constructor + * assumes that the specified units of time (when the unit magnitude is not + * specified) are seconds. + *

+ * Note: This class is provided to simplify and support backwards compatibility + * during the injection of second-based units of time into appropriate classes. + * This class is not designed for general purpose representation + * of units of time. For general purpose units of time, please use the {@link Duration} class. + * + * @author bo 2012.01.18 + * @since Coherence 12.1.2 + */ +public class Seconds + extends Duration + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link Seconds} based on another {@link Duration}. + * + * @param d the {@link Duration} + */ + public Seconds(Duration d) + { + super(d); + } + + /** + * Constructs a {@link Seconds} representing a {@link Duration} measured in seconds. + * + * @param c the number of seconds + */ + public Seconds(int c) + { + super(c, Magnitude.SECOND); + } + + /** + * Constructs a {@link Seconds} representing a {@link Duration} measured in seconds. + * + * @param s the number of seconds or other duration when magnitudes are specified + * + * @see Duration#Duration(String, Magnitude) + */ + public Seconds(String s) + { + super(s, Magnitude.SECOND); + } + + // ----- Seconds methods ------------------------------------------------ + + /** + * Obtain the {@link Duration} in units of seconds. + * + * @return the number of seconds in the {@link Duration} + */ + public long get() + { + return as(Magnitude.SECOND); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Units.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Units.java new file mode 100644 index 0000000000000..2e20708937003 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/Units.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.unit; + +import com.oracle.coherence.common.util.MemorySize; + +/** + * {@link Units} is a union of {@link MemorySize} and unit count. + * + * Note: This class is provided to support the high-units configuration property + * which can either be a memory size string or the number of units. The default + * unit calculator depends on whether the configuration explicitly specified + * a memory size (e.g. <high-units>20M</high-units>). + * + * @author bo 2012.08.27 + * @author pfm 2012.08.27 + * @since Coherence 12.1.2 + */ +public class Units + { + /** + * Construct a Units object. If the sValue is a MemorySize string (e.g. 10M) then + * this instance of Units explicitly represents a MemorySize. Otherwise, this instance + * contains a unit count. + * + * @param sValue the unit count or memory size + */ + public Units(String sValue) + { + // Attempt to parse the string (this will be without the expression + // stuff by now) into either a value or a memory size. An exception + // will be thrown if the value is a memory size. + try + { + m_cUnits = Long.parseLong(sValue); + m_memorySize = null; + } + catch (NumberFormatException e) + { + m_memorySize = new MemorySize(sValue); + m_cUnits = m_memorySize.getByteCount(); + } + } + + /** + * Construct a Units object with the given unit count. + * + * @param cUnits the unit count + */ + public Units(long cUnits) + { + m_cUnits = cUnits; + m_memorySize = null; + } + + /** + * Construct a Units object with the given {@link MemorySize}. + * + * @param memorySize the {@link MemorySize} + */ + public Units(MemorySize memorySize) + { + m_memorySize = memorySize; + m_cUnits = memorySize.getByteCount(); + } + + /** + * Return the unit count. If this object was constructed with MemorySize then the + * count will be the number of bytes. + * + * @return the unit count + */ + public long getUnitCount() + { + return m_cUnits; + } + + /** + * Return the {@link MemorySize}. If this object was constructed with a unit count then + * {@link MemorySize} will be null. + * + * @return the {@link MemorySize} or null + */ + public MemorySize getMemorySize() + { + return m_memorySize; + } + + /** + * Return true if Units contains a {@link MemorySize}. + * + * @return true if Units contains a {@link MemorySize} + */ + public boolean isMemorySize() + { + return m_memorySize != null; + } + + // ----- data members --------------------------------------------------- + + /** + * The number of units. + */ + private long m_cUnits; + + /** + * The MemorySize if the units represent memory size. + */ + private MemorySize m_memorySize; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/package.html new file mode 100644 index 0000000000000..63a658bcf98fb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/unit/package.html @@ -0,0 +1,6 @@ + +Defines the standard units of measure for the Coherence configuration +object model. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/CacheConfigNamespaceHandler.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/CacheConfigNamespaceHandler.java new file mode 100644 index 0000000000000..432e6aa755806 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/CacheConfigNamespaceHandler.java @@ -0,0 +1,378 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.SchemeMappingRegistry; + +import com.tangosol.coherence.config.builder.storemanager.AsyncStoreManagerBuilder; +import com.tangosol.coherence.config.builder.storemanager.BdbStoreManagerBuilder; +import com.tangosol.coherence.config.builder.storemanager.CustomStoreManagerBuilder; +import com.tangosol.coherence.config.builder.storemanager.NioFileManagerBuilder; + +import com.tangosol.coherence.config.scheme.CacheStoreScheme; + +import com.tangosol.coherence.config.scheme.ClassScheme; +import com.tangosol.coherence.config.scheme.ContinuousQueryCacheScheme; +import com.tangosol.coherence.config.scheme.DistributedScheme; +import com.tangosol.coherence.config.scheme.ExternalScheme; +import com.tangosol.coherence.config.scheme.FlashJournalScheme; +import com.tangosol.coherence.config.scheme.InvocationScheme; +import com.tangosol.coherence.config.scheme.LocalScheme; +import com.tangosol.coherence.config.scheme.NamedTopicScheme; +import com.tangosol.coherence.config.scheme.NearScheme; +import com.tangosol.coherence.config.scheme.OptimisticScheme; +import com.tangosol.coherence.config.scheme.OverflowScheme; +import com.tangosol.coherence.config.scheme.PagedExternalScheme; +import com.tangosol.coherence.config.scheme.ProxyScheme; +import com.tangosol.coherence.config.scheme.RamJournalScheme; +import com.tangosol.coherence.config.scheme.ReadWriteBackingMapScheme; +import com.tangosol.coherence.config.scheme.RemoteCacheScheme; +import com.tangosol.coherence.config.scheme.RemoteInvocationScheme; +import com.tangosol.coherence.config.scheme.ReplicatedScheme; +import com.tangosol.coherence.config.scheme.TransactionalScheme; +import com.tangosol.coherence.config.scheme.ViewScheme; + +import com.tangosol.coherence.config.xml.preprocessor.DefaultsCreationPreprocessor; +import com.tangosol.coherence.config.xml.preprocessor.ExtendPreprocessor; +import com.tangosol.coherence.config.xml.preprocessor.OperationalDefaultsPreprocessor; +import com.tangosol.coherence.config.xml.preprocessor.PofSerializerPreprocessor; +import com.tangosol.coherence.config.xml.preprocessor.SchemeRefPreprocessor; +import com.tangosol.coherence.config.xml.preprocessor.SystemPropertyPreprocessor; +import com.tangosol.coherence.config.xml.preprocessor.TCPAcceptorPreprocessor; + +import com.tangosol.coherence.config.xml.processor.AcceptorDependenciesProcessor; +import com.tangosol.coherence.config.xml.processor.AddressProviderBuilderProcessor; +import com.tangosol.coherence.config.xml.processor.AuthorizedHostsProcessor; +import com.tangosol.coherence.config.xml.processor.BackingMapSchemeProcessor; +import com.tangosol.coherence.config.xml.processor.BufferTypeProcessor; +import com.tangosol.coherence.config.xml.processor.CacheMappingProcessor; +import com.tangosol.coherence.config.xml.processor.CacheServiceProxyProcessor; +import com.tangosol.coherence.config.xml.processor.CachingSchemeMappingProcessor; +import com.tangosol.coherence.config.xml.processor.CachingSchemesProcessor; +import com.tangosol.coherence.config.xml.processor.CompositeSchemeProcessor; +import com.tangosol.coherence.config.xml.processor.ConfigurationProcessor; +import com.tangosol.coherence.config.xml.processor.CustomizableBinaryStoreManagerBuilderProcessor; +import com.tangosol.coherence.config.xml.processor.CustomizableBuilderProcessor; +import com.tangosol.coherence.config.xml.processor.DefaultsProcessor; +import com.tangosol.coherence.config.xml.processor.DeltaCompressorProcessor; +import com.tangosol.coherence.config.xml.processor.EvictionPolicyProcessor; +import com.tangosol.coherence.config.xml.processor.ExecutorProcessor; +import com.tangosol.coherence.config.xml.processor.HttpAcceptorDependenciesProcessor; +import com.tangosol.coherence.config.xml.processor.InitParamProcessor; +import com.tangosol.coherence.config.xml.processor.InitParamsProcessor; +import com.tangosol.coherence.config.xml.processor.InitiatorDependenciesProcessor; +import com.tangosol.coherence.config.xml.processor.InstanceProcessor; +import com.tangosol.coherence.config.xml.processor.InterceptorProcessor; +import com.tangosol.coherence.config.xml.processor.InterceptorsProcessor; +import com.tangosol.coherence.config.xml.processor.InternalCacheSchemeProcessor; +import com.tangosol.coherence.config.xml.processor.InvocationServiceProxyProcessor; +import com.tangosol.coherence.config.xml.processor.KeystoreProcessor; +import com.tangosol.coherence.config.xml.processor.LeaseGranularityProcessor; +import com.tangosol.coherence.config.xml.processor.LocalAddressProcessor; +import com.tangosol.coherence.config.xml.processor.MapListenerProcessor; +import com.tangosol.coherence.config.xml.processor.MemberListenerProcessor; +import com.tangosol.coherence.config.xml.processor.MemorySizeProcessor; +import com.tangosol.coherence.config.xml.processor.MessageDeliveryModeProcessor; +import com.tangosol.coherence.config.xml.processor.MillisProcessor; +import com.tangosol.coherence.config.xml.processor.OperationBundlingProcessor; +import com.tangosol.coherence.config.xml.processor.PagedTopicSchemeProcessor; +import com.tangosol.coherence.config.xml.processor.ParamTypeProcessor; +import com.tangosol.coherence.config.xml.processor.PartitionAssignmentStrategyProcessor; +import com.tangosol.coherence.config.xml.processor.PartitionListenerProcessor; +import com.tangosol.coherence.config.xml.processor.PartitionedQuorumPolicyProcessor; +import com.tangosol.coherence.config.xml.processor.PersistenceProcessor; +import com.tangosol.coherence.config.xml.processor.ProviderProcessor; +import com.tangosol.coherence.config.xml.processor.ProxyQuorumPolicyProcessor; +import com.tangosol.coherence.config.xml.processor.SSLHostnameVerifierProcessor; +import com.tangosol.coherence.config.xml.processor.SSLManagerProcessor; +import com.tangosol.coherence.config.xml.processor.SSLNameListProcessor; +import com.tangosol.coherence.config.xml.processor.SSLProcessor; +import com.tangosol.coherence.config.xml.processor.SchemesProcessor; +import com.tangosol.coherence.config.xml.processor.ScopeNameProcessor; +import com.tangosol.coherence.config.xml.processor.SerializerFactoryProcessor; +import com.tangosol.coherence.config.xml.processor.ServiceBuilderProcessor; +import com.tangosol.coherence.config.xml.processor.ServiceFailurePolicyProcessor; +import com.tangosol.coherence.config.xml.processor.ServiceLoadBalancerProcessor; +import com.tangosol.coherence.config.xml.processor.SocketOptionsProcessor; +import com.tangosol.coherence.config.xml.processor.SocketProviderProcessor; +import com.tangosol.coherence.config.xml.processor.SpecificInstanceProcessor; +import com.tangosol.coherence.config.xml.processor.StorageProcessor; +import com.tangosol.coherence.config.xml.processor.SubscriberGroupProcessor; +import com.tangosol.coherence.config.xml.processor.SubscriberGroupsProcessor; +import com.tangosol.coherence.config.xml.processor.TcpAcceptorProcessor; +import com.tangosol.coherence.config.xml.processor.TcpInitiatorProcessor; +import com.tangosol.coherence.config.xml.processor.TopicMappingProcessor; +import com.tangosol.coherence.config.xml.processor.TopicSchemeMappingProcessor; +import com.tangosol.coherence.config.xml.processor.TransformerProcessor; +import com.tangosol.coherence.config.xml.processor.UnitCalculatorProcessor; +import com.tangosol.coherence.config.xml.processor.UnsupportedFeatureProcessor; +import com.tangosol.coherence.config.xml.processor.ValueStorageSchemeProcessor; +import com.tangosol.coherence.config.xml.processor.WrapperStreamFactoryListProcessor; + +import com.tangosol.config.xml.AbstractNamespaceHandler; +import com.tangosol.config.xml.DocumentElementPreprocessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.internal.net.service.peer.acceptor.DefaultJmsAcceptorDependencies; +import com.tangosol.internal.net.service.peer.acceptor.DefaultMemcachedAcceptorDependencies; +import com.tangosol.internal.net.service.peer.acceptor.DefaultTcpAcceptorDependencies; + +import com.tangosol.internal.net.service.peer.initiator.DefaultJmsInitiatorDependencies; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.CacheService; +import com.tangosol.net.InvocationService; +import com.tangosol.net.OperationalContext; +import com.tangosol.net.SocketOptions; + +import com.tangosol.net.messaging.Codec; + +import com.tangosol.net.partition.KeyAssociator; +import com.tangosol.net.partition.KeyPartitioningStrategy; + +import com.tangosol.run.xml.XmlElement; + +import static com.tangosol.coherence.config.xml.processor.AbstractEmptyElementProcessor.EmptyElementBehavior; + +import java.net.URI; + +/** + * The {@link CacheConfigNamespaceHandler} is responsible for capturing and creating Coherence + * Cache Configurations when processing a Coherence Configuration file. + * + * @author bo 2011.05.26 + * @since Coherence 12.1.2 + */ +public class CacheConfigNamespaceHandler + extends AbstractNamespaceHandler + { + // ----- constructors --------------------------------------------------- + + /** + * Standard Constructor. + */ + public CacheConfigNamespaceHandler() + { + // define the DocumentElementPreprocessor for the Cache Config Namespace + DocumentElementPreprocessor dep = new DocumentElementPreprocessor(); + + // add the DefaultsCreationPreprocessor to automatically create + // appropriate elements (like for pof) + dep.addElementPreprocessor(new DefaultsCreationPreprocessor()); + + // add the pre-processor for scheme-ref + dep.addElementPreprocessor(SchemeRefPreprocessor.INSTANCE); + + // add the pre-processor for Coherence*Extend + dep.addElementPreprocessor(ExtendPreprocessor.INSTANCE); + + // add the pre-processor to merge operational service configuration + OperationalDefaultsPreprocessor odp = new OperationalDefaultsPreprocessor(); + + odp.addDefaultsDefinition("distributed-scheme", CacheFactory.getServiceConfig(CacheService.TYPE_DISTRIBUTED)); + odp.addDefaultsDefinition("invocation-scheme", CacheFactory.getServiceConfig(InvocationService.TYPE_DEFAULT)); + odp.addDefaultsDefinition("local-scheme", CacheFactory.getServiceConfig(CacheService.TYPE_LOCAL)); + odp.addDefaultsDefinition("optimistic-scheme", CacheFactory.getServiceConfig(CacheService.TYPE_OPTIMISTIC)); + odp.addDefaultsDefinition("proxy-scheme", CacheFactory.getServiceConfig("Proxy")); + odp.addDefaultsDefinition("replicated-scheme", CacheFactory.getServiceConfig(CacheService.TYPE_REPLICATED)); + odp.addDefaultsDefinition("remote-cache-scheme", CacheFactory.getServiceConfig(CacheService.TYPE_REMOTE)); + odp.addDefaultsDefinition("remote-invocation-scheme", + CacheFactory.getServiceConfig(InvocationService.TYPE_REMOTE)); + + odp.addDefaultsDefinition("paged-topic-scheme", CacheFactory.getServiceConfig(CacheService.TYPE_DISTRIBUTED)); + + dep.addElementPreprocessor(odp); + + // add the pre-processor for POF serializer + dep.addElementPreprocessor(new PofSerializerPreprocessor()); + + // add the system property pre-processor + dep.addElementPreprocessor(SystemPropertyPreprocessor.INSTANCE); + + // add the acceptor-config pre-processor + dep.addElementPreprocessor(new TCPAcceptorPreprocessor()); + + setDocumentPreprocessor(dep); + + // register the type-based ElementProcessors (in alphabetical order) + registerProcessor(AcceptorDependenciesProcessor.class); + registerProcessor(AuthorizedHostsProcessor.class); + registerProcessor(BackingMapSchemeProcessor.class); + registerProcessor(BufferTypeProcessor.class); + registerProcessor(CacheServiceProxyProcessor.class); + registerProcessor(CacheMappingProcessor.class); + registerProcessor(CachingSchemeMappingProcessor.class); + registerProcessor(CachingSchemesProcessor.class); + registerProcessor(ConfigurationProcessor.class); + registerProcessor(DefaultsProcessor.class); + registerProcessor(DeltaCompressorProcessor.class); + registerProcessor(ExecutorProcessor.class); + registerProcessor(EvictionPolicyProcessor.class); + registerProcessor(HttpAcceptorDependenciesProcessor.class); + registerProcessor(InitiatorDependenciesProcessor.class); + registerProcessor(InitParamProcessor.class); + registerProcessor(InitParamsProcessor.class); + registerProcessor(InstanceProcessor.class); + registerProcessor(InterceptorProcessor.class); + registerProcessor(InterceptorsProcessor.class); + registerProcessor(InternalCacheSchemeProcessor.class); + registerProcessor(InvocationServiceProxyProcessor.class); + registerProcessor(KeystoreProcessor.class); + registerProcessor(LeaseGranularityProcessor.class); + registerProcessor(LocalAddressProcessor.class); + registerProcessor(MapListenerProcessor.class); + registerProcessor(MemberListenerProcessor.class); + registerProcessor(MessageDeliveryModeProcessor.class); + registerProcessor(OperationBundlingProcessor.class); + registerProcessor(PagedTopicSchemeProcessor.class); + registerProcessor(ParamTypeProcessor.class); + registerProcessor(PartitionAssignmentStrategyProcessor.class); + registerProcessor(PartitionedQuorumPolicyProcessor.class); + registerProcessor(PartitionListenerProcessor.class); + registerProcessor(PersistenceProcessor.class); + registerProcessor(ProviderProcessor.class); + registerProcessor(ProxyQuorumPolicyProcessor.class); + registerProcessor(SchemesProcessor.class); + registerProcessor(ScopeNameProcessor.class); + registerProcessor(SerializerFactoryProcessor.class); + registerProcessor(ServiceFailurePolicyProcessor.class); + registerProcessor(ServiceLoadBalancerProcessor.class); + registerProcessor(SocketProviderProcessor.class); + registerProcessor(SSLProcessor.class); + registerProcessor(SSLHostnameVerifierProcessor.class); + registerProcessor(StorageProcessor.class); + registerProcessor(SubscriberGroupProcessor.class); + registerProcessor(SubscriberGroupsProcessor.class); + registerProcessor(TcpAcceptorProcessor.class); + registerProcessor(TcpInitiatorProcessor.class); + registerProcessor(TopicSchemeMappingProcessor.class); + registerProcessor(TransformerProcessor.class); + registerProcessor(UnitCalculatorProcessor.class); + registerProcessor(ValueStorageSchemeProcessor.class); + registerProcessor(WrapperStreamFactoryListProcessor.class); + + // register customized ElementProcessors (in alphabetical order) + registerProcessor("address-provider", new AddressProviderBuilderProcessor()); + registerProcessor("async-store-manager", + new CustomizableBinaryStoreManagerBuilderProcessor<>(AsyncStoreManagerBuilder.class)); + registerProcessor("backup-storage", + new CustomizableBuilderProcessor<>(DistributedScheme.BackupConfig.class)); + registerProcessor("bdb-store-manager", + new CustomizableBuilderProcessor<>(BdbStoreManagerBuilder.class)); + registerProcessor("buffer-size", new MemorySizeProcessor()); + registerProcessor("cachestore-scheme", + new CustomizableBuilderProcessor<>(CacheStoreScheme.class)); + registerProcessor("class-scheme", new CustomizableBuilderProcessor<>(ClassScheme.class)); + registerProcessor("continuous-query-cache-scheme", + new ServiceBuilderProcessor<>(ContinuousQueryCacheScheme.class)); + registerProcessor("custom-store-manager", + new CustomizableBuilderProcessor<>(CustomStoreManagerBuilder.class)); + registerProcessor("connect-timeout", new MillisProcessor()); + registerProcessor("cipher-suites", new SSLNameListProcessor("cipher-suites")); + registerProcessor("distributed-scheme", + new ServiceBuilderProcessor<>(DistributedScheme.class)); + registerProcessor("ensure-cache-timeout", new MillisProcessor()); + registerProcessor("external-scheme", + new CustomizableBinaryStoreManagerBuilderProcessor<>(ExternalScheme.class)); + registerProcessor("federated-scheme", new UnsupportedFeatureProcessor("Federated Caching")); + registerProcessor("flashjournal-scheme", + new CustomizableBuilderProcessor<>(FlashJournalScheme.class)); + registerProcessor("guardian-timeout", new MillisProcessor()); + registerProcessor("heartbeat-interval", new MillisProcessor()); + registerProcessor("heartbeat-timeout", new MillisProcessor()); + registerProcessor("identity-manager", new SSLManagerProcessor()); + + registerProcessor("key-associator", + new SpecificInstanceProcessor<>(KeyAssociator.class, + EmptyElementBehavior.IGNORE)); + registerProcessor("key-partitioning", + new SpecificInstanceProcessor<>(KeyPartitioningStrategy.class, + EmptyElementBehavior.IGNORE)); + registerProcessor("invocation-scheme", new ServiceBuilderProcessor<>(InvocationScheme.class)); + registerProcessor("limit-buffer-size", new MemorySizeProcessor()); + registerProcessor("local-scheme", new CustomizableBuilderProcessor<>(LocalScheme.class)); + + registerProcessor("max-message-size", new MemorySizeProcessor()); + registerProcessor("message-codec", new SpecificInstanceProcessor<>(Codec.class)); + registerProcessor("message-expiration", new MillisProcessor()); + registerProcessor("name-service-addresses", new AddressProviderBuilderProcessor()); + registerProcessor("near-scheme", new CompositeSchemeProcessor<>(NearScheme.class)); + registerProcessor("nio-file-manager", + new CustomizableBuilderProcessor<>(NioFileManagerBuilder.class)); + registerProcessor("nominal-buffer-size", new MemorySizeProcessor()); + registerProcessor("optimistic-scheme", new ServiceBuilderProcessor<>(OptimisticScheme.class)); + registerProcessor("overflow-scheme", new CompositeSchemeProcessor<>(OverflowScheme.class)); + registerProcessor("paged-external-scheme", + new CustomizableBinaryStoreManagerBuilderProcessor<>(PagedExternalScheme.class)); + registerProcessor("protocol-versions", new SSLNameListProcessor("protocol-versions")); + registerProcessor("proxy-scheme", + new ServiceBuilderProcessor<>(ProxyScheme.class)); + registerProcessor("ramjournal-scheme", + new CustomizableBuilderProcessor<>(RamJournalScheme.class)); + registerProcessor("read-write-backing-map-scheme", + new CustomizableBuilderProcessor<>(ReadWriteBackingMapScheme.class)); + registerProcessor("remote-addresses", new AddressProviderBuilderProcessor()); + + registerProcessor("remote-cache-scheme", + new ServiceBuilderProcessor<>(RemoteCacheScheme.class)); + registerProcessor("remote-invocation-scheme", + new ServiceBuilderProcessor<>(RemoteInvocationScheme.class)); + registerProcessor("replicated-scheme", new ServiceBuilderProcessor<>(ReplicatedScheme.class)); + registerProcessor("request-timeout", new MillisProcessor()); + registerProcessor("standard-lease-milliseconds", new MillisProcessor()); + registerProcessor("suspect-buffer-size", new MemorySizeProcessor()); + registerProcessor("task-hung-threshold", new MillisProcessor()); + registerProcessor("task-timeout", new MillisProcessor()); + registerProcessor("cache", new CacheMappingProcessor()); + registerProcessor("topic-mapping", new TopicMappingProcessor("topic-name", NamedTopicScheme.class)); + registerProcessor("topic-scheme", new PagedTopicSchemeProcessor()); + registerProcessor("transactional-scheme", + new ServiceBuilderProcessor<>(TransactionalScheme.class)); + registerProcessor("transfer-threshold", new MemorySizeProcessor()); + registerProcessor("trust-manager", new SSLManagerProcessor()); + registerProcessor("view-scheme", new CompositeSchemeProcessor<>(ViewScheme.class)); + + // register injectable types (in alphabetical order) + registerElementType("jms-acceptor", DefaultJmsAcceptorDependencies.class); + registerElementType("jms-initiator", DefaultJmsInitiatorDependencies.class); + registerElementType("memcached-acceptor", DefaultMemcachedAcceptorDependencies.class); + + registerElementType("incoming-buffer-pool", DefaultTcpAcceptorDependencies.PoolConfig.class); + registerElementType("outgoing-buffer-pool", DefaultTcpAcceptorDependencies.PoolConfig.class); + } + + // ----- NamespaceHandler interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void onStartNamespace(ProcessingContext context, XmlElement element, String prefix, URI uri) + { + super.onStartNamespace(context, element, prefix, uri); + + // register the element processors for the namespace based on types + // (in alphabetical order) + context.registerProcessor(SocketOptions.class, new SocketOptionsProcessor()); + + // ensure we have a CacheConfig available in the context of this namespace + if (context.getCookie(CacheConfig.class) == null) + { + CacheConfig cacheConfig = new CacheConfig(context.getDefaultParameterResolver()); + + cacheConfig.addCacheMappingRegistry(new SchemeMappingRegistry()); + context.addCookie(CacheConfig.class, cacheConfig); + } + + // ensure we have an OperationalContext in the context of this namespace + if (context.getCookie(OperationalContext.class) == null) + { + context.addCookie(OperationalContext.class, (OperationalContext) CacheFactory.getCluster()); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/OperationalConfigNamespaceHandler.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/OperationalConfigNamespaceHandler.java new file mode 100644 index 0000000000000..970afb17e0dc6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/OperationalConfigNamespaceHandler.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml; + +import com.tangosol.coherence.config.xml.preprocessor.SystemPropertyPreprocessor; +import com.tangosol.coherence.config.xml.processor.AddressProviderBuilderProcessor; +import com.tangosol.coherence.config.xml.processor.ExecutorProcessor; +import com.tangosol.coherence.config.xml.processor.InitParamProcessor; +import com.tangosol.coherence.config.xml.processor.InitParamsProcessor; +import com.tangosol.coherence.config.xml.processor.InstanceProcessor; +import com.tangosol.coherence.config.xml.processor.InterceptorProcessor; +import com.tangosol.coherence.config.xml.processor.InterceptorsProcessor; +import com.tangosol.coherence.config.xml.processor.KeystoreProcessor; +import com.tangosol.coherence.config.xml.processor.ParamTypeProcessor; +import com.tangosol.coherence.config.xml.processor.PasswordProviderBuilderProcessor; +import com.tangosol.coherence.config.xml.processor.PasswordProvidersProcessor; +import com.tangosol.coherence.config.xml.processor.PersistenceEnvironmentsProcessor; +import com.tangosol.coherence.config.xml.processor.ProviderProcessor; +import com.tangosol.coherence.config.xml.processor.SSLHostnameVerifierProcessor; +import com.tangosol.coherence.config.xml.processor.SSLManagerProcessor; +import com.tangosol.coherence.config.xml.processor.SSLNameListProcessor; +import com.tangosol.coherence.config.xml.processor.SSLProcessor; +import com.tangosol.coherence.config.xml.processor.SerializerBuilderProcessor; +import com.tangosol.coherence.config.xml.processor.SerializersProcessor; +import com.tangosol.coherence.config.xml.processor.SocketProviderProcessor; +import com.tangosol.coherence.config.xml.processor.SocketProvidersProcessor; +import com.tangosol.coherence.config.xml.processor.StorageAccessAuthorizerBuilderProcessor; +import com.tangosol.coherence.config.xml.processor.StorageAccessAuthorizersProcessor; +import com.tangosol.coherence.config.xml.processor.UnsupportedFeatureProcessor; + +import com.tangosol.config.xml.AbstractNamespaceHandler; +import com.tangosol.config.xml.DocumentElementPreprocessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.XmlElement; + +import java.net.URI; + +/** + * The {@link OperationalConfigNamespaceHandler} is responsible for capturing and + * creating the Coherence operational configuration when processing a Coherence + * operational configuration file. + * + * @author pfm 2013.03.21 + * @since Coherence 12.2.1 + */ +public class OperationalConfigNamespaceHandler + extends AbstractNamespaceHandler + { + // ----- constructors --------------------------------------------------- + + /** + * Standard Constructor. + */ + public OperationalConfigNamespaceHandler() + { + // define the DocumentPreprocessor for the OperationalConfig namespace + DocumentElementPreprocessor dep = new DocumentElementPreprocessor(); + + // add the system property pre-processor + dep.addElementPreprocessor(SystemPropertyPreprocessor.INSTANCE); + + setDocumentPreprocessor(dep); + + // register the type-based ElementProcessors + registerProcessor(AddressProviderBuilderProcessor.class); + registerProcessor(ExecutorProcessor.class); + registerProcessor(InitParamProcessor.class); + registerProcessor(InitParamsProcessor.class); + registerProcessor(InstanceProcessor.class); + registerProcessor(InterceptorProcessor.class); + registerProcessor(InterceptorsProcessor.class); + registerProcessor(KeystoreProcessor.class); + registerProcessor(ParamTypeProcessor.class); + registerProcessor(PasswordProviderBuilderProcessor.class); + registerProcessor(PasswordProvidersProcessor.class); + registerProcessor(PersistenceEnvironmentsProcessor.class); + registerProcessor(PersistenceEnvironmentsProcessor.PersistenceEnvironmentProcessor.class); + registerProcessor(ProviderProcessor.class); + registerProcessor(SerializerBuilderProcessor.class); + registerProcessor(SerializersProcessor.class); + registerProcessor(SocketProviderProcessor.class); + registerProcessor(SSLProcessor.class); + registerProcessor(SSLHostnameVerifierProcessor.class); + registerProcessor(StorageAccessAuthorizerBuilderProcessor.class); + registerProcessor(StorageAccessAuthorizersProcessor.class); + registerProcessor(SocketProvidersProcessor.class); + + // register customized ElementProcessors + registerProcessor("address-provider", new AddressProviderBuilderProcessor()); + registerProcessor("cipher-suites", new SSLNameListProcessor("cipher-suites")); + registerProcessor("identity-manager", new SSLManagerProcessor()); + registerProcessor("name-service-addresses", new AddressProviderBuilderProcessor()); + registerProcessor("protocol-versions", new SSLNameListProcessor("protocol-versions")); + registerProcessor("remote-addresses", new AddressProviderBuilderProcessor()); + registerProcessor("socket-provider", new SocketProviderProcessor()); + registerProcessor("trust-manager", new SSLManagerProcessor()); + + // register injectable types (in alphabetical order) + registerProcessor("federation-config", new UnsupportedFeatureProcessor("Federated Caching")); + } + + // ----- NamespaceHandler interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void onStartNamespace(ProcessingContext context, XmlElement element, String prefix, URI uri) + { + super.onStartNamespace(context, element, prefix, uri); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/package.html new file mode 100644 index 0000000000000..9313f3ea79d4d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/package.html @@ -0,0 +1,6 @@ + +Defines the Xml Document Pre-Processors and Xml Element Processors for converting +Coherence Cache Configuration files into Coherence configuration object models. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/CacheDefaultsPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/CacheDefaultsPreprocessor.java new file mode 100644 index 0000000000000..7640255cf7968 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/CacheDefaultsPreprocessor.java @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.DocumentElementPreprocessor.ElementPreprocessor; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Base; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * A {@link CacheDefaultsPreprocessor} is an {@link ElementPreprocessor} that introduces (via cloning) default xml + * content for xml elements where the said content is missing. + *

+ * Ultimately this {@link ElementPreprocessor} is designed to perform pre-processing of Coherence Cache <defaults> + * declarations, inserting them into the appropriate places in cache-config.xml documents. + * + * @see OperationalDefaultsPreprocessor + * + * @author bo 2011.12.16 + * @since Coherence 12.1.2 + */ +public class CacheDefaultsPreprocessor + implements ElementPreprocessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link CacheDefaultsPreprocessor} with a specific path to where default element content can be located. + *

+ * Example: new DefaultPreprocessor("/defaults"); + * + * @param sDefaultsParentPath the absolute path to the {@link XmlElement} that contains (is the parent of) + * the default elements + */ + public CacheDefaultsPreprocessor(String sDefaultsParentPath) + { + Base.azzert(sDefaultsParentPath != null && sDefaultsParentPath.startsWith("/")); + + // ensure the path to the defaults element does not end with a / + m_sDefaultsParentPath = sDefaultsParentPath.trim(); + + if (m_sDefaultsParentPath.endsWith("/") && m_sDefaultsParentPath.length() > 1) + { + m_sDefaultsParentPath = m_sDefaultsParentPath.substring(0, m_sDefaultsParentPath.length() - 1); + } + + m_listDefaultDefinitions = new ArrayList(); + } + + // ----- ElementPreprocessor methods ------------------------------------ + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public boolean preprocess(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // is this element with in the default element? + // (this ensures that we don't attempt to perform default substitutions with in default element) + String sElementPath = getPathToRoot(element); + + if (!sElementPath.startsWith(m_sDefaultsParentPath)) + { + List listElementChildren = (List) element.getElementList(); + + // add missing defaults to the current element (where required) + for (DefaultDefinition defaultDefn : m_listDefaultDefinitions) + { + // does the current element match the path specified by the default definition? + if (defaultDefn.matches(element)) + { + // when the required element doesn't exist, clone a default one into place + if (element.getElement(defaultDefn.getRequiredElementName()) == null) + { + // locate the default element to clone + XmlElement defaultElement = element.findElement(m_sDefaultsParentPath + "/" + + defaultDefn.getRequiredElementName()); + + if (defaultElement == null) + { + // could not find the default, so nothing should happen + } + else if (listElementChildren.isEmpty() && !element.getString().isEmpty()) + { + // we can't add the required element to an element with simple content (but no children) + } + else + { + // clone and add the default into the current element + XmlElement requiredElement = (XmlElement) defaultElement.clone(); + + listElementChildren.add(requiredElement); + } + } + } + } + + } + + return false; + } + + // ----- CacheDefaultsPreprocessor methods ------------------------------ + + /** + * Adds a requirement that the specified "default" element must be added to (if not already defined) in the element + * specified by the defined parent path. + *

+ * Paths used by this method are based those defined by the {@link XmlHelper#findElement(XmlElement, String)} + * method, with the exception that ".." is not supported. + *

+ * For example 1: The following call specifies that the "serializer" should be cloned from the defined default + * path if it doesn't exist in the "/caching-schemes/distributed-scheme" element. + *

+ * addDefaultDefinition("/caching-schemes/distributed-scheme", "serializer"); + *

+ * For example 2: The following call specifies that the "serializer" should be cloned from the defined default + * path if it doesn't exist in any "distributed-scheme" elements + *

+ * addDefaultDefinition("distributed-scheme", "serializer"); + * + * @param sRequiredElementParentPath the path of the parent for the required element + * @param sRequiredElementName the name of the element that should be cloned (if it doesn't exist in the + * specified parent path) from the defined default element path + */ + public void addDefaultDefinition(String sRequiredElementParentPath, String sRequiredElementName) + { + m_listDefaultDefinitions.add(new DefaultDefinition(sRequiredElementParentPath, sRequiredElementName)); + } + + /** + * Obtains the path to the root element given the specified element, but does not include the root element + * name so that the value returned can be used by {@link XmlHelper#findElement(XmlElement, String)}. + * + * @param element the element from which to produce the path + * @return the path to the root element (excluding the root element name) + */ + private String getPathToRoot(XmlElement element) + { + if (element == null) + { + return null; + } + else + { + StringBuilder builder = new StringBuilder(); + + while (element.getParent() != null) + { + builder.insert(0, "/" + element.getName()); + element = element.getParent(); + } + + return builder.length() == 0 ? "/" : builder.toString(); + } + } + + // ----- DefaultMapping class ------------------------------------------- + + /** + * A {@link DefaultDefinition} captures the definition of a required default element. + */ + private static class DefaultDefinition + { + // ----- constructors ----------------------------------------------- + + /** + * Constructs a {@link DefaultDefinition}. + * + * @param sRequiredElementParentPath the path of the parent for the required element + * @param sRequiredElementName the name of the element that should be cloned (if it doesn't exist in the + * specified parent path) from the default element path + */ + public DefaultDefinition(String sRequiredElementParentPath, String sRequiredElementName) + { + m_listRequiredElementParentNames = new ArrayList(10); + sRequiredElementParentPath = sRequiredElementParentPath.trim(); + + // for absolute paths, we add a "/" as the absolute parent + if (sRequiredElementParentPath.startsWith("/")) + { + m_listRequiredElementParentNames.add("/"); + } + + // tokenize the parent element path into parent names + StringTokenizer tokenizer = new StringTokenizer(sRequiredElementParentPath, "/"); + + for (; tokenizer.hasMoreTokens(); ) + { + m_listRequiredElementParentNames.add(tokenizer.nextToken()); + } + + m_sRequiredElementName = sRequiredElementName.trim(); + } + + // ----- DefaultMapping methods ------------------------------------- + + /** + * Obtains the required element name. + * + * @return the required element name + */ + public String getRequiredElementName() + { + return m_sRequiredElementName; + } + + /** + * Determines if the specified {@link XmlElement} matches the path specified by the {@link DefaultDefinition}. + * + * @param element the {@link XmlElement} for comparison. + * + * @return if the {@link XmlElement} matches the path + */ + public boolean matches(XmlElement element) + { + // ensure that the specified element has the same path as that defined by the mapping + // (assume it does to start) + boolean fPathMatches = true; + + for (int i = m_listRequiredElementParentNames.size() - 1; i >= 0 && fPathMatches; i--) + { + String sParentName = m_listRequiredElementParentNames.get(i); + + if (element != null && sParentName.equals("/")) + { + fPathMatches = element.getParent() == null; + } + else if (element != null && element.getName().equals(sParentName)) + { + element = element.getParent(); + } + else + { + fPathMatches = false; + } + } + + return fPathMatches; + } + + // ----- data members --------------------------------------------------- + + /** + * The parent element names of the required element. + */ + private ArrayList m_listRequiredElementParentNames; + + /** + * The name of the potentially required, but potentially missing element + * (this will be appended to the parent path). + */ + private String m_sRequiredElementName; + } + + // ----- data members --------------------------------------------------- + + /** + * The path to the parent element of all default elements. + */ + private String m_sDefaultsParentPath; + + /** + * The {@link DefaultDefinition}s defined for the {@link CacheDefaultsPreprocessor}. + */ + private ArrayList m_listDefaultDefinitions; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/DefaultsCreationPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/DefaultsCreationPreprocessor.java new file mode 100644 index 0000000000000..4234a93e06833 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/DefaultsCreationPreprocessor.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.coherence.config.CacheConfig; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.xml.DocumentElementPreprocessor.ElementPreprocessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.SimpleElement; +import com.tangosol.run.xml.XmlElement; + + +/** + * A {@link DefaultsCreationPreprocessor} is an {@link ElementPreprocessor} that + * creates necessary defaults, like for {@literal }s, if they are missing + * from the {@literal } element. + * + * @author bo 2014.03.21 + * @since Coherence 12.2.1 + */ +public class DefaultsCreationPreprocessor + implements ElementPreprocessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link DefaultsCreationPreprocessor}. + */ + public DefaultsCreationPreprocessor() + { + } + + // ----- ElementPreprocessor methods ------------------------------------ + + @Override + public boolean preprocess(ProcessingContext context, XmlElement xmlConfig) + throws ConfigurationException + { + // assume we didn't update the defaults + boolean fUpdatedDefaults = false; + + // we only process the root element + String sName = xmlConfig.getName(); + if (sName.equals(CacheConfig.TOP_LEVEL_ELEMENT_NAME)) + { + XmlElement xmlDefaults = xmlConfig.getElement("defaults"); + + // ----- create the pof when required ----- + ParameterResolver parameterResolver = context.getDefaultParameterResolver(); + Parameter paramPofConfigUri = parameterResolver.resolve("pof-config-uri"); + + String sPofConfigUri = paramPofConfigUri == null ? null : + paramPofConfigUri.evaluate(parameterResolver).as(String.class); + + if (sPofConfigUri != null && !sPofConfigUri.isEmpty()) + { + // When a POF Config URI is specified we must ensure that the + // pof is defined for pof + // (if not defined for something else) + if (xmlDefaults == null) + { + xmlDefaults = addDefaults(xmlConfig); + fUpdatedDefaults = true; + } + + XmlElement xmlSerializer = xmlDefaults.getElement("serializer"); + if (xmlSerializer == null) + { + xmlSerializer = xmlDefaults.addElement("serializer"); + xmlSerializer.setString("pof"); + + fUpdatedDefaults = true; + } + } + + // ---- backward compatibility: check for the root "scope-name" element + // (deprecated in 12.1.2) + XmlElement xmlScope = xmlConfig.getElement("scope-name"); + if (xmlScope != null && !xmlScope.isEmpty()) + { + // move the "scope-name" element from the root into the "defaults" + if (xmlDefaults == null) + { + xmlDefaults = addDefaults(xmlConfig); + fUpdatedDefaults = true; + } + + xmlConfig.getElementList().remove(xmlScope); + + XmlElement xmlDefaultScope = xmlDefaults.getElement("scope-name"); + if (xmlDefaultScope == null) + { + // according to the scheme, the "scope-name" should be first + xmlDefaults.getElementList().add(0, xmlScope); + fUpdatedDefaults = true; + } + else if (xmlDefaults.isEmpty()) + { + xmlDefaults.setString(xmlScope.getString()); + fUpdatedDefaults = true; + } + } + } + + return fUpdatedDefaults; + } + + /** + * Add the top level "defaults" element. + * + * @param xmlConfig the top level "configuration" element + * + * @return newly created "defaults" element + */ + private XmlElement addDefaults(XmlElement xmlConfig) + { + assert xmlConfig.getElement("defaults") == null; + + // make sure defaults is created as the first child of configuration; + // otherwise the XML validation may fail + XmlElement xmlDefaults = new SimpleElement("defaults"); + xmlConfig.getElementList().add(0, xmlDefaults); + + return xmlDefaults; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/ExtendPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/ExtendPreprocessor.java new file mode 100644 index 0000000000000..a44e824f188ad --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/ExtendPreprocessor.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.DocumentElementPreprocessor.ElementPreprocessor; + + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +/** + * An {@link ExtendPreprocessor} is an {@link ElementPreprocessor} that will + * inject an "acceptor-config" {@link XmlElement} into a "proxy-scheme" + * {@link XmlElement} if one does not exist. + * + * @author lh 2013.07.09 + * @since Coherence 12.1.3 + */ +public class ExtendPreprocessor + implements ElementPreprocessor + { + // ----- ElementPreprocessor methods ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public boolean preprocess(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + XmlElement xmlProxyScheme = element.getElement("proxy-scheme"); + if (xmlProxyScheme == null) + { + return false; + } + + XmlHelper.ensureElement(xmlProxyScheme, "acceptor-config"); + return false; + } + + // ----- constants ------------------------------------------------------ + + /** + * This singleton instance of the {@link ExtendPreprocessor}. + */ + public static final ExtendPreprocessor INSTANCE = new ExtendPreprocessor(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/OperationalDefaultsPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/OperationalDefaultsPreprocessor.java new file mode 100644 index 0000000000000..9031fd9f812aa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/OperationalDefaultsPreprocessor.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.DocumentElementPreprocessor.ElementPreprocessor; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * A {@link OperationalDefaultsPreprocessor} is an {@link ElementPreprocessor} that introduces + * (via cloning) default xml content for xml elements where the said content is missing. + *

+ * Ultimately this {@link ElementPreprocessor} is designed to perform pre-processing of Coherence cache-config.xml + * files to inject appropriate Operational Config elements defined separately in tangosol-coherence.*.xml files. + * + * @author bo 2012.01.10 + * @since Coherence 12.1.2 + */ +public class OperationalDefaultsPreprocessor + implements ElementPreprocessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link OperationalDefaultsPreprocessor}. + */ + public OperationalDefaultsPreprocessor() + { + m_listDefaultsDefinitions = new ArrayList(); + } + + // ----- ElementPreprocessor methods ------------------------------------ + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public boolean preprocess(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // add missing defaults to the current element (where required) + for (DefaultsDefinition defaultDefn : m_listDefaultsDefinitions) + { + // does the default definition path match the current element (if so we may have to clone in defaults) + if (defaultDefn.matches(element)) + { + // ensure each of the children of the default are in the element and clone/add those that aren't + XmlElement defaults = defaultDefn.getDefaultsElement(); + + List listDefaultChildren = (List) defaults.getElementList(); + + for (XmlElement defaultChild : listDefaultChildren) + { + // when the required child is missing, clone and add one from the defaults + if (element.getElement(defaultChild.getName()) == null) + { + XmlElement child = (XmlElement) defaultChild.clone(); + + element.getElementList().add(child); + } + } + } + } + + return false; + } + + // ----- OperationalDefaultsPreprocessor methods ----------------------------------- + + /** + * Defines that elements matching the specified path must contain the child elements defined by the default element. + * If not the missing children must be cloned from the default element into the element matching the path during + * pre-processing. + *

+ * Paths used by this method are based those defined by the {@link XmlHelper#findElement(XmlElement, String)} + * method, with the exception that ".." is not supported. + *

+ * For example 1: The following specifies that the elements matching the absolute path + * "/caching-schemes/distributed-scheme" must contain the children defined by the xmlDistributedSchemeDefaults + * element. If not, they must be cloned into place. + *

+ * addDefaultsDefinition("/caching-schemes/distributed-scheme", xmlDistributedSchemeDefaults); + *

+ * For example 2: The following specifies that the elements matching the relative path + * "distributed-scheme" must contain the children defined by the xmlDistributedSchemeDefaults element. + * If not, they must be cloned into place. + *

+ * addDefaultsDefinition("distributed-scheme", xmlDistributedSchemeDefaults); + * + * @param sPath The path of elements requiring the defaults + * @param defaultsElement The xml element containing the required defaults for the specified path + */ + public void addDefaultsDefinition(String sPath, XmlElement defaultsElement) + { + m_listDefaultsDefinitions.add(new DefaultsDefinition(sPath, defaultsElement)); + } + + // ----- DefaultsDefinition class --------------------------------------- + + /** + * A {@link DefaultsDefinition} captures the definition of defaults for a specific path + */ + private static class DefaultsDefinition + { + // ----- constructors ----------------------------------------------- + + /** + * Constructs a {@link DefaultsDefinition}. + * + * @param sPath The path of elements requiring the defaults + * @param defaultsElement The xml element containing the required defaults for the specified path + */ + public DefaultsDefinition(String sPath, XmlElement defaultsElement) + { + m_listRequiredElementParentNames = new ArrayList(10); + sPath = sPath.trim(); + + // for absolute paths, we add a "/" as the absolute parent + if (sPath.startsWith("/")) + { + m_listRequiredElementParentNames.add("/"); + } + + // tokenize the parent element path into parent names + StringTokenizer tokenizer = new StringTokenizer(sPath, "/"); + + for (; tokenizer.hasMoreTokens(); ) + { + m_listRequiredElementParentNames.add(tokenizer.nextToken()); + } + + m_xmlDefaultsElement = defaultsElement; + } + + // ----- DefaultsDefinition methods --------------------------------- + + /** + * Obtains the {@link XmlElement} containing the required default (child) elements. + * + * @return the default {@link XmlElement} + */ + public XmlElement getDefaultsElement() + { + return m_xmlDefaultsElement; + } + + /** + * Determines if the specified {@link XmlElement} matches the path specified by the {@link DefaultsDefinition}. + * + * @param element the {@link XmlElement} for comparison. + * + * @return if the {@link XmlElement} matches the path + */ + public boolean matches(XmlElement element) + { + // ensure that the specified element has the same path as that defined by the mapping + // (assume it does to start) + boolean fPathMatches = true; + + for (int i = m_listRequiredElementParentNames.size() - 1; i >= 0 && fPathMatches; i--) + { + String sParentName = m_listRequiredElementParentNames.get(i); + + if (element != null && sParentName.equals("/")) + { + fPathMatches = element.getParent() == null; + } + else if (element != null && element.getName().equals(sParentName)) + { + element = element.getParent(); + } + else + { + fPathMatches = false; + } + } + + return fPathMatches; + } + + // ----- data members --------------------------------------------------- + + /** + * The parent element names of the required element. + */ + private ArrayList m_listRequiredElementParentNames; + + /** + * The parent element containing the defaults to be cloned into the above specified parent + */ + private XmlElement m_xmlDefaultsElement; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link DefaultsDefinition}s defined for the {@link OperationalDefaultsPreprocessor}. + */ + private ArrayList m_listDefaultsDefinitions; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/PofSerializerPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/PofSerializerPreprocessor.java new file mode 100644 index 0000000000000..0366de3ba6b50 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/PofSerializerPreprocessor.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.DocumentElementPreprocessor.ElementPreprocessor; + +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link PofSerializerPreprocessor} is an {@link ElementPreprocessor} that + * replaces any occurrence of {@literal pof} with a + * {@link com.tangosol.io.pof.ConfigurablePofContext} configuration, passing in + * the provided POF configuration URI in the initialization parameters. + * + * @author pfm 2012.01.20 + * @since Coherence 12.1.2 + */ +public class PofSerializerPreprocessor + implements ElementPreprocessor + { + // ----- ElementPreprocessor methods ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public boolean preprocess(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + if (element.getQualifiedName().getLocalName().equals("serializer")) + { + Object oVal = element.getValue(); + + if (oVal != null && oVal instanceof String && ((String) oVal).equals("pof")) + { + ParameterResolver parameterResolver = context.getDefaultParameterResolver(); + + Parameter paramPofConfigUri = parameterResolver.resolve("pof-config-uri"); + + if (paramPofConfigUri != null) + { + String sPofConfigUri = paramPofConfigUri.evaluate(parameterResolver).as(String.class); + + if (sPofConfigUri != null && !sPofConfigUri.isEmpty()) + { + element.setString(null); + + XmlElement xmlInstance = element.ensureElement("instance"); + + xmlInstance.ensureElement("class-name").setString("com.tangosol.io.pof.ConfigurablePofContext"); + xmlInstance.ensureElement("init-params/init-param/param-type").setString("String"); + xmlInstance.ensureElement("init-params/init-param/param-value").setString(sPofConfigUri); + + return true; + } + } + } + } + + return false; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/SchemeRefPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/SchemeRefPreprocessor.java new file mode 100644 index 0000000000000..bc340a24f8ff6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/SchemeRefPreprocessor.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.DocumentElementPreprocessor.ElementPreprocessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link SchemeRefPreprocessor} is an {@link ElementPreprocessor} that resolves declarations of + * <scheme-ref> as required by Coherence. + * + * @author bo 2011.08.03 + * @since Coherence 12.1.2 + */ +public class SchemeRefPreprocessor implements ElementPreprocessor + { + // ----- ElementPreprocessor methods ------------------------------------ + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public boolean preprocess(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + XmlElement xmlSchemeRef = element.getElement("scheme-ref"); + boolean fNullSchemeRef = false; + + if (xmlSchemeRef == null) + { + return false; + } + else + { + String sSchemeName = xmlSchemeRef.getString().trim(); + + if (sSchemeName.isEmpty()) + { + throw new ConfigurationException(String.format("The referenced scheme in %s is blank.", element), + "Please ensure that the referenced scheme name is declared in the configuration"); + } + else + { + // find the <*-scheme> with in the element that has the required scheme name + XmlElement xmlScheme = findCachingScheme(sSchemeName, element); + + if (xmlScheme == null) + { + String sXmlNullScheme = ""; + + xmlScheme = XmlHelper.loadXml(sXmlNullScheme); + + fNullSchemeRef = true; + } + + if (fNullSchemeRef || xmlScheme.getName().equals(element.getName())) + { + // ensure we don't have a cyclic reference (by ensuring we're not referencing ourself) + if (element == xmlScheme) + { + throw new ConfigurationException(String.format( + "Discovered a cyclic reference to scheme [%s] in %s.", xmlSchemeRef.getString(), + element), "Please ensure that the referenced scheme won't eventually reference itself."); + } + + // remove the from element + List listElements = (element.getElementList()); + + listElements.remove(xmlSchemeRef); + + // perform the merge by adding the children of the referenced scheme into this element + // iff they don't already exist in the element + boolean fChanged = false; + + for (XmlElement xmlSchemeChild : ((List) xmlScheme.getElementList())) + { + if (element.getElement(xmlSchemeChild.getName()) == null) + { + listElements.add(xmlSchemeChild); + fChanged = true; + } + } + + // if we changed the element we need to re-visit it (this allows for chained ) + return fChanged; + } + else + { + throw new ConfigurationException(String.format( + "The referenced scheme [%s] in %s is a different type of scheme.", xmlSchemeRef.getString(), + element), "Please ensure that the referenced scheme is the same type"); + } + } + } + } + + // ----- SchemeRefPreprocessor methods ---------------------------------- + + /** + * Obtains the {@link XmlElement} that contains a <scheme-name> definition for the specified sSchemeName with in + * the provided {@link XmlElement}, or null if not found. + * + * @param sSchemeName The scheme name to locate + * @param element The {@link XmlElement} to search + * + * @return the caching scheme xml element + */ + @SuppressWarnings("unchecked") + public XmlElement findCachingScheme(String sSchemeName, XmlElement element) + { + XmlElement xmlSchemes = element.getRoot().getElement("schemes"); + XmlElement xmlCachingSchemes = element.getRoot().getElement("caching-schemes"); + List elements = new ArrayList<>(); + + if (xmlSchemes != null) + { + elements.addAll(xmlSchemes.getElementList()); + } + + if (xmlCachingSchemes != null) + { + elements.addAll(xmlCachingSchemes.getElementList()); + } + + for (XmlElement xml : elements) + { + if (xml.getSafeElement("scheme-name").getString().trim().equals(sSchemeName)) + { + return xml; + } + } + + return null; + } + + // ----- constants ------------------------------------------------------ + + /** + * This singleton instance of the {@link SchemeRefPreprocessor}. + */ + public static final SchemeRefPreprocessor INSTANCE = new SchemeRefPreprocessor(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/SystemPropertyPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/SystemPropertyPreprocessor.java new file mode 100644 index 0000000000000..fc48a3011f9a2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/SystemPropertyPreprocessor.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.coherence.config.Config; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.SystemPropertyParameterResolver; +import com.tangosol.config.expression.ValueMacroExpression; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.DocumentElementPreprocessor.ElementPreprocessor; + + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlValue; +import com.tangosol.run.xml.XmlHelper; + +/** + * A {@link SystemPropertyPreprocessor} is an {@link ElementPreprocessor} that will + * replace {@link XmlElement} content annotated with "system-property" attributes with appropriate + * {@link System#getProperties()}. + *

+ * The element's value is processed for macro expansion. The macro syntax is ${system-property default-value}. + * Thus, a value of near-${coherence.client direct} can be macro expanded by default to near-direct. + * If system property coherence.client is set to remote, then the value would be expanded to near-remote. + * + * @author bo 2011.08.03 + * @since Coherence 12.1.2 + */ +public class SystemPropertyPreprocessor + implements ElementPreprocessor + { + // ----- ElementPreprocessor methods ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public boolean preprocess(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + boolean fUpdated = false; + XmlValue attribute = element.getAttribute(SYSTEM_PROPERTY); + if (attribute != null) + { + // remove the attribute + element.setAttribute(SYSTEM_PROPERTY, null); + + // set the element's value from the specified system property + try + { + String sName = attribute.getString(); + String sValue = Config.getProperty(sName); + + if (sValue != null) + { + element.setString(sValue); + fUpdated = true; + } + } + catch (Exception e) + { + // ignore security exception accessing config property + } + } + + fUpdated |= processValueMacro(element); + + return fUpdated; + } + + /** + * Process macros embedded in element's value + * + * @param element the {@link XmlElement} to preprocess + * + * @return true iff the String value of element was macro expanded + */ + static public boolean processValueMacro(XmlElement element) + { + if (ValueMacroExpression.containsMacro(element.getString())) + { + ValueMacroExpression macroExpr = new ValueMacroExpression(element.getString().trim()); + String sValue = macroExpr.evaluate(SystemPropertyParameterResolver.INSTANCE); + element.setString(sValue); + return true; + } + else + { + return false; + } + } + + // ----- constants ------------------------------------------------------ + + /** + * This singleton instance of the {@link SystemPropertyPreprocessor}. + */ + public static final SystemPropertyPreprocessor INSTANCE = new SystemPropertyPreprocessor(); + + /** + * The constant for the "system-property" attribute. + */ + private static final String SYSTEM_PROPERTY = "system-property"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/TCPAcceptorPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/TCPAcceptorPreprocessor.java new file mode 100644 index 0000000000000..92b0835ff8c84 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/TCPAcceptorPreprocessor.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.DocumentElementPreprocessor.ElementPreprocessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.SimpleElement; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import java.util.List; + + +/** + * A {@link TCPAcceptorPreprocessor} is an {@link ElementPreprocessor} that + * introduces re-writes <tcp-acceptor> configuration elements, in + * particular <local-address> declarations into an <address-provider> containing + * a <local-address>. + * + * @author bko 2013.07.03 + * @since Coherence 12.1.3 + */ +public class TCPAcceptorPreprocessor + implements ElementPreprocessor + { + /** + * {@inheritDoc} + */ + @Override + public boolean preprocess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + if (xmlElement.getName().equals("tcp-acceptor")) + { + XmlElement xmlLocal = xmlElement.getSafeElement("local-address"); + XmlElement xmlProvider = xmlElement.getSafeElement("address-provider"); + + boolean fLocal = !XmlHelper.isEmpty(xmlLocal); + boolean fProvider = !XmlHelper.isEmpty(xmlProvider); + + if (fLocal && fProvider) + { + throw new ConfigurationException(" and " + + " elements are mutually exclusive", "Please define one or the other."); + } + else if (fLocal) + { + // copy the into an + xmlProvider = new SimpleElement("address-provider"); + XmlElement xmlLocalAddress = xmlProvider.addElement("local-address"); + + for (XmlElement xmlChild : (List) xmlLocal.getElementList()) + { + xmlLocalAddress.ensureElement(xmlChild.getName()).setString(xmlChild.getString()); + } + + // now remove the outer + xmlElement.getElementList().remove(xmlLocal); + + // now add the provider, and port-auto-adjust + xmlElement.getElementList().add(xmlProvider); + + return true; + } + else + { + return false; + } + } + else + { + return false; + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/TransactionalPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/TransactionalPreprocessor.java new file mode 100644 index 0000000000000..f548aa5878fc7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/TransactionalPreprocessor.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.DocumentElementPreprocessor.ElementPreprocessor; + +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link TransactionalPreprocessor} is an {@link ElementPreprocessor} that + * introduces (via cloning) internal cache-config xml content for xml elements. + *

+ * Ultimately this {@link ElementPreprocessor} is designed to perform pre-processing of + * Coherence Cache <cache-config> declarations by merging the + * internal-txn-cache-config.xml elements if a transactional-scheme is specified. + * + * @see OperationalDefaultsPreprocessor + * + * @author der 2012.1.17 + * @since Coherence 12.1.2 + */ +public class TransactionalPreprocessor + implements ElementPreprocessor + { + // ----- ElementPreprocessor methods ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public boolean preprocess(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + throw new UnsupportedOperationException("Transactions are not supported in Coherence CE"); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/ViewSchemePreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/ViewSchemePreprocessor.java new file mode 100644 index 0000000000000..ee36df3608db2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/ViewSchemePreprocessor.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.preprocessor; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.DocumentElementPreprocessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +/** + * {@link DocumentElementPreprocessor.ElementPreprocessor} for the {@value VIEW_SCHEME_ELEMENT} element. + * + * @author rlubke + * @since 12.2.1.4 + */ +public class ViewSchemePreprocessor + implements DocumentElementPreprocessor.ElementPreprocessor + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor. + */ + private ViewSchemePreprocessor() + { + } + + // ----- interface ElementPreprocessor ---------------------------------- + + @SuppressWarnings("unchecked") + @Override + public boolean preprocess(ProcessingContext context, XmlElement element) throws ConfigurationException + { + boolean fModified = false; + if (VIEW_SCHEME_ELEMENT.equals(element.getName())) + { + XmlElement xmlViewFilter = element.getElement(VIEW_FILTER_ELEMENT); + XmlElement xmlFrontScheme = element.getElement(FRONT_SCHEME_ELEMENT); + + if (xmlViewFilter == null && xmlFrontScheme == null) + { + element.getElementList().add(DEFAULT_XML.clone()); + xmlFrontScheme = element.getElement(FRONT_SCHEME_ELEMENT); + fModified = true; + } + else + { + if (xmlFrontScheme == null) + { + xmlFrontScheme = XmlHelper.ensureElement(element, FRONT_SCHEME_ELEMENT); + XmlElement xmlViewScheme = XmlHelper.ensureElement(xmlFrontScheme, + CONTINUOUS_QUERY_CACHE_SCHEME_ELEMENT); + + xmlViewScheme.getElementList().add(xmlViewFilter); + element.getElementList().remove(xmlViewFilter); + fModified = true; + } + } + + XmlElement xmlViewScheme = xmlFrontScheme.getElement(CONTINUOUS_QUERY_CACHE_SCHEME_ELEMENT); + for (int i = 0, len = ELEMENTS_TO_MOVE.length; i < len; i++) + { + XmlElement elementToMove = element.getElement(ELEMENTS_TO_MOVE[i]); + if (elementToMove != null) + { + xmlViewScheme.getElementList().add(elementToMove); + element.getElementList().remove(elementToMove); + } + } + } + return fModified; + } + + // ----- constants --------------------------------------------------- + + /** + * The {@code view-filter} element. + */ + private static final String VIEW_FILTER_ELEMENT = "view-filter"; + + /** + * The {@code read-only} element. + */ + private static final String READ_ONLY_ELEMENT = "read-only"; + + /** + * The {@code transformer} element. + */ + private static final String TRANSFORMER_ELEMENT = "transformer"; + + /** + * The {@code reconnect-interval} element. + */ + private static final String RECONNECT_INTERVAL_ELEMENT = "reconnect-interval"; + + /** + * The {@code view-scheme} element. + */ + private static final String VIEW_SCHEME_ELEMENT = "view-scheme"; + + /** + * The {@code front-scheme} element. + */ + private static final String FRONT_SCHEME_ELEMENT = "front-scheme"; + + /** + * The {@code continuous-query-cache-scheme} element. + */ + private static final String CONTINUOUS_QUERY_CACHE_SCHEME_ELEMENT = "continuous-query-cache-scheme"; + + /** + * The {@code listener} element. + */ + private static final String LISTENER_ELEMENT = "listener"; + + /** + * XML that will be injected if no {@value VIEW_FILTER_ELEMENT} element is present. + */ + private static final XmlElement DEFAULT_XML = + XmlHelper.loadXml("" + + " " + + " " + + " " + + " com.tangosol.util.filter.AlwaysFilter" + + " " + + " " + + " " + + ""); + + /** + * Elements that are top-level within the {@value VIEW_SCHEME_ELEMENT} element and need to moved as children + * of the {@value CONTINUOUS_QUERY_CACHE_SCHEME_ELEMENT} element. + */ + private static final String[] ELEMENTS_TO_MOVE = + { + LISTENER_ELEMENT, READ_ONLY_ELEMENT, RECONNECT_INTERVAL_ELEMENT, TRANSFORMER_ELEMENT + }; + + /** + * Singleton {@link ViewSchemePreprocessor} reference. + */ + public static final ViewSchemePreprocessor INSTANCE = new ViewSchemePreprocessor(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/package.html new file mode 100644 index 0000000000000..bac10060be214 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/preprocessor/package.html @@ -0,0 +1,5 @@ + +Defines the Xml document Pre-Processors for Coherence Cache Configuration files. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AbstractEmptyElementProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AbstractEmptyElementProcessor.java new file mode 100644 index 0000000000000..0c17bc74dfe0f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AbstractEmptyElementProcessor.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ConditionalElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link ConditionalElementProcessor} that provides defined behaviors + * for processing empty {@link XmlElement}s. + * + * @author bo 2013.09.15 + * @since Coherence 12.1.3 + */ +public abstract class AbstractEmptyElementProcessor + implements ConditionalElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs an {@link AbstractEmptyElementProcessor} that will + * attempt to process empty {@link XmlElement}s. + */ + public AbstractEmptyElementProcessor() + { + m_behavior = EmptyElementBehavior.PROCESS; + m_oDefaultValue = null; + } + + /** + * Constructs an {@link AbstractEmptyElementProcessor} with the + * specified behavior for processing empty {@link XmlElement}s + * (with a default value of null). + * + * @param behavior the required {@link EmptyElementBehavior} + */ + public AbstractEmptyElementProcessor(EmptyElementBehavior behavior) + { + m_behavior = behavior; + m_oDefaultValue = null; + } + + /** + * Constructs an {@link AbstractEmptyElementProcessor} that will + * return the specified default value when it encounters an empty + * {@link XmlElement}. + * + * @param oDefaultValue the default value to return + */ + public AbstractEmptyElementProcessor(T oDefaultValue) + { + m_behavior = EmptyElementBehavior.USE_DEFAULT_VALUE; + m_oDefaultValue = oDefaultValue; + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + /** + * Process an {@link XmlElement} to return a specific type of value. + * + * @param context the {@link ProcessingContext} in which the + * {@link XmlElement} is being processed + * @param xmlElement the {@link XmlElement} to process + * + * @throws ConfigurationException when a configuration problem was encountered + * + * @return a value of type T + */ + abstract protected T onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException; + + /** + * Determines if an {@link XmlElement} is considered empty. + * + * @param context the {@link ProcessingContext} in which the + * {@link XmlElement} is being processed + * @param xmlElement the {@link XmlElement} to process + * + * @return true if the {@link XmlElement} is considered empty + */ + protected boolean isEmptyElement(ProcessingContext context, XmlElement xmlElement) + { + return xmlElement.isEmpty() && xmlElement.getElementList().size() == 0; + } + + // ----- ConditionalElementProcessor interface -------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean accepts(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + return (m_behavior == EmptyElementBehavior.IGNORE && !isEmptyElement(context, xmlElement)) + || m_behavior != EmptyElementBehavior.IGNORE; + } + + /** + * {@inheritDoc} + */ + @Override + public final T process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + boolean fIsEmpty = isEmptyElement(context, xmlElement); + + if (fIsEmpty) + { + switch (m_behavior) + { + case USE_DEFAULT_VALUE : + return m_oDefaultValue; + + case PROCESS : + return onProcess(context, xmlElement); + + case THROW_EXCEPTION : + throw new ConfigurationException("Invalid <" + xmlElement.getName() + + "> declaration. The specified element is empty in [" + + xmlElement.getParent() + "]", "Please specify a valid <" + + xmlElement.getName() + ">"); + + default : + throw new IllegalStateException("Impossible state reached"); + } + } + else + { + return onProcess(context, xmlElement); + } + } + + // ----- EmptyElementBehavior enum -------------------------------------- + + /** + * The behavior of the {@link ConditionalElementProcessor} when it encounters + * an empty {@link XmlElement}. + */ + public static enum EmptyElementBehavior + { + /** + * When an empty {@link XmlElement} is encountered, simply ignore + * it and don't process the {@link XmlElement} at all. + */ + IGNORE, + + /** + * When an empty {@link XmlElement} is encountered, raise a + * {@link ConfigurationException}. + */ + THROW_EXCEPTION, + + /** + * When an empty {@link XmlElement} is encountered, return a + * default value. + */ + USE_DEFAULT_VALUE, + + /** + * When an empty {@link XmlElement} is encountered, attempt to + * process it. + */ + PROCESS + } + + // ----- data members --------------------------------------------------- + + /** + * The behavior when an empty {@link XmlElement} is encountered. + */ + private EmptyElementBehavior m_behavior; + + /** + * The default value to return (optional). + */ + private T m_oDefaultValue; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AcceptorDependenciesProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AcceptorDependenciesProcessor.java new file mode 100644 index 0000000000000..f26b64692b0ed --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AcceptorDependenciesProcessor.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.peer.acceptor.AcceptorDependencies; + +import com.tangosol.internal.net.service.peer.acceptor.DefaultTcpAcceptorDependencies; +import com.tangosol.run.xml.XmlElement; + +import java.util.Iterator; +import java.util.List; + +/** + * An {@link ElementProcessor} that will parse an <acceptor-config> and + * produce an {@link AcceptorDependencies}. + * + * @author bo 2013.07.02 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("acceptor-config") +public class AcceptorDependenciesProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public AcceptorDependencies process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // locate the definition of an '-acceptor' element and attempt to parse it + XmlElement xmlAcceptor = null; + + for (Iterator iter = ((List) xmlElement.getElementList()).iterator(); + iter.hasNext() && xmlAcceptor == null; ) + { + XmlElement xmlCurrent = iter.next(); + + if (xmlCurrent.getName().endsWith("-acceptor")) + { + xmlAcceptor = xmlCurrent; + } + } + + if (xmlAcceptor == null) + { + // Use the default TCP acceptor if the config is missing. + AcceptorDependencies dependencies = new DefaultTcpAcceptorDependencies(); + context.inject(dependencies, xmlElement); + return dependencies; + } + else + { + // process the acceptor to produce the dependencies + Object oAcceptorDependencies = context.processElement(xmlAcceptor); + + if (oAcceptorDependencies instanceof AcceptorDependencies) + { + AcceptorDependencies dependencies = (AcceptorDependencies) oAcceptorDependencies; + + // now inject properties from this (parent) element into the + // acceptor dependencies. this allows the dependencies to + // "inherit" common properties from this element + context.inject(dependencies, xmlElement); + + return dependencies; + } + else + { + throw new ConfigurationException("The specified acceptor configuration [" + xmlAcceptor + + "] does not produce an AcceptorDependencies", "Please ensure that the acceptor produces an AcceptorDependencies implementation."); + } + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AddressProviderBuilderProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AddressProviderBuilderProcessor.java new file mode 100644 index 0000000000000..9aabd09357d7f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AddressProviderBuilderProcessor.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.AddressProviderBuilder; +import com.tangosol.coherence.config.builder.CustomAddressProviderBuilder; +import com.tangosol.coherence.config.builder.FactoryBasedAddressProviderBuilder; +import com.tangosol.coherence.config.builder.ListBasedAddressProviderBuilder; +import com.tangosol.coherence.config.builder.LocalAddressProviderBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.net.AddressProvider; +import com.tangosol.net.AddressProviderFactory; +import com.tangosol.net.CacheFactory; +import com.tangosol.net.OperationalContext; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Base; + +import java.util.List; + +/** + * An {@link com.tangosol.config.xml.ElementProcessor} that will parse and produce a + * ParameterizedBuilder<AddressProvider> based on an address-provider configuration element, + * that of which is defined as such (with support for foreign-namespaces) + *

+ *   <!ELEMENT ... (socket-address+ | address-provider)>
+ *   <!ELEMENT address-provider
+ *     (class-name | (class-factory-name, method-name), init-params?>
+ *   <!ELEMENT socket-address (address, port)>
+ * 
+ * + * @author bo 2013.03.07 + * @since Coherence 12.1.3 + */ +public class AddressProviderBuilderProcessor + implements ElementProcessor> + { + /** + * {@inheritDoc} + */ + @Override + public AddressProviderBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + AddressProviderBuilder bldrAddressProvider = null; + + if (XmlHelper.hasElement(xmlElement, "address-provider")) + { + xmlElement = xmlElement.getElement("address-provider"); + } + + // assume a custom builder has been provided + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr == null) + { + List listElements = xmlElement.getElementList(); + + if (listElements.size() == 0) + { + // when the element is empty assume it's a string value + // representing a named and registered address provider + // (e.g. pof). + String sName = xmlElement.getString().trim(); + + // grab the operational context from which we can look it up + OperationalContext ctxOperational = context.getCookie(OperationalContext.class); + + if (ctxOperational == null) + { + ConfigurationException e = + new ConfigurationException("Attempted to resolve the OperationalContext in [" + xmlElement + + "] but it was not defined", "The registered ElementHandler for the <" + + xmlElement.getName() + "> element is not operating in an OperationalContext"); + + bldrAddressProvider = new FactoryBasedAddressProviderBuilder(null).setDeferredException(e); + } + else + { + // resolve the named provider factory + AddressProviderFactory factory = ctxOperational.getAddressProviderMap().get(sName); + + if (factory == null) + { + CacheFactory + .log("The address provider named '" + sName + "', specified in [" + xmlElement + + "], cannot be found. Please ensure that the address provider is correctly defined " + + "in the operational configuration override file", Base.LOG_WARN); + + // return null. All 3 @Injectable("address-provider") methods can handle null as an AddressProviderBuilder. + // DefaultTcpAcceptorDependencies.getLoaderAddressProviderBuilder() defaults to NS local address. + } + else + { + bldrAddressProvider = new FactoryBasedAddressProviderBuilder(factory); + } + } + } + else if (listElements.size() == 1 && XmlHelper.hasElement(xmlElement, "local-address")) + { + bldrAddressProvider = newLocalAddressProviderBuilder(listElements.get(0)); + } + else + { + ListBasedAddressProviderBuilder bldrAddressListProvider = new ListBasedAddressProviderBuilder(); + + bldrAddressProvider = bldrAddressListProvider; + + for (XmlElement xmlAddr : (List) xmlElement.getElementList()) + { + String sAddr; + int nPort; + + switch (xmlAddr.getName()) + { + case "socket-address" : + sAddr = xmlAddr.getSafeElement("address").getString().trim(); + nPort = xmlAddr.getSafeElement("port").getInt(); + break; + + case "host-address" : + case "address" : + sAddr = xmlAddr.getString().trim(); + nPort = 0; + if (xmlElement.getName().equals("name-service-addresses")) + { + nPort = CacheFactory.getCluster().getDependencies().getGroupPort(); + } + break; + + default : + continue; + } + + if (sAddr.isEmpty()) + { + // ignore empty elements + continue; + } + + bldrAddressListProvider.add(sAddr, nPort); + } + } + } + else + { + bldrAddressProvider = new CustomAddressProviderBuilder((ParameterizedBuilder) bldr, + context.getDefaultParameterResolver(), xmlElement); + } + + return bldrAddressProvider; + } + + /** + * Build a new AddressProviderBuilder for the local-address. + * + * @param xmlLocalAddress the {@link XmlElement} to process + * + * @return the newly constructed AddressProviderBuilder + */ + public static AddressProviderBuilder newLocalAddressProviderBuilder(XmlElement xmlLocalAddress) + { + XmlElement xmlAddress = xmlLocalAddress.getSafeElement("address"); + XmlElement xmlPort = xmlLocalAddress.getSafeElement("port"); + XmlElement xmlPortAdjust = xmlLocalAddress.getSafeElement("port-auto-adjust"); + String sAddress = xmlAddress.getString().trim(); + String sPort = xmlPort.getString().trim(); + // -1: use ephemeral subport + int nPort = sPort == null || sPort.isEmpty() ? -1 : xmlPort.getInt(); + String sPortAdjust = xmlPortAdjust.getString("true").trim(); + // if nPort is ephemeral (-1 or 0), don't adjust + int nPortAdjust = nPort <= 0 + ? nPort + : sPortAdjust.compareToIgnoreCase("true") == 0 + ? LocalAddressProviderBuilder.MAX_PORT + : sPortAdjust.compareToIgnoreCase("false") == 0 ? nPort : Integer.parseInt(sPortAdjust); + + return new LocalAddressProviderBuilder(sAddress, nPort, nPortAdjust, xmlLocalAddress); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AuthorizedHostsProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AuthorizedHostsProcessor.java new file mode 100644 index 0000000000000..28261a030ce3c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/AuthorizedHostsProcessor.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.InetAddressRangeFilterBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Filter; + +import java.util.Iterator; + +/** + * An {@link ElementProcessor} for <authorized-hosts> Configuration + * Elements. + * + * @author bo 2013.07.10 + */ +@XmlSimpleName("authorized-hosts") +public class AuthorizedHostsProcessor + implements ElementProcessor> + { + /** + * {@inheritDoc} + */ + @Override + public ParameterizedBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + XmlElement xmlHostsFilter = xmlElement.getElement("host-filter"); + + if (xmlHostsFilter != null && !XmlHelper.isEmpty(xmlHostsFilter)) + { + // create a ParameterizedBuilder for the host-filter + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlHostsFilter); + + return (ParameterizedBuilder) bldr; + } + else + { + InetAddressRangeFilterBuilder builder = new InetAddressRangeFilterBuilder(); + + // + for (Iterator iter = xmlElement.getElements("host-address"); iter.hasNext(); ) + { + XmlElement xmlHost = (XmlElement) iter.next(); + + builder.addAuthorizedHostsToFilter(xmlHost.getString(), null); + } + + // + for (Iterator iter = xmlElement.getElements("host-range"); iter.hasNext(); ) + { + XmlElement xmlHost = (XmlElement) iter.next(); + + builder.addAuthorizedHostsToFilter(xmlHost.getSafeElement("from-address").getString(), + xmlHost.getSafeElement("to-address").getString()); + } + + return builder; + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/BackingMapSchemeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/BackingMapSchemeProcessor.java new file mode 100644 index 0000000000000..d659d7311392e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/BackingMapSchemeProcessor.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.coherence.config.scheme.BackingMapScheme; +import com.tangosol.coherence.config.scheme.CachingScheme; +import com.tangosol.coherence.config.scheme.CustomScheme; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +/** + * A BackingMapSchemeProcessor is responsible for processing a + * backing-map-scheme {@link XmlElement} to produce a {@link BackingMapScheme}. + * + * @author pfm 2011.12.02 + * @since Coherence 12.1.2 + */ +@XmlSimpleName(BackingMapSchemeProcessor.ELEMENT_NAME) +public class BackingMapSchemeProcessor + implements ElementProcessor + { + // ----- ElementProcessor interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public BackingMapScheme process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // create a BackingMapScheme instance and inject all annotated properties + BackingMapScheme scheme = context.inject(new BackingMapScheme(), element); + + // the remaining element must be the CachingScheme definition + Object oResult = context.processRemainingElementOf(element); + + if (oResult instanceof CachingScheme) + { + scheme.setInnerScheme((CachingScheme) oResult); + } + else if (oResult instanceof ParameterizedBuilder) + { + scheme.setInnerScheme(new CustomScheme((ParameterizedBuilder) oResult)); + } + else + { + throw new ConfigurationException("The <" + m_sElementName + "> is not a CachingScheme", + "Please ensure that the configured scheme is appropriate for the <" + m_sElementName + '>'); + } + + return scheme; + } + + // ----- data members --------------------------------------------------- + + /** + * The name of the {@link XmlElement} to process. + * + * @since 12.2.1.4 + */ + protected String m_sElementName = ELEMENT_NAME; + + // ----- constants ------------------------------------------------------ + + /** + * The name of the backing-map-scheme {@link XmlElement}. + * + * @since 12.2.1.4 + */ + public static final String ELEMENT_NAME = "backing-map-scheme"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/BufferTypeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/BufferTypeProcessor.java new file mode 100644 index 0000000000000..4c5aa5c67e249 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/BufferTypeProcessor.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.peer.acceptor.TcpAcceptorDependencies; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} for the <buffer-type> Cache Configuration + * element. + * + * @author bo 2013.07.10 + */ +@XmlSimpleName("buffer-type") +public class BufferTypeProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public Integer process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + String sType = xmlElement.getString().trim(); + + return sType.equalsIgnoreCase("heap") + ? TcpAcceptorDependencies.BufferPoolConfig.TYPE_HEAP + : TcpAcceptorDependencies.BufferPoolConfig.TYPE_DIRECT; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CacheMappingProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CacheMappingProcessor.java new file mode 100644 index 0000000000000..4d2b858c36346 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CacheMappingProcessor.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.CacheMapping; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link CacheMappingProcessor} is responsible for processing <cache-mapping> {@link XmlElement}s to produce + * a {@link CacheMapping}. + * + * @author bo 2011.06.24 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("cache-mapping") +public class CacheMappingProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public CacheMapping process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // construct the CacheMapping with the required properties + String sCacheNamePattern = context.getMandatoryProperty("cache-name", String.class, element); + String sSchemeName = context.getMandatoryProperty("scheme-name", String.class, element); + CacheMapping mapping = new CacheMapping(sCacheNamePattern, sSchemeName); + + // now inject any other (optional) properties it may require + context.inject(mapping, element); + + // add the cache-mapping as a cookie so that child processors may access it (mainly to add resources if necessary) + context.addCookie(CacheMapping.class, mapping); + + // process all of the foreign elements + // (this allows the elements to modify the configuration) + context.processForeignElementsOf(element); + + return mapping; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CacheServiceProxyProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CacheServiceProxyProcessor.java new file mode 100644 index 0000000000000..c07b522c86564 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CacheServiceProxyProcessor.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.extend.proxy.DefaultCacheServiceProxyDependencies; +import com.tangosol.net.CacheService; +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link com.tangosol.config.xml.ElementProcessor} that will parse cache-service-proxy configuration + * element tp produce a {@link DefaultCacheServiceProxyDependencies} object. + * + * @author pfm 2013.08.15 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("cache-service-proxy") +public class CacheServiceProxyProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public DefaultCacheServiceProxyDependencies process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + DefaultCacheServiceProxyDependencies deps = new DefaultCacheServiceProxyDependencies(); + context.inject(deps, xmlElement); + + // assume a custom builder has been provided + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr != null) + { + deps.setServiceBuilder((ParameterizedBuilder) bldr); + } + + return deps; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CachingSchemeMappingProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CachingSchemeMappingProcessor.java new file mode 100644 index 0000000000000..a163e6e876811 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CachingSchemeMappingProcessor.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.ResourceMapping; +import com.tangosol.coherence.config.CacheMapping; +import com.tangosol.coherence.config.ResourceMappingRegistry; + +import com.tangosol.coherence.config.SchemeMappingRegistry; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import java.util.Map; + +/** + * An {@link CachingSchemeMappingProcessor} is responsible for processing <caching-scheme-mapping> {@link XmlElement}s + * to update the {@link ResourceMappingRegistry} with {@link CacheMapping}s. + * + * @author bo 2011.06.24 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("caching-scheme-mapping") +public class CachingSchemeMappingProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public ResourceMappingRegistry process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // process the children of the + Map mapProcessedChildren = context.processElementsOf(element); + + // add all of the ResourceMappings to the ResourceMappingRegistry + CacheConfig cacheConfig = context.getCookie(CacheConfig.class); + ResourceMappingRegistry registry = cacheConfig == null + ? new SchemeMappingRegistry() : cacheConfig.getMappingRegistry(); + + for (Object oChild : mapProcessedChildren.values()) + { + if (oChild instanceof ResourceMapping) + { + // FUTURE: handle the case when the cache mapping is a duplicate or weaker pattern? + registry.register((ResourceMapping) oChild); + } + } + + // process all of the foreign elements + // (this allows the elements to modify the configuration) + context.processForeignElementsOf(element); + + return registry; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CachingSchemesProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CachingSchemesProcessor.java new file mode 100644 index 0000000000000..ca1825b78bd65 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CachingSchemesProcessor.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.ServiceSchemeRegistry; +import com.tangosol.coherence.config.scheme.ServiceScheme; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +import java.util.Map; + +/** + * A {@link CachingSchemesProcessor} is an {@link ElementProcessor} for the + * <caching-schemes%gt; element of Coherence Cache Configuration files. + * + * @author bo 2012.05.02 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("caching-schemes") +public class CachingSchemesProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public ServiceSchemeRegistry process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // process the children of the + Map mapProcessedChildren = context.processElementsOf(xmlElement); + + // add all of the ServiceSchemes to the ServiceSchemeRegistry + CacheConfig cacheConfig = context.getCookie(CacheConfig.class); + Base.azzert(cacheConfig != null); + + ServiceSchemeRegistry registry = cacheConfig.getServiceSchemeRegistry(); + Base.azzert(registry != null); + + for (Object oChild : mapProcessedChildren.values()) + { + if (oChild instanceof ServiceScheme) + { + registry.register((ServiceScheme) oChild); + } + } + + // process all of the foreign elements + // (this allows the elements to modify the configuration) + context.processForeignElementsOf(xmlElement); + + return registry; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CompositeSchemeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CompositeSchemeProcessor.java new file mode 100644 index 0000000000000..87ef10b01549d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CompositeSchemeProcessor.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.scheme.AbstractCompositeScheme; +import com.tangosol.coherence.config.scheme.CachingScheme; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; + + +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link CompositeSchemeProcessor} is a {@link CustomizableBuilderProcessor} for schemes + * that consist of a front and back scheme. + * + * @author bo 2012.02.09 + * @since Coherence 12.1.2 + */ +public class CompositeSchemeProcessor + extends CustomizableBuilderProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link CompositeSchemeProcessor} for the specified {@link Class}. + * + * @param clzToRealize the class that will be instantiated, injected and returned during processing + */ + public CompositeSchemeProcessor(Class clzToRealize) + { + super(clzToRealize); + } + + // ----- ElementProcessor methods --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public T process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // create the composite cache and inject and InstanceBuilder if present + T scheme = super.process(context, element); + + // find the front-scheme and inject it into the front CachingScheme + XmlElement xmlFront = element.findElement("front-scheme"); + + if (xmlFront != null) + { + Object oFront = context.processOnlyElementOf(xmlFront); + + if (oFront instanceof CachingScheme) + { + scheme.setFrontScheme((CachingScheme) oFront); + } + else + { + throw new ConfigurationException(String.format(" of %s is not a CachingScheme.", element), + "Please ensure the is of the appropriate type"); + } + } + + // find the back-scheme and inject it into the back CachingScheme + XmlElement xmlBack = element.findElement("back-scheme"); + + if (xmlBack != null) + { + Object oBack = context.processOnlyElementOf(xmlBack); + + if (oBack instanceof CachingScheme) + { + scheme.setBackScheme((CachingScheme) oBack); + } + else + { + throw new ConfigurationException(String.format(" of %s is not a CachingScheme.", element), + "Please ensure the is of the appropriate type"); + } + } + + return scheme; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ConfigurationProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ConfigurationProcessor.java new file mode 100644 index 0000000000000..09d2221c12282 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ConfigurationProcessor.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.CacheConfig; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.run.xml.XmlHelper; +import com.tangosol.util.Base; + +/** + * A {@link ConfigurationProcessor} is responsible for processing a + * configuration {@link XmlElement} to produce a {@link CacheConfig} object. + * + * @author jk 2015.05.28 + * @since Coherence 14.1.1 + */ +@XmlSimpleName(CacheConfig.TOP_LEVEL_ELEMENT_NAME) +public class ConfigurationProcessor + implements ElementProcessor + { + // ----- ElementProcessor interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public CacheConfig process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // get the CacheConfig into which we'll inject configuration + // (this cookie is added by the CacheConfigNamespaceHandler) + CacheConfig cacheConfig = context.getCookie(CacheConfig.class); + + Base.azzert(cacheConfig != null); + + // process the to establish the ResourceRegistry defaults + XmlElement xmlDefaults = element.getElement("defaults"); + + if (xmlDefaults != null) + { + context.processElement(xmlDefaults); + } + + // inject the configuration in the cache config + context.inject(cacheConfig, element); + + // process the foreign elements to allow custom configuration + context.processForeignElementsOf(element); + + // finally add the processed element as resource that can be used + // by legacy parts of coherence + context.getResourceRegistry().registerResource(XmlElement.class, "legacy-cache-config", element); + + return cacheConfig; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CustomizableBinaryStoreManagerBuilderProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CustomizableBinaryStoreManagerBuilderProcessor.java new file mode 100644 index 0000000000000..71f0b9f41b19a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CustomizableBinaryStoreManagerBuilderProcessor.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.storemanager.BinaryStoreManagerBuilder; +import com.tangosol.coherence.config.builder.storemanager.BinaryStoreManagerBuilderCustomization; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; + + +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link CustomizableBinaryStoreManagerBuilderProcessor} is a {@link CustomizableBuilderProcessor} that additionally + * processes the required definition of a {@link BinaryStoreManagerBuilder} for those classes supporting + * {@link BinaryStoreManagerBuilderCustomization}. + * + * @author bo 2012.02.09 + * @since Coherence 12.1.2 + */ +public class CustomizableBinaryStoreManagerBuilderProcessor + extends CustomizableBuilderProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link CustomizableBinaryStoreManagerBuilderProcessor} for the specified + * {@link BinaryStoreManagerBuilderCustomization} {@link Class}. + * + * @param clzToRealize the class that will be instantiated, injected and returned during processing + */ + public CustomizableBinaryStoreManagerBuilderProcessor(Class clzToRealize) + { + super(clzToRealize); + } + + // ----- ElementProcessor methods --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public T process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + T bldr = super.process(context, element); + Object oProcessed = context.processRemainingElementOf(element); + + if (oProcessed instanceof BinaryStoreManagerBuilder) + { + bldr.setBinaryStoreManagerBuilder((BinaryStoreManagerBuilder) oProcessed); + } + else + { + throw new ConfigurationException(String.format( + "%s is missing the definition of an appropriate <*-store-manager>.", + element), "Please ensure that a store manager is correctly defined"); + } + + return bldr; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CustomizableBuilderProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CustomizableBuilderProcessor.java new file mode 100755 index 0000000000000..f52d68264a1df --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/CustomizableBuilderProcessor.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.BuilderCustomization; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +/** + * A {@link CustomizableBuilderProcessor} is a multi-purpose {@link ElementProcessor} + * responsible for processing xml elements that produce objects supporting {@link BuilderCustomization}. + * + * @author pfm 2011.11.30 + * @author bo 2012.02.09 + * + * @since Coherence 12.1.2 + */ +public class CustomizableBuilderProcessor + implements ElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link CustomizableBuilderProcessor} for the specified {@link Class}. + * + * @param clzToRealize the class that will be instantiated, injected and returned during processing + */ + public CustomizableBuilderProcessor(Class clzToRealize) + { + m_clzToRealize = clzToRealize; + } + + // ----- ElementProcessor methods --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public T process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // create the target object to be injected + T target = instantiate(); + + // should it support builder customization, configure the custom builder from the element + if (target instanceof BuilderCustomization) + { + // get the parameterized builder from the element + ParameterizedBuilder bldrCustom = ElementProcessorHelper.processParameterizedBuilder(context, element); + + // inject the custom builder into the target + ((BuilderCustomization) target).setCustomBuilder(bldrCustom); + } + + // now inject the target with the values in the element + return context.inject(target, element); + } + + // ----- CustomizableBuilderProcessor methods --------------------------- + + /** + * Instantiate the required class to inject and return. + * + * @return object to be injected + */ + protected T instantiate() + { + try + { + return m_clzToRealize.newInstance(); + } + catch (InstantiationException e) + { + throw Base.ensureRuntimeException(e, "Failed to instantiate " + m_clzToRealize + + ". Please ensure it has a public no args constructor."); + } + catch (IllegalAccessException e) + { + throw Base.ensureRuntimeException(e, "Failed to instantiate " + m_clzToRealize + + ". Please ensure it has a public no args constructor."); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Class} to create, inject (from the {@link XmlElement} being processed) and return. + */ + private Class m_clzToRealize; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/DefaultsProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/DefaultsProcessor.java new file mode 100644 index 0000000000000..e454870aaad8c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/DefaultsProcessor.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlConfigurable; +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.RegistrationBehavior; +import com.tangosol.util.ResourceRegistry; + +import java.util.List; + +import static com.tangosol.util.BuilderHelper.using; + +/** + * The {@link DefaultsProcessor} is responsible for processing the <defaults> {@link XmlElement} + * in a Coherence Cache Configuration file, registering each of the processed elements with + * the {@link com.tangosol.util.ResourceRegistry}. + * + * @author bo 2013.12.01 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("defaults") +public class DefaultsProcessor + implements ElementProcessor + { + @Override + public Void process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // acquire the resource registry into which we'll register default resources + ResourceRegistry registry = context.getResourceRegistry(); + + // iterate over the child xml elements, each of which produces a resource + List listChildren = element.getElementList(); + + for (XmlElement xmlChild : listChildren) + { + // process the child to produce a resource + Object oResource = context.processElement(xmlChild); + + // only register non-null resources + if (oResource != null) + { + // the name of the xml element will be the default name of the resource + String sResourceName = xmlChild.getQualifiedName().getLocalName(); + Class clzResource = oResource.getClass(); + + // register the resource (as the class) using replace to ensure values set here override + registry.registerResource(clzResource, sResourceName, using(oResource), RegistrationBehavior.REPLACE, null); + + // register the resource (for each non-java interface that it implements) + for (Class clzInterface : clzResource.getInterfaces()) + { + if (isRegisterable(clzInterface)) + { + registry.registerResource(clzInterface, sResourceName, using(oResource), RegistrationBehavior.REPLACE, null); + } + } + } + } + + // nothing to return + return null; + } + + /** + * Determines if the specified {@link Class} of resource should be registered + * with a {@link ResourceRegistry}. + * + * @param clzResource the {@link Class} of resource + * + * @return true if the resource should be registered + */ + protected boolean isRegisterable(Class clzResource) + { + // 1. We don't want to register resources using java platform interfaces + // (ie: resources that implement java interface) + + // 2. We don't want to register resources that are deprecated (like XmlConfigurable) + return clzResource != null && !clzResource.getName().startsWith("java") + && !clzResource.equals(XmlConfigurable.class); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/DeltaCompressorProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/DeltaCompressorProcessor.java new file mode 100644 index 0000000000000..93c61bfc295db --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/DeltaCompressorProcessor.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.io.BinaryDeltaCompressor; +import com.tangosol.io.DecoratedBinaryDeltaCompressor; +import com.tangosol.io.DecorationOnlyDeltaCompressor; +import com.tangosol.io.DeltaCompressor; + +import com.tangosol.run.xml.XmlElement; + +/** + * An ElementProcessor for processing <compressor> configurations. + * + * @author bo 2013.04.01 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("compressor") +public class DeltaCompressorProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public DeltaCompressor process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // attempt to locate a ParameterizedBuilder + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr instanceof ParameterizedBuilder) + { + try + { + ParameterizedBuilder bldrDeltaCompressor = + (ParameterizedBuilder) bldr; + + return bldrDeltaCompressor.realize(context.getDefaultParameterResolver(), + context.getContextClassLoader(), null); + } + catch (Exception e) + { + throw new ConfigurationException("Invalid <" + xmlElement.getName() + + "> declaration. The specified builder doesn't produce a DeltaCompressor in [" + xmlElement + + "]", "Please specify a <" + xmlElement.getName() + ">", e); + } + } + else if (xmlElement.getString().equals("standard")) + { + // COH-5528 workaround: use the plain binary delta compressor even + // for POF until the performance regression is solved. + return new DecoratedBinaryDeltaCompressor(new BinaryDeltaCompressor()); + + // COH-5250: create a "dummy" serializer to check for POF + // ClassLoader loader = Base.getContextClassLoader(); + // SerializerFactory factory = deps.getSerializerFactory(); + // Serializer serializer = factory == null + // ? ExternalizableHelper.ensureSerializer(loader) : factory.createSerializer(loader); + // + // return new DecoratedBinaryDeltaCompressor(serializer instanceof PofContext + // ? ExternalizableHelper.getDeltaCompressor(serializer, new PofDeltaCompressor()) + // : new BinaryDeltaCompressor()); + } + else + { + return new DecorationOnlyDeltaCompressor(); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ElementProcessorHelper.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ElementProcessorHelper.java new file mode 100644 index 0000000000000..0225ab2f972f4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ElementProcessorHelper.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.InstanceBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.StaticFactoryInstanceBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + + +import com.tangosol.run.xml.QualifiedName; +import com.tangosol.run.xml.XmlElement; + +import java.util.List; + +/** + * An {@link ElementProcessorHelper} provides a number of helper methods for {@link ElementProcessor}s. + * + * @author bo 2012.02.02 + * @since Coherence 12.1.2 + */ +public class ElementProcessorHelper + { + /** + * Attempts to process the specified {@link XmlElement} to produce a {@link ParameterizedBuilder} given a + * {@link ProcessingContext}. + * + * @param context the {@link ProcessingContext} to use + * @param element the {@link XmlElement} that contains the definition of a {@link ParameterizedBuilder} + * + * @return a {@link ParameterizedBuilder} or null if one is not available + * + * @throws ConfigurationException if an error occurs while processing the {@link ParameterizedBuilder} + */ + public static ParameterizedBuilder processParameterizedBuilder(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + ParameterizedBuilder bldr; + + // determine the prefix of the namespace in which we're operating as this should be used + // for locating child elements that define custom builders in this element + String sPrefix = element.getQualifiedName().getPrefix(); + + // does this element define a "class-scheme"? + QualifiedName qName = new QualifiedName(sPrefix, "class-scheme"); + XmlElement xml = element.getElement(qName.getName()); + + if (xml != null) + { + // have the context process the "class-scheme" element + bldr = (ParameterizedBuilder) context.processElement(xml); + } + else + { + // does this element define an "instance" element? + qName = new QualifiedName(sPrefix, "instance"); + xml = element.getElement(qName.getName()); + + if (xml != null) + { + // have the context process the "instance" element + bldr = (ParameterizedBuilder) context.processElement(xml); + } + else + { + // does this element define a "class-name" element? + qName = new QualifiedName(sPrefix, "class-name"); + xml = element.getElement(qName.getName()); + + if (xml != null) + { + // use an InstanceBuilder to capture the "class-name" definition + bldr = context.inject(new InstanceBuilder(), element); + } + else + { + // does this element define a "class-factory-name" element? + qName = new QualifiedName(sPrefix, "class-factory-name"); + xml = element.getElement(qName.getName()); + + if (xml != null) + { + // use a StaticFactoryInstanceBuilder to capture the "class-factory-name" + bldr = context.inject(new StaticFactoryInstanceBuilder(), element); + } + else + { + // when there's only a single child element and it's from a foreign namespace, + // assume that it produces a builder and return it if it is + List listChildren = element.getElementList(); + + if (listChildren.size() == 1 + && !listChildren.get(0).getQualifiedName().getPrefix().equals(sPrefix)) + { + bldr = ensureBuilderOrNull(context.processOnlyElementOf(element)); + } + else if (listChildren.size() == 2) + { + // or there may be a scheme-name element and a foreign namespace + qName = new QualifiedName(sPrefix, "scheme-name"); + + XmlElement elementOne = listChildren.get(0); + XmlElement elementTwo = listChildren.get(1); + + if (elementOne.getQualifiedName().equals(qName) + && !elementTwo.getQualifiedName().getPrefix().equals(sPrefix)) + { + // first element is scheme-name and second element is a foreign namespace + bldr = ensureBuilderOrNull(context.processElement(elementTwo)); + } + else if (!elementOne.getQualifiedName().getPrefix().equals(sPrefix) + && elementTwo.getQualifiedName().equals(qName)) + { + // second element is scheme-name and first element is a foreign namespace + bldr = ensureBuilderOrNull(context.processElement(elementOne)); + } + else + { + bldr = null; + } + } + else + { + // no custom builder is available + bldr = null; + } + } + } + } + } + + return bldr; + } + + private static ParameterizedBuilder ensureBuilderOrNull(Object oBuilder) + { + if (oBuilder instanceof ParameterizedBuilder) + { + return (ParameterizedBuilder) oBuilder; + } + else + { + // not a builder, so ignore it + return null; + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/EnumProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/EnumProcessor.java new file mode 100644 index 0000000000000..7fb7aa46b65e1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/EnumProcessor.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link EnumProcessor} is responsible for processing Coherence Enum values + * and return the corresponding Enum type. + * + * @author lh 2016.06.14 + * @since Coherence 12.2.1 + */ +public class EnumProcessor> + extends AbstractEmptyElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link EnumProcessor}. + * + * @param clzEnum the class type of enum + */ + public EnumProcessor(Class clzEnum) + { + super(EmptyElementBehavior.IGNORE); + m_clzEnum = clzEnum; + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + /** + * {@inheritDoc} + */ + @Override + public T onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + for (T type : m_clzEnum.getEnumConstants()) + { + if (type.name().equalsIgnoreCase(xmlElement.getValue().toString())) + { + return type; + } + } + + return null; + } + + Class m_clzEnum; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/EvictionPolicyProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/EvictionPolicyProcessor.java new file mode 100755 index 0000000000000..9d6b848b91c8a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/EvictionPolicyProcessor.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.EvictionPolicyBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + + +import com.tangosol.net.cache.ConfigurableCacheMap.EvictionPolicy; + +import com.tangosol.run.xml.XmlElement; + +import java.text.ParseException; + +/** + * A {@link EvictionPolicyProcessor} is responsible for processing an + * eviction-policy {@link XmlElement} to produce an {@link EvictionPolicyBuilder}. + * + * @author pfm 2011.12.02 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("eviction-policy") +public class EvictionPolicyProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public EvictionPolicyBuilder process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + EvictionPolicyBuilder bldr = new EvictionPolicyBuilder(); + + if (element.getElementList().isEmpty()) + { + try + { + String sValue = element.getString().trim(); + + bldr.setEvictionType(context.getExpressionParser().parse(sValue, String.class)); + } + catch (ParseException e) + { + throw new ConfigurationException("Failed to parse the specifie eviction-policy", + "Please ensure a correct eviction-policy is specified", e); + } + } + else + { + bldr.setCustomBuilder((ParameterizedBuilder) context.processOnlyElementOf(element)); + } + + return bldr; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ExecutorProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ExecutorProcessor.java new file mode 100644 index 0000000000000..da28c70bdc850 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ExecutorProcessor.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.ssl.SSLSocketProviderDefaultDependencies; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import java.util.concurrent.Executor; + +/** + * An {@link ElementProcessor} that will parse and produce a + * Executor based on an ssl/executor configuration element. + * + * @author jf 2015.11.11 + * @since Coherence 12.2.1.1 + */ +@XmlSimpleName("executor") +public class ExecutorProcessor + implements ElementProcessor> + { + /** + * {@inheritDoc} + */ + @Override + public ParameterizedBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + if (XmlHelper.hasElement(xmlElement, "executor")) + { + xmlElement = xmlElement.getElement("executor"); + } + + return (ParameterizedBuilder) ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/HttpAcceptorDependenciesProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/HttpAcceptorDependenciesProcessor.java new file mode 100644 index 0000000000000..7a4accfec8141 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/HttpAcceptorDependenciesProcessor.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.InstanceBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.coherence.http.HttpServer; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.peer.acceptor.DefaultHttpAcceptorDependencies; +import com.tangosol.internal.net.service.peer.acceptor.HttpAcceptorDependencies; + +import com.tangosol.run.xml.XmlElement; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.ServiceLoader; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +/** + * An {@link ElementProcessor} to produce a {@link HttpAcceptorDependencies} + * from a <http-acceptor%gt; configuration. + * + * @author bo 2013.07.11 + */ +@XmlSimpleName("http-acceptor") +public class HttpAcceptorDependenciesProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public HttpAcceptorDependencies process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // establish the dependencies into which we'll perform some injection + DefaultHttpAcceptorDependencies dependencies = new DefaultHttpAcceptorDependencies(); + + // resolve the HTTP server + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr == null) + { + // no builder has been specified, so let's use a default server + dependencies.setHttpServer(HttpServer.create()); + } + else + { + try + { + ParameterizedBuilder bldrHttpServer = (ParameterizedBuilder) bldr; + + dependencies.setHttpServer(bldrHttpServer.realize(context.getDefaultParameterResolver(), + context.getContextClassLoader(), null)); + } + catch (ClassCastException e) + { + throw new ConfigurationException("Invalid <" + xmlElement.getName() + + "> declaration. The specified element doesn't produce a HttpServer" + + " as expected in [" + xmlElement + "]", "Please specify a valid <" + + xmlElement.getName() + ">"); + } + } + + // process each of the definitions + Map mapConfig = new HashMap(); + + for (Iterator iter = xmlElement.getElements("resource-config"); iter.hasNext(); ) + { + XmlElement xmlResourceConfig = (XmlElement) iter.next(); + String sContextPath = context.getOptionalProperty("context-path", String.class, null, xmlResourceConfig); + + ParameterizedBuilder bldrResourceConfig = ElementProcessorHelper.processParameterizedBuilder(context, + xmlResourceConfig); + + if (bldrResourceConfig == null) + { + bldrResourceConfig = + new InstanceBuilder<>("com.tangosol.coherence.rest.server.DefaultResourceConfig"); + } + + try + { + // Backporting note: the following referenced Jersey class is Jersey 2.x. In Jersey 1.x, + // the same class is in different package: com.sun.jersey.api.core.ResourceConfig. + // At this time 12.1.3 is still using Jersey 1.x and 12.2.1 is using Jersey 2.x. + Class jerseyClz = + context.getContextClassLoader().loadClass("org.glassfish.jersey.server.ResourceConfig"); + Object oResourceConfig = bldrResourceConfig.realize(context.getDefaultParameterResolver(), + context.getContextClassLoader(), null); + + if (!jerseyClz.isAssignableFrom(oResourceConfig.getClass())) + { + throw new IllegalArgumentException(" is not an instance of " + jerseyClz.getCanonicalName()); + } + + if (sContextPath == null) + { + ApplicationPath path = oResourceConfig.getClass().getAnnotation(ApplicationPath.class); + sContextPath = path == null ? "/" : path.value(); + } + + mapConfig.put(sContextPath, oResourceConfig); + + } + catch (Exception e) + { + throw new ConfigurationException("Failed to realize the specified by [" + + xmlResourceConfig + + "] as the underlying class could not be found", "Please ensure that the class is available on the class path", e); + } + } + + if (mapConfig.isEmpty()) + { + ServiceLoader loaderApps = ServiceLoader.load(Application.class); + + for (Application application : loaderApps) + { + ApplicationPath annotation = application.getClass().getAnnotation(ApplicationPath.class); + String sPath = annotation == null ? "/" : annotation.value(); + + if (sPath.charAt(0) != '/') + { + sPath = '/' + sPath; + } + + mapConfig.put(sPath, application); + } + } + + dependencies.setResourceConfig(mapConfig); + + // now inject everything else + context.inject(dependencies, xmlElement); + + return dependencies; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InitParamProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InitParamProcessor.java new file mode 100644 index 0000000000000..62cc01cb63bc5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InitParamProcessor.java @@ -0,0 +1,466 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.CacheMapping; +import com.tangosol.coherence.config.CacheMappingRegistry; +import com.tangosol.coherence.config.ResourceMapping; +import com.tangosol.coherence.config.ResourceMappingRegistry; +import com.tangosol.coherence.config.ServiceSchemeRegistry; +import com.tangosol.coherence.config.TopicMapping; +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.coherence.config.scheme.CachingScheme; +import com.tangosol.coherence.config.scheme.ClassScheme; +import com.tangosol.coherence.config.scheme.ExternalScheme; +import com.tangosol.coherence.config.scheme.FlashJournalScheme; +import com.tangosol.coherence.config.scheme.InvocationScheme; +import com.tangosol.coherence.config.scheme.LocalScheme; +import com.tangosol.coherence.config.scheme.ObservableCachingScheme; +import com.tangosol.coherence.config.scheme.OverflowScheme; +import com.tangosol.coherence.config.scheme.RamJournalScheme; +import com.tangosol.coherence.config.scheme.ReadWriteBackingMapScheme; +import com.tangosol.coherence.config.scheme.RemoteInvocationScheme; +import com.tangosol.coherence.config.scheme.ServiceScheme; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.expression.Value; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.net.BackingMapManagerContext; +import com.tangosol.net.CacheFactory; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.ExtensibleConfigurableCacheFactory; +import com.tangosol.net.NamedCache; + +import com.tangosol.net.NamedCollection; +import com.tangosol.net.ValueTypeAssertion; +import com.tangosol.net.topic.NamedTopic; +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; +import com.tangosol.util.UUID; + +import java.util.Map; + +/** + * An {@link InitParamProcessor} is responsible for processing <init-param> {@link XmlElement}s to produce + * {@link Parameter}s. + * + * @author bo 2011.06.24 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("init-param") +public class InitParamProcessor + implements ElementProcessor + { + // ----- ElementProcessor interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Parameter process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // when param-name is undefined, use a newly generated UUID + String sName = context.getOptionalProperty("param-name", String.class, new UUID().toString(), element); + Expression exprValue = context.getMandatoryProperty("param-value", Expression.class, element); + String sType = context.getOptionalProperty("param-type", String.class, null, element); + + // handle the special case when the parameter type is a reference to another cache. + Class clzType = null; + + if (sType != null && sType.equals("{cache-ref}")) + { + Expression exprCacheName = exprValue; + ClassLoader loader = context.getContextClassLoader(); + ResourceMappingRegistry registry = context.getCookie(CacheConfig.class).getMappingRegistry(); + + clzType = NamedCache.class; + exprValue = new CacheRefExpression(exprCacheName, loader, registry); + } + else if (sType != null && sType.equals("{destination-ref}")) + { + Expression exprCacheName = exprValue; + ClassLoader loader = context.getContextClassLoader(); + ResourceMappingRegistry registry = context.getCookie(CacheConfig.class).getMappingRegistry(); + + clzType = NamedTopic.class; + exprValue = new CollectionRefExpression(exprCacheName, loader, registry); + } + else if (sType != null && sType.equals("{scheme-ref}")) + { + Expression exprSchemeName = exprValue; + ServiceSchemeRegistry registry = context.getCookie(CacheConfig.class).getServiceSchemeRegistry(); + + // we use Object.class here as we don't know the type of value the scheme-ref will produce. + clzType = Object.class; + exprValue = new SchemeRefExpression(exprSchemeName, registry); + } + else + { + // attempt to load the specified class + if (sType != null) + { + try + { + clzType = context.getContextClassLoader().loadClass(sType); + } + catch (ClassNotFoundException e) + { + throw new ConfigurationException(String.format( + "Failed to resolve the specified param-type in [%s]", + element), "Please ensure that the specified class is publicly accessible via the class path", + e); + } + } + } + + return new Parameter(sName, clzType, exprValue); + } + + // ----- inner class: DataStructureRefExpression ------------------------ + + /** + * An {@link Expression} implementation that represents the use of + * a {cache-ref} macro in a Cache Configuration File. + */ + public static abstract class DataStructureRefExpression + implements Expression + { + // ----- constructors ----------------------------------------------- + + /** + * Constructs a DataStructureRefExpression. + * + * @param exprCacheName the Expression representing the Cache Name + * @param classLoader the ClassLoader to use when loading the Cache + * @param registry the ResourceMappingRegistry to locate CacheMapping/TopicMapping + * definitions + */ + public DataStructureRefExpression(Expression exprCacheName, ClassLoader classLoader, + ResourceMappingRegistry registry) + { + m_exprCacheName = exprCacheName; + m_classLoader = classLoader; + m_registryResourceMappings = registry; + } + + // ----- DataStructureRefExpression methods ------------------------- + + public String evaluateName(ParameterResolver resolver, + Class clsType, + String sElementName) + { + String sDesiredName = new Value(m_exprCacheName.evaluate(resolver)).as(String.class); + + if (sDesiredName.contains("*")) + { + // the desired name contains a wildcard * and as + // such we need to replace that with the string that + // matched the wildcard * in the parameterized name + + // the parameter resolver will have the name + Parameter paramCacheName = resolver.resolve(sElementName); + String sCacheName = paramCacheName == null ? null : paramCacheName.evaluate(resolver).as(String.class); + + if (sCacheName == null) + { + // as we can't resolve the parameterized cache-name + // we'll just use the desired name as is. + } + else + { + ResourceMapping mapping = m_registryResourceMappings.findMapping(sCacheName, clsType); + + if (mapping.usesWildcard()) + { + String sWildcardValue = mapping.getWildcardMatch(sCacheName); + + sDesiredName = sDesiredName.replaceAll("\\*", sWildcardValue); + } + else + { + // as the parameterized cache-name doesn't use a wildcard + // we'll just use the desired name as is. + } + } + } + + return sDesiredName; + } + + public ClassLoader getClassLoader() + { + return m_classLoader; + } + + // ----- Object interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return String.format(getClass().getSimpleName() + "{exprCacheName=%s}", m_exprCacheName); + } + + // ----- data members ----------------------------------------------- + + /** + * The Expression that specifies the Cache Name to resolve. + */ + private Expression m_exprCacheName; + + /** + * The ClassLoader to use to resolve/load the underlying Cache. + */ + private ClassLoader m_classLoader; + + /** + * The ResourceMappingRegistry to use to lookup CacheMapping/TopicMapping definitions. + */ + private ResourceMappingRegistry m_registryResourceMappings; + } + + // ----- inner class: CacheRefExpression -------------------------------- + + /** + * An {@link Expression} implementation that represents the use of + * a {cache-ref} macro in a Configuration File. + */ + public static class CacheRefExpression + extends DataStructureRefExpression + { + // ----- constructors ----------------------------------------------- + + /** + * Constructs a CacheRefExpression. + * + * @param exprCacheName the Expression representing the Cache Name + * @param classLoader the ClassLoader to use when loading the Cache + * @param registry the ResourceMappingRegistry to locate ResourceMapping + * definitions + */ + public CacheRefExpression(Expression exprCacheName, ClassLoader classLoader, ResourceMappingRegistry registry) + { + super(exprCacheName, classLoader, registry); + } + + /** + * Constructs a CacheRefExpression. + * + * @param exprCacheName the Expression representing the Cache Name + * @param classLoader the ClassLoader to use when loading the Cache + * @param registry the CacheMappingRegistry to locate CacheMapping + * definitions + * + * @deprecated As of Coherence 14.1.1, use {@link #CacheRefExpression(Expression, ClassLoader, ResourceMappingRegistry)}. + */ + public CacheRefExpression(Expression exprCacheName, ClassLoader classLoader, CacheMappingRegistry registry) + { + super(exprCacheName, classLoader, registry.getMappingRegistry()); + } + + // ----- Expression interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public NamedCache evaluate(ParameterResolver resolver) + { + String sName = evaluateName(resolver, CacheMapping.class, "cache-name"); + ClassLoader loader = getClassLoader(); + Parameter parameter = resolver.resolve("manager-context"); + BackingMapManagerContext ctx = (BackingMapManagerContext) parameter.evaluate(resolver).get(); + ConfigurableCacheFactory ccf = ctx == null + ? CacheFactory.getConfigurableCacheFactory(loader) + : ctx.getManager().getCacheFactory(); + + return ccf.ensureCache(sName, loader); + } + } + + // ----- inner-class: CollectionRefExpression --------------------------- + + /** + * An {@link Expression} implementation that represents the use of + * a {collection-ref} macro in a Configuration File. + */ + public static class CollectionRefExpression + extends DataStructureRefExpression + { + // ----- constructors ---------------------------------------------------- + + /** + * Constructs a CollectionRefExpression. + * + * @param exprCacheName the Expression representing the collection name + * @param classLoader the ClassLoader to use when loading the collection + * @param registry the ResourceMappingRegistry to locate QueueMapping + * definitions + */ + public CollectionRefExpression(Expression exprCacheName, ClassLoader classLoader, ResourceMappingRegistry registry) + { + super(exprCacheName, classLoader, registry); + } + + // ----- Expression interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public NamedCollection evaluate(ParameterResolver resolver) + { + String sName = evaluateName(resolver, TopicMapping.class, "topic-name"); + ClassLoader classLoader = getClassLoader(); + Parameter parameter = resolver.resolve("manager-context"); + BackingMapManagerContext ctx = (BackingMapManagerContext) parameter.evaluate(resolver).get(); + ExtensibleConfigurableCacheFactory eccf = ctx == null + ? (ExtensibleConfigurableCacheFactory) CacheFactory.getConfigurableCacheFactory(classLoader) + : (ExtensibleConfigurableCacheFactory) ctx.getManager().getCacheFactory(); + + return eccf.ensureTopic(sName, classLoader, ValueTypeAssertion.withRawTypes()); + } + } + + // ----- inner class: SchemeRefExpression ------------------------------- + + /** + * An {@link Expression} implementation that represents the use of + * a {scheme-ref} macro in a Cache Configuration File. + */ + public static class SchemeRefExpression + implements Expression + { + // ----- constructors ----------------------------------------------- + + /** + * Constructs a SchemeRefExpression. + * + * @param exprSchemeName the name of the ServiceScheme to resolve + * @param registry the ServiceSchemeRegistry to lookup ServiceSchemes + */ + public SchemeRefExpression(Expression exprSchemeName, ServiceSchemeRegistry registry) + { + m_exprSchemeName = exprSchemeName; + m_registry = registry; + } + + // ----- Expression interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object evaluate(ParameterResolver resolver) + { + String sSchemeName = new Value(m_exprSchemeName.evaluate(resolver)).as(String.class); + ServiceScheme scheme = m_registry.findSchemeBySchemeName(sSchemeName); + + // the parameter resolver will have the classloader + Parameter paramClassLoader = resolver.resolve("class-loader"); + ClassLoader classLoader = paramClassLoader == null + ? Base.getContextClassLoader() + : paramClassLoader.evaluate(resolver).as(ClassLoader.class); + + if (scheme instanceof InvocationScheme || scheme instanceof RemoteInvocationScheme) + { + return ((InvocationScheme) scheme).realizeService(resolver, classLoader, CacheFactory.getCluster()); + } + else if (scheme instanceof ClassScheme) + { + return ((ClassScheme) scheme).realize(resolver, classLoader, null); + } + else if (scheme instanceof CachingScheme) + { + CachingScheme cachingScheme = (CachingScheme) scheme; + + // construct MapBuilder.Dependencies so that we can realize the caching scheme/backing map + ConfigurableCacheFactory ccf = CacheFactory.getConfigurableCacheFactory(); + + // the parameter resolver will have the cache-name + Parameter paramCacheName = resolver.resolve("cache-name"); + String sCacheName = paramCacheName == null ? null : paramCacheName.evaluate(resolver).as(String.class); + + // the parameter resolver will have the manager-context + Parameter paramBMMC = resolver.resolve("manager-context"); + BackingMapManagerContext bmmc = paramBMMC == null + ? null + : paramBMMC.evaluate(resolver).as(BackingMapManagerContext.class); + + MapBuilder.Dependencies dependencies = new MapBuilder.Dependencies(ccf, bmmc, classLoader, sCacheName, + cachingScheme.getServiceType()); + + // the resulting map/cache + Map map; + + if (scheme instanceof LocalScheme || scheme instanceof OverflowScheme + || scheme instanceof ExternalScheme || scheme instanceof ReadWriteBackingMapScheme + || scheme instanceof FlashJournalScheme || scheme instanceof RamJournalScheme) + { + // for "local" schemes we realize a backing map + map = cachingScheme.realizeMap(resolver, dependencies); + } + else + { + // for anything else, we realize the cache + // NOTE: this cache won't be registered or visible + // via ensureCache/getCache + map = cachingScheme.realizeCache(resolver, dependencies); + } + + // add the listeners when the scheme is observable + if (cachingScheme instanceof ObservableCachingScheme) + { + ObservableCachingScheme observableScheme = (ObservableCachingScheme) cachingScheme; + + observableScheme.establishMapListeners(map, resolver, dependencies); + } + + return map; + } + else + { + throw new ConfigurationException(String.format("Failed to resolve the {scheme-ref} name [%s]", + sSchemeName), "Please ensure that the specified {scheme-ref} refers to a defined "); + } + } + + // ----- Object interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return String.format("SchemeRefExpression{exprSchemeName=%s}", m_exprSchemeName); + } + + // ----- data members ----------------------------------------------- + + /** + * The Expression that specifies the Scheme Name to resolve. + */ + private Expression m_exprSchemeName; + + /** + * The ServiceSchemeRegistry to use to lookup ServiceSchemes. + */ + private ServiceSchemeRegistry m_registry; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InitParamsProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InitParamsProcessor.java new file mode 100644 index 0000000000000..530e5dcd93249 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InitParamsProcessor.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + + +import com.tangosol.run.xml.XmlElement; + + +import java.util.List; + +/** + * An {@link InitParamsProcessor} is responsible for processing <init-params> {@link XmlElement}s to produce + * {@link ResolvableParameterList}s. + * + * @author bo 2011.06.24 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("init-params") +public class InitParamsProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public ResolvableParameterList process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + ResolvableParameterList listParameters = new ResolvableParameterList(); + + for (XmlElement elementChild : ((List) element.getElementList())) + { + Object oValue = context.processElement(elementChild); + + if (oValue instanceof Parameter) + { + listParameters.add((Parameter) oValue); + } + else + { + throw new ConfigurationException(String.format("Invalid parameter definition [%s] in [%s]", + elementChild, element), "Please ensure the parameter is correctly defined"); + } + } + + return listParameters; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InitiatorDependenciesProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InitiatorDependenciesProcessor.java new file mode 100644 index 0000000000000..1d3de4ea22a56 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InitiatorDependenciesProcessor.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.peer.initiator.DefaultTcpInitiatorDependencies; +import com.tangosol.internal.net.service.peer.initiator.InitiatorDependencies; + +import com.tangosol.run.xml.XmlElement; + +import java.util.Iterator; +import java.util.List; + +/** + * An {@link ElementProcessor} that will parse an <initator-config> and + * produce an {@link InitiatorDependencies}. + * + * @author bo 2013.07.16 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("initiator-config") +public class InitiatorDependenciesProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public InitiatorDependencies process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // locate the definition of an '-initiator' element and attempt to parse it + XmlElement xmlInitiator = null; + + for (Iterator iter = ((List) xmlElement.getElementList()).iterator(); + iter.hasNext() && xmlInitiator == null; ) + { + XmlElement xmlCurrent = iter.next(); + + if (xmlCurrent.getName().endsWith("-initiator")) + { + xmlInitiator = xmlCurrent; + } + } + + // process the initiator to produce the dependencies + Object oInitiatorDependencies = xmlInitiator == null + ? new DefaultTcpInitiatorDependencies() + : context.processElement(xmlInitiator); + + if (oInitiatorDependencies instanceof InitiatorDependencies) + { + InitiatorDependencies dependencies = (InitiatorDependencies) oInitiatorDependencies; + + // now inject properties from this (parent) element into the + // initiator dependencies. this allows the dependencies to + // "inherit" common properties from this element + context.inject(dependencies, xmlElement); + + return dependencies; + } + else + { + throw new ConfigurationException("The specified element [" + xmlElement + + "] does not produce an InitiatorDependencies", "Please ensure that the initiator produces an InitiatorDependencies implementation."); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InstanceProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InstanceProcessor.java new file mode 100644 index 0000000000000..f7f94976e9292 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InstanceProcessor.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.InstanceBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.StaticFactoryInstanceBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + + + + +import com.tangosol.run.xml.QualifiedName; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.util.Base; + +import java.util.List; + + +/** + * An {@link InstanceProcessor} is responsible for processing {@literal } {@link XmlElement}s + * to produce {@link ParameterizedBuilder}s. + * + * @author bo 2011.09.28 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("instance") +public class InstanceProcessor + implements ElementProcessor> + { + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public ParameterizedBuilder process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + ParameterizedBuilder builder; + + // when there's a single element in an and it is different from the + // Coherence namespace, then assume we're using a custom-namespace that will return + // a ParameterizedBuilder; this permits use of foreign namespaces in elements + // that produce custom ParameterizedBuilders. + if (isForeignNamespace(element)) + { + // process the custom-namespace element (it should return a ParameterizedBuilder) + Object oBuilder = context.processOnlyElementOf(element); + + // ensure we have a ParameterizedBuilder + if (oBuilder instanceof ParameterizedBuilder) + { + builder = (ParameterizedBuilder) oBuilder; + } + else + { + throw new ConfigurationException(String + .format( + "The element [%s] can't be used within a <%s> as the associated namespace implementation " + + "doesn't return a ParameterizedBuilder.", element.getElementList().get(0), element + .getName()), "Please ensure that the namespace handler for the element constructs " + + "a ParameterizedBuilder for the element."); + } + } + else + { + // determine if we need to use a static factory or regular class-scheme + if (element.getElement(new QualifiedName(element.getQualifiedName(), "class-factory-name").getName()) + == null) + { + // configure a regular InstanceBuilder based on the declaration + builder = context.inject(new InstanceBuilder(), element); + } + else + { + // configure a StaticFactoryInstanceBuilder based on the declaration + builder = context.inject(new StaticFactoryInstanceBuilder(), element); + } + } + + return builder; + } + + /** + * Return true if the given element meets the following criteria: + *
    + *
  1. there is a single sub element
  2. + *
  3. the namespace of the sub element is not in the Coherence namespace
  4. + *
+ * @param element the {@literal } element to inspect + * + * @return true if the given element is in a foreign namespace + */ + protected boolean isForeignNamespace(XmlElement element) + { + List listElement = element.getElementList(); + + return (listElement.size() == 1) + && !(Base.equals(((XmlElement) listElement.get(0)).getQualifiedName().getPrefix(), + element.getQualifiedName().getPrefix())); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InterceptorProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InterceptorProcessor.java new file mode 100644 index 0000000000000..597727b940434 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InterceptorProcessor.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.XmlSimpleName; + +/** + * A {@link ElementProcessor} for the <interceptor> element. + * + * @author pfm 2012.11.05 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("interceptor") +public class InterceptorProcessor + extends CustomizableBuilderProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an InterceptorProcessor. + */ + public InterceptorProcessor() + { + super (NamedEventInterceptorBuilder.class); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InterceptorsProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InterceptorsProcessor.java new file mode 100644 index 0000000000000..df2a07a64e552 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InterceptorsProcessor.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.NamedEventInterceptorBuilder; +import com.tangosol.coherence.config.xml.preprocessor.SchemeRefPreprocessor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlValue; + +import com.tangosol.util.Base; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A {@link ElementProcessor} for the <interceptors> element. + * + * @author bo 2012.10.31 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("interceptors") +public class InterceptorsProcessor + implements ElementProcessor> + { + /** + * {@inheritDoc} + */ + @Override + public List process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // process all of the NamedEventInterceptorBuilders + Map mapBuilders = context.processElementsOf(xmlElement); + + List listBuilders = new ArrayList<>(mapBuilders.size()); + + listBuilders.addAll((Collection) mapBuilders.values()); + + return listBuilders; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InternalCacheSchemeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InternalCacheSchemeProcessor.java new file mode 100755 index 0000000000000..081940a6fdd6b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InternalCacheSchemeProcessor.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.MapBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.scheme.CachingScheme; +import com.tangosol.coherence.config.scheme.CustomScheme; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import java.util.Map; + +/** + * A {@link InternalCacheSchemeProcessor} is responsible for processing an + * internal-cache-scheme {@link XmlElement}s to produce a {@link MapBuilder}. + * + * @author pfm 2011.12.02 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("internal-cache-scheme") +public class InternalCacheSchemeProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public MapBuilder process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + Object oProcessed = context.processOnlyElementOf(element); + + if (oProcessed instanceof CachingScheme) + { + return ((CachingScheme) oProcessed); + } + else if (oProcessed instanceof MapBuilder) + { + return (MapBuilder) oProcessed; + } + else if (oProcessed instanceof ParameterizedBuilder) + { + return new CustomScheme((ParameterizedBuilder) oProcessed); + } + else + + { + throw new ConfigurationException("The is not a CachingScheme or MapBuilder", + "Please ensure that the configured scheme is appropriate for the "); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InvocationServiceProxyProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InvocationServiceProxyProcessor.java new file mode 100644 index 0000000000000..ea4ace5d2273c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/InvocationServiceProxyProcessor.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.extend.proxy.DefaultInvocationServiceProxyDependencies; +import com.tangosol.net.InvocationService; +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link com.tangosol.config.xml.ElementProcessor} that will parse invocation-service-proxy configuration + * element tp produce a {@link DefaultInvocationServiceProxyDependencies} object. + * + * @author pfm 2013.08.22 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("invocation-service-proxy") +public class InvocationServiceProxyProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public DefaultInvocationServiceProxyDependencies process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + DefaultInvocationServiceProxyDependencies deps = new DefaultInvocationServiceProxyDependencies(); + context.inject(deps, xmlElement); + + // assume a custom builder has been provided + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr != null) + { + try + { + deps.setServiceBuilder((ParameterizedBuilder) bldr); + } + catch (Exception e) + { + throw new ConfigurationException("Expected a ParameterizedBuilder, but found [" + + bldr + "] after parsing [" + xmlElement + + "]", "Please specify a class that is a InvocationService", e); + } + } + + return deps; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/KeystoreProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/KeystoreProcessor.java new file mode 100644 index 0000000000000..cb792910bec10 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/KeystoreProcessor.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.SSLSocketProviderDependenciesBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} that will parse and produce a + * DefaultKeystoreDependencies based on a key-store configuration element. + * + * @author jf 2015.11.11 + * @since Coherence 12.2.1.1 + */ +@XmlSimpleName("key-store") +public class KeystoreProcessor implements ElementProcessor + { + @Override + public SSLSocketProviderDependenciesBuilder.DefaultKeystoreDependencies process(ProcessingContext context, XmlElement xmlElement) throws ConfigurationException + { + SSLSocketProviderDependenciesBuilder.DefaultKeystoreDependencies deps = new SSLSocketProviderDependenciesBuilder.DefaultKeystoreDependencies(); + context.inject(deps, xmlElement); + return deps; + } + } + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/LeaseGranularityProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/LeaseGranularityProcessor.java new file mode 100644 index 0000000000000..e7997810a039e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/LeaseGranularityProcessor.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.grid.LeaseConfig; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link com.tangosol.config.xml.ElementProcessor} for Coherence <lease-granularity> + * configuration + * + * @author bo 2013.04.01 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("lease-granularity") +public class LeaseGranularityProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public Integer process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + String sLeaseGranularity = xmlElement.getString("").trim().toLowerCase(); + + if (sLeaseGranularity.equals("member")) + { + return LeaseConfig.LEASE_BY_MEMBER; + } + else if (sLeaseGranularity.equals("thread")) + { + return LeaseConfig.LEASE_BY_THREAD; + } + else + { + throw new ConfigurationException("Invalid <" + xmlElement.getName() + "> value of [" + sLeaseGranularity + + "]", "Please specify either 'member' or 'thread'."); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/LocalAddressProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/LocalAddressProcessor.java new file mode 100644 index 0000000000000..f330464a503f6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/LocalAddressProcessor.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.LegacyXmlConfigHelper; + +import com.tangosol.run.xml.XmlElement; + +import java.net.InetSocketAddress; + +/** + * An {@link ElementProcessor} that parses a <local-address> to produce + * an {@link InetSocketAddress}. + * + * @author bo 2013.07.16 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("local-address") +public class LocalAddressProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public InetSocketAddress process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // TODO: refactor this to remove the need to use the legacy helper clas + return LegacyXmlConfigHelper.parseLocalSocketAddress(xmlElement); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MapListenerProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MapListenerProcessor.java new file mode 100755 index 0000000000000..cc3cd4bafa0d8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MapListenerProcessor.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.MapListener; + +/** + * A {@link MapListenerProcessor} is responsible for processing a listener + * {@link XmlElement}s to produce a {@link ParameterizedBuilder} for a + * {@link MapListener}. + * + * @author pfm 2011.12.02 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("listener") +public class MapListenerProcessor + implements ElementProcessor> + { + /** + * {@inheritDoc} + */ + @Override + public ParameterizedBuilder process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // fetch the builder defined in the "listener" element + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, element); + + return (ParameterizedBuilder) bldr; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MemberListenerProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MemberListenerProcessor.java new file mode 100644 index 0000000000000..23d575f591dcb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MemberListenerProcessor.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.net.MemberListener; + +import com.tangosol.run.xml.XmlElement; + +import java.util.ArrayList; +import java.util.List; + +/** + * An ElementProcessor to process a <member-listener> to produce a + * List containing a single MemberListener. + * + * @author bo 2013.03.08 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("member-listener") +public class MemberListenerProcessor + extends AbstractEmptyElementProcessor>> + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link MemberListenerProcessor}. + */ + public MemberListenerProcessor() + { + super(EmptyElementBehavior.IGNORE); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + /** + * {@inheritDoc} + */ + @Override + public List> onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // attempt to locate a ParameterizedBuilder + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr instanceof ParameterizedBuilder) + { + try + { + ArrayList> listBuilders = new ArrayList>(); + listBuilders.add((ParameterizedBuilder)bldr); + return listBuilders; + } + catch (Exception e) + + { + throw new ConfigurationException("Invalid <" + xmlElement.getName() + + "> declaration. The specified builder doesn't produce a MemberListener in [" + xmlElement + + "]", "Please specify a <" + xmlElement.getName() + ">", e); + } + } + else + { + throw new ConfigurationException("Invalid <" + xmlElement.getName() + "> declaration in [" + xmlElement + + "]", "Please specify a <" + xmlElement.getName() + + "> that will produce a MemberListener"); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MemorySizeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MemorySizeProcessor.java new file mode 100644 index 0000000000000..9361882f81aa2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MemorySizeProcessor.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Base; + +/** + * A {@link MemorySizeProcessor} is responsible for processing Coherence + * memory sizes and returning them in bytes. + * + * @author bo 2013.07.02 + * @since Coherence 12.1.3 + */ +public class MemorySizeProcessor + extends AbstractEmptyElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link MemorySizeProcessor}. + */ + public MemorySizeProcessor() + { + super(EmptyElementBehavior.IGNORE); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Integer onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + return (int) Base.parseMemorySize(xmlElement.getString()); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MessageDeliveryModeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MessageDeliveryModeProcessor.java new file mode 100644 index 0000000000000..f723a200f1064 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MessageDeliveryModeProcessor.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import javax.jms.DeliveryMode; + +/** + * An {@link ElementProcessor} for JMS <message-delivery-mode> + * configurations. + * + * @author bo 2013.07.11 + */ +@XmlSimpleName("message-delivery-mode") +public class MessageDeliveryModeProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public Integer process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + String sMessageDeliveryMode = xmlElement.getString().trim(); + + return sMessageDeliveryMode.equalsIgnoreCase("PERSISTENT") + ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MillisProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MillisProcessor.java new file mode 100644 index 0000000000000..8bb5f4dd29983 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/MillisProcessor.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +/** + * A {@link MillisProcessor} is responsible for processing Coherence time values + * and returning them in milliseconds. + * + * @author bo 2013.03.07 + * @since Coherence 12.1.3 + */ +public class MillisProcessor + extends AbstractEmptyElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link MillisProcessor}. + */ + public MillisProcessor() + { + super(EmptyElementBehavior.IGNORE); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Long onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + return XmlHelper.parseTime(xmlElement.getString()); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/OperationBundlingProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/OperationBundlingProcessor.java new file mode 100755 index 0000000000000..6b46e314db765 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/OperationBundlingProcessor.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.scheme.BundleManager; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + + +import com.tangosol.run.xml.XmlElement; + +import java.util.List; + +/** + * A {@link OperationBundlingProcessor} is responsible for processing an + * operation-bundling {@link XmlElement} to produce an {@link BundleManager}. + * + * @author pfm 2011.12.02 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("operation-bundling") +public class OperationBundlingProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public BundleManager process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + BundleManager manager = new BundleManager(); + + // for each bundle-config child, create and populate a BundleConfig + // and add it to the manager + List xmlChildren = element.getElementList(); + for (XmlElement xmlChild : xmlChildren) + { + if (xmlChild.getName().equals("bundle-config")) + { + BundleManager.BundleConfig config = new BundleManager.BundleConfig(); + context.inject(config, xmlChild); + manager.addConfig(config); + } + } + + return manager; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PagedTopicSchemeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PagedTopicSchemeProcessor.java new file mode 100644 index 0000000000000..09d036169c2e1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PagedTopicSchemeProcessor.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.scheme.BackingMapScheme; +import com.tangosol.coherence.config.scheme.CachingScheme; +import com.tangosol.coherence.config.scheme.PagedTopicScheme; +import com.tangosol.coherence.config.scheme.PagedTopicStorageScheme; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.DefaultServiceDependencies; +import com.tangosol.internal.net.service.grid.DefaultPartitionedCacheDependencies; +import com.tangosol.internal.net.service.grid.PartitionedCacheDependencies; + +import com.tangosol.io.DefaultSerializer; +import com.tangosol.io.Serializer; +import com.tangosol.io.SerializerFactory; +import com.tangosol.io.pof.PofContext; +import com.tangosol.io.pof.SafeConfigurablePofContext; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.ExternalizableHelper; + +/** + * An {@link ElementProcessor} that parses a <paged-topic-scheme> element; + * produces a {@link PagedTopicScheme}. + * + * @author jk 2015.05.28 + * @since Coherence 14.1.1 + */ +@XmlSimpleName("paged-topic-scheme") +public class PagedTopicSchemeProcessor + extends ServiceBuilderProcessor + { + // ----- constructors --------------------------------------------------- + + public PagedTopicSchemeProcessor() + { + super(PagedTopicScheme.class); + } + + // ----- ServiceBuilderProcessor methods -------------------------------- + + @Override + public PagedTopicScheme process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + PagedTopicScheme scheme = super.process(context, element); + PartitionedCacheDependencies deps = new DefaultPartitionedCacheDependencies(scheme.getServiceDependencies()); + final SerializerFactory factory = deps.getSerializerFactory(); + SerializerFactory factoryPof = new SerializerFactory() + { + @Override + public Serializer createSerializer(ClassLoader loader) + { + Serializer serializer = factory == null + ? ExternalizableHelper.ensureSerializer(loader) + : factory.createSerializer(loader); + + return serializer instanceof PofContext + ? serializer + : new SafeConfigurablePofContext(serializer, loader); + } + }; + + // Ensure POF serializer since topic data model and processors are EvolvablePortableObject. + // Application payload published to topic is serialized using serializer specified in cache configuration. + ((DefaultServiceDependencies) deps).setSerializerFactory(factoryPof); + scheme.setServiceDependencies(deps); + + CachingScheme schemeStorage = scheme.getStorageScheme(context.getDefaultParameterResolver()); + BackingMapScheme backingMapScheme = new BackingMapScheme(); + + backingMapScheme.setStorageAccessAuthorizer(scheme.getStorageAccessAuthorizer()); + backingMapScheme.setTransient(scheme.getTransientExpression()); + + backingMapScheme.setInnerScheme(new PagedTopicStorageScheme(schemeStorage, scheme)); + + scheme.setBackingMapScheme(backingMapScheme); + + return scheme; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ParamTypeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ParamTypeProcessor.java new file mode 100644 index 0000000000000..1d0cfab53a6c0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ParamTypeProcessor.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.ClassHelper; + +/** + * A {@link ParamTypeProcessor} is responsible for processing <param-type> {@link XmlElement}s to produce a + * fully qualified class name. + * + * @author bo 2011.06.24 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("param-type") +public class ParamTypeProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public String process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + String sType = element.getString(); + + return ClassHelper.getFullyQualifiedClassNameOf(sType); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PartitionAssignmentStrategyProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PartitionAssignmentStrategyProcessor.java new file mode 100644 index 0000000000000..d9ae2199503c0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PartitionAssignmentStrategyProcessor.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.coherence.config.builder.PartitionAssignmentStrategyBuilder; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.run.xml.XmlElement; + +/** + * An ElementProcessor to process a <partition-assignment-strategy> to + * produce a PartitionAssignmentStrategy. + * + * @author bo 2013.03.12 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("partition-assignment-strategy") +public class PartitionAssignmentStrategyProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public PartitionAssignmentStrategyBuilder process(ProcessingContext context, XmlElement xmlElement) + { + // attempt to locate a ParameterizedBuilder + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + return bldr instanceof ParameterizedBuilder ? + new PartitionAssignmentStrategyBuilder(bldr, xmlElement) : + new PartitionAssignmentStrategyBuilder(xmlElement.getString(), xmlElement); + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PartitionListenerProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PartitionListenerProcessor.java new file mode 100644 index 0000000000000..57b8ca3077e7c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PartitionListenerProcessor.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.net.partition.PartitionListener; + +import com.tangosol.run.xml.XmlElement; + +import java.util.ArrayList; +import java.util.List; + +/** + * An ElementProcessor to process a <partition-listener> to produce a + * List containing a single PartitionListener. + * + * @author bo 2013.03.12 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("partition-listener") +public class PartitionListenerProcessor + extends AbstractEmptyElementProcessor>> + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link PartitionListenerProcessor}. + */ + public PartitionListenerProcessor() + { + super(EmptyElementBehavior.IGNORE); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + /** + * {@inheritDoc} + */ + @Override + public List> onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // attempt to locate a ParameterizedBuilder + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr instanceof ParameterizedBuilder) + { + ArrayList> listBuildersListener = + new ArrayList>(); + + listBuildersListener.add((ParameterizedBuilder) bldr); + + return listBuildersListener; + } + else + { + throw new ConfigurationException("Invalid <" + xmlElement.getName() + "> declaration in [" + xmlElement + + "]", "Please specify a <" + xmlElement.getName() + + "> that will produce a PartitionListener"); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PartitionedQuorumPolicyProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PartitionedQuorumPolicyProcessor.java new file mode 100644 index 0000000000000..79f3b045877c2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PartitionedQuorumPolicyProcessor.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ActionPolicyBuilder; +import com.tangosol.coherence.config.builder.AddressProviderBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.PartitionedCacheQuorumPolicyBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.net.ActionPolicy; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +import static com.tangosol.net.ConfigurableQuorumPolicy.PartitionedCacheQuorumPolicy; + +/** + * An ElementProcessor that will parse a <partitioned-quorum-policy-scheme> + * and produce a suitable {@link ActionPolicy} + * + * @author bo 2013.03.08 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("partitioned-quorum-policy-scheme") +public class PartitionedQuorumPolicyProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public ActionPolicyBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // attempt to locate a ParameterizedBuilder + ParameterizedBuilder builderCustom = (ParameterizedBuilder) + ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (builderCustom == null) + { + XmlElement xmlRecoveryHosts = xmlElement.getSafeElement("recovery-hosts"); + + AddressProviderBuilder bldrRecoveryHosts = xmlRecoveryHosts.isEmpty() ? null : + new AddressProviderBuilderProcessor().process(context, + xmlRecoveryHosts); + + PartitionedCacheQuorumPolicyBuilder builder = + new PartitionedCacheQuorumPolicyBuilder(bldrRecoveryHosts, xmlElement); + + for (PartitionedCacheQuorumPolicy.ActionRule action: PartitionedCacheQuorumPolicy.ActionRule.values()) + { + int nThreshold = 0; + float flThresholdPct = 0.0f; + if (action.getMask() == PartitionedCacheQuorumPolicy.MASK_RECOVER) + { + String sElementName = action.getElementName(); + String sThreshold = context.getOptionalProperty(sElementName, String.class, "0", xmlElement); + int ofPct = sThreshold.indexOf("%"); + if (ofPct >= 0) + { + try + { + flThresholdPct = Base.parsePercentage(sThreshold); + } + catch (IllegalArgumentException e) + { + throw new ConfigurationException("The partitioned-quorum-policy-scheme/" + + sElementName + " value is not valid: " + sThreshold, + "Please ensure that the value is a non-nagative integer with or without a % <" + sElementName + ">"); + } + } + else + { + nThreshold = Integer.parseInt(sThreshold); + } + } + else + { + nThreshold = context.getOptionalProperty(action.getElementName(), Integer.class, 0, xmlElement); + } + + builder.addQuorumRule(action.getElementName(), action.getMask(), nThreshold, flThresholdPct); + } + + return builder; + } + else + { + return new ActionPolicyBuilder.ActionPolicyParameterizedBuilder(builderCustom); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PasswordProviderBuilderProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PasswordProviderBuilderProcessor.java new file mode 100644 index 0000000000000..9a130dde65b4e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PasswordProviderBuilderProcessor.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ParameterMacroExpression; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.builder.InstanceBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.coherence.config.builder.ParameterizedBuilderRegistry; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.net.PasswordProvider; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.util.Base; + +import java.util.Arrays; + +/** + * An {@link ElementProcessor} for <password-provider> elements defined by + * a Coherence Operational Configuration file. + * + * @author spuneet + * @since Coherence 12.12.1.4 + */ +@XmlSimpleName("password-provider") +public class PasswordProviderBuilderProcessor + extends AbstractEmptyElementProcessor> + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link PasswordProviderBuilderProcessor}. + */ + public PasswordProviderBuilderProcessor() + { + super(EmptyElementBehavior.IGNORE); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + @Override + public ParameterizedBuilder onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // assume the contains a builder definition + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr == null) + { + ParameterizedBuilderRegistry registry = context.getCookie(ParameterizedBuilderRegistry.class); + + // Lookup builderRegistry using the name supplied. + String name = getNameFromXML(xmlElement); + Base.azzert(name != null , "valid-id is missing/empty. Failed to lookup a builder for PasswordProvider"); + + ParameterizedBuilder bldrFound = registry.getBuilder(PasswordProvider.class, name); + + if (bldrFound != null && bldrFound instanceof InstanceBuilder) + { + bldr = newPasswordProviderBuilderFromExisitng(context, xmlElement, bldrFound); + } + } + + if (bldr == null) + { + // Either a valid is not defined + // Or provider-id doesn't have a valid name to look up the builder. + throw new ConfigurationException(" fails to correctly define a PasswordProvider implementation: " + + xmlElement, "Please define a valid "); + } + + return (ParameterizedBuilder) bldr; + } + + /** + * Returns "null" if "name" element doesn't exist, else trimmed value of the name. + * + * @param xmlElement + * @return sName + */ + private String getNameFromXML (XmlElement xmlElement) + { + Object sName = xmlElement.getElement("name").getValue(); //luk could be null? + return null == sName ? null : sName.toString().trim(); + } + + /* + * Returns a ParameterizedBuilder by copying the contents of an existing builder. + * - Fetches constructor params of mapped builder + * - Get a list of override params and overide in ResolvableParameterList. + * - If a param-name exists, its param-value will be overwritten. + * - If a param-name doesn't exists, then its added to the list after the default listConstructorParameters. + * - For additional params, the class-name provided should have a supporting/relevant constructor. + * Else it will fail at runtime to realize. + * - Class-name is used from existing builder + * + * returns ParameterizedBuilder + */ + private ParameterizedBuilder newPasswordProviderBuilderFromExisitng(ProcessingContext context, + XmlElement xmlElement, + ParameterizedBuilder bldrFound) + { + InstanceBuilder bldr = (InstanceBuilder)bldrFound; + + ParameterList listConstructorParams = bldr.getConstructorParameterList(); + ResolvableParameterList resolvableParameterList = + listConstructorParams instanceof ResolvableParameterList ? + (ResolvableParameterList) listConstructorParams : + new ResolvableParameterList(listConstructorParams); + + // Get the Overriding params from input override-XML + XmlElement element = xmlElement.getSafeElement("init-params"); + ResolvableParameterList listOverrideParams = (ResolvableParameterList) context.processElement(element); + + // Add / Update params to the list + for (Parameter parameter : listOverrideParams) + { + resolvableParameterList.add(parameter); + } + + InstanceBuilder bldrOverridden = new InstanceBuilder(); + bldrOverridden.setClassName(bldr.getClassName()); + bldrOverridden.setConstructorParameterList(resolvableParameterList); + return bldrOverridden; + } + + /** + * Generates a ParameterizedBuilder using the DefaultPasswordProvider.class + * by passing the string-input to its single-param constructor. + * + * @param password the clear text password + * + * @return ParameterizedBuilder of PsswordProvider + */ + public static ParameterizedBuilder getPasswordProviderBuilderForPasswordStr(String password) + { + Expression expr = + new ParameterMacroExpression(DefaultPasswordProvider.class.getName(), String.class); + + ResolvableParameterList resolvableParameterList = new ResolvableParameterList(); + if (null != password) + { + resolvableParameterList.add(new Parameter("param1", password)); + } + + InstanceBuilder bldr = new InstanceBuilder(); + bldr.setClassName(expr); + bldr.setConstructorParameterList(resolvableParameterList); + return bldr; + } + + /** + * Rerturns a builder for null password values. + * + * @return ParameterizedBuilder of PasswordProvider + */ + public static ParameterizedBuilder getNullPasswordProviderBuilder() + { + return getPasswordProviderBuilderForPasswordStr(null); + } + + /** + * This class is to wrap the existing password into the password-provider approach. + * The single-arg constructor will accept the {@link String} password and store it as a char[], + * to be returned when the "get()", implemented as part of {@link PasswordProvider}, is called. + * + * @author spuneet 2017.08.18 + * @since Coherence 12.2.1.4 + */ + public static class DefaultPasswordProvider + implements PasswordProvider + { + /* + * Default constructor, set the password to null. + */ + public DefaultPasswordProvider() + { + this(null); + } + + /** + * Constructor sets the password-string as a char[] when "get()" is called. + * + * @param pass the clear text password + */ + public DefaultPasswordProvider(String pass) + { + f_achPass = (null == pass ? null : pass.toCharArray()); + } + + /** + * Returns the password. + * + * @return a copy of password char[]. The consumer can zero the char[] after usage; + * and/but may call PasswordProvider#get if it requires the password again. + */ + @Override + public char[] get() + { + return null == f_achPass + ? null + : Arrays.copyOf(f_achPass, f_achPass.length); + } + + // char[] to store the password + private final char[] f_achPass; + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PasswordProvidersProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PasswordProvidersProcessor.java new file mode 100644 index 0000000000000..8a340e699ce70 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PasswordProvidersProcessor.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilderRegistry; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.net.PasswordProvider; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.util.Base; + +import java.util.Map; + +/** + * A {@link PasswordProvidersProcessor} is responsible for processing <password-providers> + * {@link XmlElement} of Coherence Operational Configuration files + * + * @author spuneet + * @since Coherence 12.2.1.4 + */ +@XmlSimpleName("password-providers") +public class PasswordProvidersProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public Void process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // add all of the ParameterizedBuilders to the ParameterizedBuilderRegistry + ParameterizedBuilderRegistry registry = context.getCookie(ParameterizedBuilderRegistry.class); + Base.azzert(registry != null); + + Map mapProcessedChildren = context.processElementsOf(element); + + for (Map.Entry entry : mapProcessedChildren.entrySet()) + { + String sName = entry.getKey(); + Object oBuilder = entry.getValue(); + + if (oBuilder instanceof ParameterizedBuilder) + { + ParameterizedBuilder builder = (ParameterizedBuilder) oBuilder; + + registry.registerBuilder(PasswordProvider.class, sName, + (ParameterizedBuilder) builder); + } + else + { + throw new ConfigurationException("The specified [" + sName + + "] is not a ParameterizedBuilder", + "Use element to specify a PasswordProvider implementation"); + } + } + + return null; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PersistenceEnvironmentsProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PersistenceEnvironmentsProcessor.java new file mode 100644 index 0000000000000..e992f86ac09a2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PersistenceEnvironmentsProcessor.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.oracle.coherence.persistence.PersistenceEnvironment; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilderRegistry; +import com.tangosol.coherence.config.builder.PersistenceEnvironmentParamBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.util.Base; + +import java.util.Map; + +/** + * An {@link ElementProcessor} for the <persistence-environments%gt; element of + * Coherence Operational Configuration files. + * + * @author jf 2015.03.03 + * @since Coherence 12.2.1 + */ +@XmlSimpleName("persistence-environments") +public class PersistenceEnvironmentsProcessor + implements ElementProcessor + { + // ----- ElementProcessor methods --------------------------------------- + + @Override + public Void process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // process the children of the + Map mapProcessedChildren = context.processElementsOf(xmlElement); + + // add all of the ParameterizedBuilders to the ParameterizedBuilderRegistry + ParameterizedBuilderRegistry registry = context.getCookie(ParameterizedBuilderRegistry.class); + + assert registry != null; + + for (Map.Entry entry : mapProcessedChildren.entrySet()) + { + String sName = entry.getKey(); + Object oBuilder = entry.getValue(); + + if (oBuilder instanceof ParameterizedBuilder) + { + @SuppressWarnings("unchecked") + ParameterizedBuilder builder = + (ParameterizedBuilder) oBuilder; + + registry.registerBuilder(PersistenceEnvironment.class, sName, builder); + } + else + { + throw new ConfigurationException("The specified [" + sName + + "] is not a ParameterizedBuilder", + "Use element to specify a ParameterizedBuilder implementation"); + + } + } + + return null; + } + + // ----- inner class: PersistenceEnvironmentProcessor ------------------- + + /** + * An {@link com.tangosol.config.xml.ElementProcessor} for children elements + * of <persistence-environments%gt; element of Coherence Operational + * Configuration files. Produces a {@link PersistenceEnvironmentParamBuilder}. + */ + @XmlSimpleName("persistence-environment") + public static class PersistenceEnvironmentProcessor + extends AbstractEmptyElementProcessor + { + @Override + protected PersistenceEnvironmentParamBuilder onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + return context.inject(new PersistenceEnvironmentParamBuilder(), xmlElement); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PersistenceProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PersistenceProcessor.java new file mode 100644 index 0000000000000..a75b57dd395ac --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/PersistenceProcessor.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.oracle.coherence.persistence.PersistenceEnvironment; + +import com.tangosol.coherence.config.Config; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.PersistenceEnvironmentParamBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.grid.DefaultPersistenceDependencies; +import com.tangosol.internal.net.service.grid.PersistenceDependencies; + +import com.tangosol.net.OperationalContext; + +import com.tangosol.persistence.SnapshotArchiverFactory; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} that will parse a <persistence> element to + * produce a {@link PersistenceDependencies} instance. + * + * @author bo 2013.03.12 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("persistence") +public class PersistenceProcessor + extends AbstractEmptyElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link PersistenceProcessor}. + */ + public PersistenceProcessor() + { + super(EmptyElementBehavior.PROCESS); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + @Override + public PersistenceDependencies onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + DefaultPersistenceDependencies depsPersistence = new DefaultPersistenceDependencies(); + OperationalContext ctxOp = context.getCookie(OperationalContext.class); + + // process the + { + XmlElement xmlEnvironment = xmlElement.getSafeElement("environment"); + + String sMode = Config.getProperty("coherence.distributed.persistence.mode", () -> + Config.getProperty("coherence.distributed.persistence-mode", + "on-demand")); + String sEnvName = xmlEnvironment.isEmpty() + ? "default-" + sMode + : xmlEnvironment.getString(); + + PersistenceEnvironmentParamBuilder bldr = (PersistenceEnvironmentParamBuilder) + ctxOp.getBuilderRegistry().getBuilder(PersistenceEnvironment.class, sEnvName); + + if (bldr == null) + { + throw new IllegalArgumentException("Persistence environment (\"" + sEnvName + + "\") must be defined in the operational config"); + } + + depsPersistence.setPersistenceMode(bldr.getPersistenceMode()); + depsPersistence.setPersistenceEnvironmentBuilder((ParameterizedBuilder) bldr); + } + + // process the + { + String sArchiver = xmlElement.getSafeElement("archiver").getString(); + if (!sArchiver.isEmpty()) + { + // + SnapshotArchiverFactory factory = ctxOp.getSnapshotArchiverMap().get(sArchiver); + + if (factory == null) + { + throw new IllegalArgumentException("Snapshot archiver (\"" + sArchiver + + "\") must be defined in the operational configuration"); + } + + depsPersistence.setArchiverFactory(factory); + } + } + + // process the persistence exception handling + String sFailureMode = xmlElement.getSafeElement("active-failure-mode").getString("stop-service"); + switch (sFailureMode) + { + case "stop-service": + depsPersistence.setFailureMode(PersistenceDependencies.FAILURE_STOP_SERVICE); + break; + case "stop-persistence": + depsPersistence.setFailureMode(PersistenceDependencies.FAILURE_STOP_PERSISTENCE); + break; + default: + throw new IllegalArgumentException("Unknown persistence active-failure-mode: " + sFailureMode); + } + + return depsPersistence; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ProviderProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ProviderProcessor.java new file mode 100644 index 0000000000000..e220616c8c2ee --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ProviderProcessor.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.SSLSocketProviderDependenciesBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.run.xml.XmlElement; +import java.security.Provider; + +/** + * An {@link ElementProcessor} that will parse and produce a + * ProviderBuilder based on a provider configuration element. + * + * @author jf 2015.11.11 + * @since Coherence 12.2.1.1 + */ +@XmlSimpleName("provider") +public class ProviderProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public SSLSocketProviderDependenciesBuilder.ProviderBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + SSLSocketProviderDependenciesBuilder.ProviderBuilder bldr = new SSLSocketProviderDependenciesBuilder.ProviderBuilder(); + context.inject(bldr, xmlElement); + bldr.setBuilder((ParameterizedBuilder)ElementProcessorHelper.processParameterizedBuilder(context, xmlElement)); + return bldr; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ProxyQuorumPolicyProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ProxyQuorumPolicyProcessor.java new file mode 100644 index 0000000000000..176625429c867 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ProxyQuorumPolicyProcessor.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ActionPolicyBuilder; +import com.tangosol.coherence.config.builder.ProxyQuorumPolicyBuilder; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.net.ActionPolicy; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.NullImplementation; + +/** + * An {@link ElementProcessor} that will parse a <proxy-quorum-policy-scheme> + * and produce a suitable {@link ActionPolicy} + * + * @author bo 2013.07.12 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("proxy-quorum-policy-scheme") +public class ProxyQuorumPolicyProcessor + implements ElementProcessor + { + // ----- ElementProcessor methods -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ActionPolicyBuilder process(ProcessingContext context, XmlElement xmlElement) + { + // attempt to locate a ParameterizedBuilder + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr == null) + { + if (xmlElement.getElementList().isEmpty()) + { + return new ActionPolicyBuilder.NullImplementationBuilder(); + } + else + { + int nThreshold = context.getOptionalProperty(ProxyQuorumPolicyBuilder.CONNECT_RULE_NAME, Integer.class, 0, xmlElement); + return new ProxyQuorumPolicyBuilder(nThreshold, xmlElement); + } + } + else + { + ParameterizedBuilder bldrPolicy = + (ParameterizedBuilder) bldr; + + return new ActionPolicyBuilder.ActionPolicyParameterizedBuilder(bldrPolicy); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLHostnameVerifierProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLHostnameVerifierProcessor.java new file mode 100644 index 0000000000000..b76a7fa522b56 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLHostnameVerifierProcessor.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import javax.net.ssl.HostnameVerifier; + +/** + * An {@link ElementProcessor} that will parse and produce a + * {@link HostnameVerifier} based on hostname-verifier configuration element. + * + * @author jf 2015.11.11 + * @since Coherence 12.2.1.1 + */ +@XmlSimpleName("hostname-verifier") +public class SSLHostnameVerifierProcessor + implements ElementProcessor> + { + /** + * {@inheritDoc} + */ + @Override + public ParameterizedBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + if (XmlHelper.hasElement(xmlElement, "hostname-verifier")) + { + xmlElement = xmlElement.getElement("hostname-verifier"); + } + + if (xmlElement == null || XmlHelper.isInstanceConfigEmpty(xmlElement)) + { + return null; + } + + // assume a custom builder has been provided + return (ParameterizedBuilder)ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLManagerProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLManagerProcessor.java new file mode 100644 index 0000000000000..c7421a6c2852b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLManagerProcessor.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.SSLSocketProviderDependenciesBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} that will parse and produce a + * DefaultManagerDependencies based on a identity-manager/trust-manager configuration element. + * + * @author jf 2015.11.11 + * @since Coherence 12.2.1.1 + */ +public class SSLManagerProcessor implements ElementProcessor + { + + @Override + public SSLSocketProviderDependenciesBuilder.DefaultManagerDependencies process(ProcessingContext context, XmlElement xmlElement) throws ConfigurationException + { + String sManagerKind = xmlElement.getQualifiedName().getLocalName(); + SSLSocketProviderDependenciesBuilder.DefaultManagerDependencies deps = new SSLSocketProviderDependenciesBuilder.DefaultManagerDependencies(sManagerKind); + context.inject(deps, xmlElement); + return deps; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLNameListProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLNameListProcessor.java new file mode 100644 index 0000000000000..eb39c7b104ed1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLNameListProcessor.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.SSLSocketProviderDependenciesBuilder.NameListDependencies; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlValue; + +import java.util.List; + +/** + * An {@link ElementProcessor} that will parse and produce a + * NameListDependencies based on a protocol-versions or cipher-suites configuration element. + * + * @author jf 2015.11.11 + * @since Coherence 12.2.1.1 + */ +public class SSLNameListProcessor implements ElementProcessor + { + // ----- constructors ---------------------------------------------------- + + public SSLNameListProcessor(String sName) + { + f_sName = sName; + } + + @Override + public NameListDependencies process(ProcessingContext context, XmlElement xmlElement) throws ConfigurationException + { + NameListDependencies bldr = new NameListDependencies(xmlElement.getQualifiedName().getLocalName()); + XmlValue value = xmlElement.getAttribute("usage"); + if (value != null) + { + bldr.setUsage(value.getString()); + } + + for (XmlElement elementChild : ((List) xmlElement.getElementList())) + { + if (elementChild.getQualifiedName().getLocalName().equals("name")) + { + bldr.add((String) elementChild.getValue()); + } + } + + return bldr; + } + + // ----- constants ------------------------------------------------------- + /** + * xmlElement local name should be this value. + */ + private final String f_sName; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLProcessor.java new file mode 100644 index 0000000000000..3e9bafe4dc598 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SSLProcessor.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.SSLSocketProviderDependenciesBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.internal.net.ssl.SSLSocketProviderDefaultDependencies; +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} that will parse an <ssl> and + * produce a {@link SSLSocketProviderDependenciesBuilder} object. + * + * @author jf 2015.11.11 + * @since Coherence 12.2.1.1 + */ +@XmlSimpleName("ssl") +public class SSLProcessor + implements ElementProcessor + { + // ----- ElementProcessor methods ---------------------------------------- + /** + * {@inheritDoc} + */ + @Override + public SSLSocketProviderDependenciesBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + SSLSocketProviderDefaultDependencies deps = context.getCookie(SSLSocketProviderDefaultDependencies.class); + SSLSocketProviderDependenciesBuilder builder = new SSLSocketProviderDependenciesBuilder(deps); + + context.inject(builder, xmlElement); + + return builder; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SchemesProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SchemesProcessor.java new file mode 100644 index 0000000000000..9747ade4946ab --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SchemesProcessor.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.ServiceSchemeRegistry; + +import com.tangosol.coherence.config.scheme.ServiceScheme; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +import java.util.Map; + +/** + * A {@link SchemesProcessor} is an {@link ElementProcessor} for the + * <caching-schemes%gt; element of Coherence Cache Configuration files. + * + * @author jk 2015.05.28 + * @since Coherence 14.1.1 + */ +@XmlSimpleName("schemes") +public class SchemesProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public ServiceSchemeRegistry process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // process the children of the + Map mapProcessedChildren = context.processElementsOf(xmlElement); + + // add all of the ServiceSchemes to the ServiceSchemeRegistry + CacheConfig cacheConfig = context.getCookie(CacheConfig.class); + Base.azzert(cacheConfig != null); + + ServiceSchemeRegistry registry = cacheConfig.getServiceSchemeRegistry(); + Base.azzert(registry != null); + + for (Object oChild : mapProcessedChildren.values()) + { + if (oChild instanceof ServiceScheme) + { + registry.register((ServiceScheme) oChild); + } + } + + // process all of the foreign elements + // (this allows the elements to modify the configuration) + context.processForeignElementsOf(xmlElement); + + return registry; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ScopeNameProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ScopeNameProcessor.java new file mode 100644 index 0000000000000..2edd680ad26b2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ScopeNameProcessor.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.ResourceRegistry; + +import java.util.List; + +/** + * The {@link ScopeNameProcessor} is responsible for processing the <scope-name> {@link XmlElement} + * in a Coherence Cache Configuration file. + * + * @author bo 2013.12.01 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("scope-name") +public class ScopeNameProcessor + implements ElementProcessor + { + @Override + public String process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + String sScopeName = element.getString(); + + return sScopeName == null ? null : sScopeName.trim(); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SerializerBuilderProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SerializerBuilderProcessor.java new file mode 100644 index 0000000000000..408d13eecfee7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SerializerBuilderProcessor.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.io.Serializer; +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link com.tangosol.config.xml.ElementProcessor} for <serializer> elements defined by + * a Coherence Operational Configuration file. + * + * @author jf 2015.03.03 + * @since Coherence 12.2.1 + */ +@XmlSimpleName("serializer") +public class SerializerBuilderProcessor + extends AbstractEmptyElementProcessor> + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link com.tangosol.coherence.config.xml.processor.SerializerBuilderProcessor}. + */ + public SerializerBuilderProcessor() + { + super(EmptyElementBehavior.IGNORE); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + @Override + public ParameterizedBuilder onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // assume the contains a builder definition + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr == null) + { + throw new ConfigurationException(" fails to correctly define a ParameterizedBuilder implementation: " + + xmlElement, "Please define a "); + } + + return (ParameterizedBuilder) bldr; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SerializerFactoryProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SerializerFactoryProcessor.java new file mode 100644 index 0000000000000..a429066fb8f68 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SerializerFactoryProcessor.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.io.ClassLoaderAware; +import com.tangosol.io.Serializer; +import com.tangosol.io.SerializerFactory; + +import com.tangosol.net.OperationalContext; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.WrapperException; + +/** + * An ElementProcessor that will parse a <serializer> and produce a + * suitable SerializerFactory + * + * @author bo 2013.03.07 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("serializer") +public class SerializerFactoryProcessor + extends AbstractEmptyElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link SerializerFactoryProcessor}. + */ + public SerializerFactoryProcessor() + { + super(EmptyElementBehavior.IGNORE); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + /** + * {@inheritDoc} + */ + @Override + public SerializerFactory onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + SerializerFactory factory; + + // attempt to locate a ParameterizedBuilder + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr == null) + { + // The element is _not_ empty; ie: it contains + // a string value (e.g. pof). + // it must be a named/registered serializer factory, so let's look it up + String sName = xmlElement.getString(); + + // grab the operational context from which we can lookup the serializer + OperationalContext ctxOperational = context.getCookie(OperationalContext.class); + + if (ctxOperational == null) + { + throw new ConfigurationException("Attempted to resolve the OperationalContext in [" + xmlElement + + "] but it was not defined", "The registered ElementHandler for the element is not operating in an OperationalContext"); + } + else + { + // attempt to resolve the serializer + factory = ctxOperational.getSerializerMap().get(sName); + + if (factory == null) + { + throw new IllegalArgumentException("Serializer name \"" + sName + "\" is undefined:\n" + + xmlElement); + } + } + } + else + { + final ParameterResolver resolver = context.getDefaultParameterResolver(); + final ParameterizedBuilder bldrSerializer = (ParameterizedBuilder) bldr; + final XmlElement f_xmlElement = xmlElement; + + // adapt the ParameterizedBuilder into a SerializerFactory + factory = new SerializerFactory() + { + @Override + public Serializer createSerializer(ClassLoader loader) + { + try + { + Serializer serializer = bldrSerializer.realize(resolver, loader, null); + + if (serializer instanceof ClassLoaderAware) + { + ((ClassLoaderAware) serializer).setContextClassLoader(loader); + } + + return serializer; + } + catch (Exception e) + { + throw new ConfigurationException("Expected a ParameterizedBuilder, but found [" + + bldrSerializer + "] after parsing [" + f_xmlElement + + "]", " Please specify the name of a registered " + + "or a ParameterizedBuilder", e); + } + } + + }; + } + + return factory; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SerializersProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SerializersProcessor.java new file mode 100644 index 0000000000000..97777b6716bb0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SerializersProcessor.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilderRegistry; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; +import com.tangosol.io.Serializer; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.util.Base; + +import java.util.Map; + +/** + * An {@link com.tangosol.config.xml.ElementProcessor} for the <serializers%gt; element of + * Coherence Operational Configuration files. + * + * @author jf 2015.03.03 + * @since Coherence 12.2.1 + */ +@XmlSimpleName("serializers") +public class SerializersProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public Void process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // process the children of the + Map mapProcessedChildren = context.processElementsOf(xmlElement); + + // add all of the ParameterizedBuilders to the ParameterizedBuilderRegistry + ParameterizedBuilderRegistry registry = context.getCookie(ParameterizedBuilderRegistry.class); + + Base.azzert(registry != null); + + for (Map.Entry entry : mapProcessedChildren.entrySet()) + { + String sName = entry.getKey(); + Object oBuilder = entry.getValue(); + + if (oBuilder instanceof ParameterizedBuilder) + { + ParameterizedBuilder builder = (ParameterizedBuilder) oBuilder; + + registry.registerBuilder(Serializer.class, sName, + (ParameterizedBuilder) builder); + } + else + { + throw new ConfigurationException("The specified [" + sName + + "] is not a ParameterizedBuilder", "Use element to specify a ParameterizedBuilder implementation"); + + } + } + + return null; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ServiceBuilderProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ServiceBuilderProcessor.java new file mode 100644 index 0000000000000..dcee46719b0c5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ServiceBuilderProcessor.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ServiceBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +/** + * A {@link ServiceBuilderProcessor} is an {@link ElementProcessor} responsible for producing various kinds of + * {@link ServiceBuilder}s. + * + * @author pfm 2011.11.30 + * @since Coherence 12.1.2 + */ +public class ServiceBuilderProcessor + implements ElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link ServiceBuilderProcessor} for the specified {@link Class} of {@link ServiceBuilder}. + * + * @param clzToRealize the class that will be instantiated, injected and returned during processing + */ + public ServiceBuilderProcessor(Class clzToRealize) + { + m_clzToRealize = clzToRealize; + } + + // ----- ElementProcessor methods --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public T process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // Create the ServiceBuilder and set the XML which contains the service + // configuration. The ServiceBuilder.realizeService method will pass that + // XML to SafeCluster.ensureService. In some future release, the ServiceBuilder + // will have its properties injected by CODI, like the scheme builders. At + // that time, the XML will no longer be needed. + T bldr = instantiate(); + + //TODO: remove the following line once dependencies are injectable (remove in 12.2.1) + bldr.setXml(element); + + // Inject all annotated properties into the ServiceBuilder. Only a few of + // the service properties are annotated, the rest are in the form of XML as + // previously stated. + context.inject(bldr, element); + + return bldr; + } + + // ----- ServiceBuilderProcessor methods -------------------------------- + + /** + * Instantiate the required class to inject and return. + * + * @return object to be injected + */ + protected T instantiate() + { + try + { + return m_clzToRealize.newInstance(); + } + catch (InstantiationException e) + { + throw Base.ensureRuntimeException(e, "Failed to instantiate " + m_clzToRealize + + ". Please ensure it has a public no args constructor."); + } + catch (IllegalAccessException e) + { + throw Base.ensureRuntimeException(e, "Failed to instantiate " + m_clzToRealize + + ". Please ensure it has a public no args constructor."); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Class} to create, inject (from the {@link XmlElement} being processed) and return. + */ + private Class m_clzToRealize; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ServiceFailurePolicyProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ServiceFailurePolicyProcessor.java new file mode 100644 index 0000000000000..c26bd8872d8f7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ServiceFailurePolicyProcessor.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.ServiceFailurePolicyBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.net.ServiceFailurePolicy; + +import com.tangosol.run.xml.XmlElement; + +/** + * An ElementProcessor that will parse a <service-failure-policyr> and + * produce a suitable ServiceFailurePolicy + * + * @author bo 2013.03.07 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("service-failure-policy") +public class ServiceFailurePolicyProcessor + extends AbstractEmptyElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link ServiceFailurePolicyProcessor}. + */ + public ServiceFailurePolicyProcessor() + { + super(EmptyElementBehavior.IGNORE); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ServiceFailurePolicyBuilder onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // attempt to locate a ParameterizedBuilder + ParameterizedBuilder bldr = + (ParameterizedBuilder) ElementProcessorHelper.processParameterizedBuilder(context, + xmlElement); + + return bldr == null + ? new ServiceFailurePolicyBuilder(xmlElement.getString().trim(), xmlElement) + : new ServiceFailurePolicyBuilder(bldr, xmlElement); + + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ServiceLoadBalancerProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ServiceLoadBalancerProcessor.java new file mode 100644 index 0000000000000..8f585f2e424fa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ServiceLoadBalancerProcessor.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.InvalidConfigServiceLoadBalancerBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.coherence.config.builder.ProxyServiceLoadBalancerBuilder; +import com.tangosol.coherence.config.builder.ServiceLoadBalancerBuilder; +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} for <load-balancer> configuration used + * by federated and proxy services. + * + * @author bb 2014.04.03 + */ +@XmlSimpleName("load-balancer") +public class ServiceLoadBalancerProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public ServiceLoadBalancerBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + String sLoadBalancer = xmlElement.getString().trim(); + + // compute default load balanced service type from context + if (sLoadBalancer == null || sLoadBalancer.length() == 0) + { + String sScheme = xmlElement.getParent().getQualifiedName().getLocalName(); + switch (sScheme) + { + case PROXY_SCHEME: + sLoadBalancer = PROXY; + break; + + default: + sLoadBalancer = "no default load balancer for scheme " + sScheme; + } + } + + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + switch (sLoadBalancer) + { + case PROXY: + return new ProxyServiceLoadBalancerBuilder(bldr, xmlElement); + + case CLIENT: + return null; + + default: + return new InvalidConfigServiceLoadBalancerBuilder(sLoadBalancer, xmlElement); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * Proxy scheme. + */ + public static final String PROXY_SCHEME = "proxy-scheme"; + + /** + * Proxy option for the service load balancer. + */ + public static final String PROXY = "proxy"; + + /** + * Client option for the service load balancer. + */ + public static final String CLIENT = "client"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SocketOptionsProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SocketOptionsProcessor.java new file mode 100644 index 0000000000000..b028b1128eb7c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SocketOptionsProcessor.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.net.SocketOptions; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +import java.net.SocketException; + +import java.util.Map; + +/** + * An {@link ElementProcessor} for {@link SocketOptions}. + * + * @author bo 2013.07.10 + */ +public class SocketOptionsProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public SocketOptions process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + SocketOptions socketOptions = new SocketOptions(); + + try + { + XmlElement xmlOption; + + socketOptions.setOption(SocketOptions.SO_KEEPALIVE, Boolean.TRUE); + socketOptions.setOption(SocketOptions.TCP_NODELAY, Boolean.TRUE); + socketOptions.setOption(SocketOptions.SO_LINGER, Integer.valueOf(0)); + + if ((xmlOption = xmlElement.getElement("reuse-address")) != null) + { + socketOptions.setOption(SocketOptions.SO_REUSEADDR, xmlOption.getBoolean(true)); + } + + if ((xmlOption = xmlElement.getElement("receive-buffer-size")) != null) + { + socketOptions.setOption(SocketOptions.SO_RCVBUF, (int) Base.parseMemorySize(xmlOption.getString())); + } + + if ((xmlOption = xmlElement.getElement("send-buffer-size")) != null) + { + socketOptions.setOption(SocketOptions.SO_SNDBUF, (int) Base.parseMemorySize(xmlOption.getString())); + } + + if ((xmlOption = xmlElement.getElement("timeout")) != null) + { + socketOptions.setOption(SocketOptions.SO_TIMEOUT, (int) Base.parseTime(xmlOption.getString())); + } + + if ((xmlOption = xmlElement.getElement("linger-timeout")) != null) + { + socketOptions.setOption(SocketOptions.SO_LINGER, (int) (Base.parseTime(xmlOption.getString()) / 1000)); + } + + if ((xmlOption = xmlElement.getElement("keep-alive-enabled")) != null) + { + socketOptions.setOption(SocketOptions.SO_KEEPALIVE, xmlOption.getBoolean(true)); + } + + if ((xmlOption = xmlElement.getElement("out-of-band-inline")) != null) + { + socketOptions.setOption(SocketOptions.SO_OOBINLINE, xmlOption.getBoolean(true)); + } + + if ((xmlOption = xmlElement.getElement("tcp-delay-enabled")) != null) + { + socketOptions.setOption(SocketOptions.TCP_NODELAY, !xmlOption.getBoolean(true)); + } + + if ((xmlOption = xmlElement.getElement("traffic-class")) != null) + { + socketOptions.setOption(SocketOptions.IP_TOS, xmlOption.getInt()); + } + + return socketOptions; + } + catch (SocketException e) + { + throw new ConfigurationException("Illegal Socket Option defined in [" + xmlElement + "]", + "Please ensure the Socket Options are valid", e); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SocketProviderProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SocketProviderProcessor.java new file mode 100644 index 0000000000000..bd46471e6fca0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SocketProviderProcessor.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.SSLSocketProviderDependenciesBuilder; +import com.tangosol.coherence.config.builder.SocketProviderBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.cluster.DefaultClusterDependencies; +import com.tangosol.internal.net.ssl.SSLSocketProviderDefaultDependencies; +import com.tangosol.net.OperationalContext; +import com.tangosol.net.SocketProviderFactory; +import com.tangosol.net.TcpDatagramSocketProvider; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; +import com.tangosol.run.xml.XmlValue; + +import java.util.Iterator; + +/** + * An {@link ElementProcessor} that will parse an <socket-provider> and + * produce a {@link SocketProviderBuilder}. + * + * @author bo 2013.07.02 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("socket-provider") +public class SocketProviderProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public SocketProviderBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + String sId = getProviderDefinitionId(xmlElement); + boolean fInlinedProvider = SocketProviderFactory.UNNAMED_PROVIDER_ID.equals(sId); + SocketProviderFactory factory = getSocketProviderFactory(context, xmlElement); + SocketProviderFactory.DefaultDependencies deps = + (SocketProviderFactory.DefaultDependencies) factory.getDependencies(); + + if (XmlHelper.isEmpty(xmlElement)) + { + return new SocketProviderBuilder(null, factory.getDependencies()); + } + + if (fInlinedProvider) + { + // check for reference to a socket-provider definition from . + // The element is _not_ empty; ie: it contains + // a string value (e.g. system). + // it must be a named/registered SocketProvider, so let's look it up + String sName = getProviderIdReference(xmlElement); + + if (sName != null && sName.length() > 0) + { + return new SocketProviderBuilder(sName, factory.getDependencies()); + } + + // inlined, anonymous socket-provider, create an anonymous dependencies + deps = new SocketProviderFactory.DefaultDependencies(); + deps.setSocketProviderFactory(factory); + } + + for (Iterator itr = xmlElement.getElementList().iterator(); itr.hasNext(); ) + { + XmlElement xmlProvider = (XmlElement) itr.next(); + String sType = xmlProvider.getName(); + + if (sType.equals(SocketProviderFactory.Dependencies.ProviderType.SYSTEM.getName())) + { + deps.addNamedProviderType(sId, SocketProviderFactory.Dependencies.ProviderType.SYSTEM); + } + else if (sType.equals(SocketProviderFactory.Dependencies.ProviderType.TCP.getName())) + { + deps.addNamedProviderType(sId, SocketProviderFactory.Dependencies.ProviderType.TCP); + + XmlElement xmlCat = xmlElement.getSafeElement("datagram-socket"); + TcpDatagramSocketProvider.Dependencies depsDatagram = new TcpDatagramSocketProvider.DefaultDependencies(); + + context.inject(depsDatagram, xmlCat); + deps.addNamedTCPDatagramDependencies(sId, depsDatagram); + } + else if (sType.equals(SocketProviderFactory.Dependencies.ProviderType.SSL.getName())) + { + deps.addNamedProviderType(sId, SocketProviderFactory.Dependencies.ProviderType.SSL); + SSLSocketProviderDefaultDependencies depsSSL = new SSLSocketProviderDefaultDependencies(deps); + + context.addCookie(SSLSocketProviderDefaultDependencies.class, depsSSL); + SSLSocketProviderDependenciesBuilder builder = new SSLSocketProviderDependenciesBuilder(depsSSL); + context.inject(builder, xmlProvider); + + deps.addNamedSSLDependenciesBuilder(sId, builder); + + // check if datagram-socket element is present + XmlElement xmlCat = xmlProvider.getElement("datagram-socket"); + + if (xmlCat != null) + { + TcpDatagramSocketProvider.Dependencies dependencies = + new TcpDatagramSocketProvider.DefaultDependencies(); + + context.inject(dependencies, xmlCat); + deps.addNamedTCPDatagramDependencies(sId, dependencies); + } + } + else if (sType.equals(SocketProviderFactory.Dependencies.ProviderType.SDP.getName())) + { + deps.addNamedProviderType(sId, SocketProviderFactory.Dependencies.ProviderType.SDP); + } + else + { + throw new IllegalArgumentException("Unsupported socket provider: " + sType); + } + } + + return new SocketProviderBuilder(sId, deps); + } + + // ----- helpers --------------------------------------------------------- + + /** + * Return the cluster's {@link SocketProviderFactory}. + * @param ctx Cluster operational context + * @param xml socket-provider xml fragment being processed. + * @return the cluster's {@link SocketProviderFactory} + */ + private static SocketProviderFactory getSocketProviderFactory(ProcessingContext ctx, XmlElement xml) + { + // grab the operational context from which we can lookup the socket provider factory + OperationalContext ctxOperational = ctx.getCookie(OperationalContext.class); + + if (ctxOperational == null) + { + DefaultClusterDependencies deps = ctx.getCookie(DefaultClusterDependencies.class); + if (deps == null) + { + throw new ConfigurationException("Attempted to resolve the OperationalContext in [" + xml + + "] but it was not defined", "The registered ElementHandler for the <" + + xml.getName() + + "> element is not operating in an OperationalContext"); + } + return deps.getSocketProviderFactory(); + } + else + { + return ctxOperational.getSocketProviderFactory(); + } + } + + /** + * Get the reference to a ProviderId stored as text on the element. + * + * @param xmlSocketProvider a socket-provider element + * + * @return the provider id reference found in socket-provider element or null if no reference found. + */ + private static String getProviderIdReference(XmlElement xmlSocketProvider) + { + String sRef; + + if (xmlSocketProvider == null) + { + return null; + } + else if (xmlSocketProvider.getElementList().isEmpty()) + { + // socket-provider reference + sRef = xmlSocketProvider.getString(); + + if (sRef.length() == 0) + { + // empty configuration + return null; + } + } + else + { + return null; + } + + return sRef; + } + + /** + * Get the identifier of a SocketProvider definition. + * + * @param xmlSocketProvider a socket provider xml element + * + * @return the socket provider definitions id or {SocketProviderFactory#UNNAMED_PROVIDER_ID} if an unnamed, inlined socket-provider. + */ + private static String getProviderDefinitionId(XmlElement xmlSocketProvider) + { + XmlValue valueIdAttribute = xmlSocketProvider != null ? xmlSocketProvider.getAttribute("id") : null; + return valueIdAttribute == null ? SocketProviderFactory.UNNAMED_PROVIDER_ID : valueIdAttribute.getString(SocketProviderFactory.UNNAMED_PROVIDER_ID); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SocketProvidersProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SocketProvidersProcessor.java new file mode 100644 index 0000000000000..cc31f4df1f173 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SocketProvidersProcessor.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilderRegistry; +import com.tangosol.coherence.config.builder.SocketProviderBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.oracle.coherence.common.net.SocketProvider; + +import com.tangosol.run.xml.XmlElement; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * An {@link SocketProvidersProcessor} is responsible for processing <socket-provider> + * {@link XmlElement}s SocketProvider definitions. + * + * @author pfm 2013.03.18 + * @since Coherence 12.2.1 + */ +@XmlSimpleName("socket-providers") +public class SocketProvidersProcessor implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public Void process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + List listSocketProvider = new ArrayList<>(); + + // add all of the ParameterizedBuilders to the ParameterizedBuilderRegistry + ParameterizedBuilderRegistry registry = context.getCookie(ParameterizedBuilderRegistry.class); + + Map mapProcessedChildren = context.processElementsOf(element); + + for (Map.Entry entry : mapProcessedChildren.entrySet()) + { + registry.registerBuilder(SocketProvider.class, entry.getKey(), (SocketProviderBuilder) entry.getValue()); + } + + return null; + } + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SpecificInstanceProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SpecificInstanceProcessor.java new file mode 100644 index 0000000000000..192df1dae5563 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SpecificInstanceProcessor.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} that will process an {@link XmlElement} defining + * a {@link ParameterizedBuilder}, after which it will eagerly realized to produce + * an instance of the required type. + * + * @author bo 2013.03.11 + * @since Coherence 12.1.3 + */ +public class SpecificInstanceProcessor + extends AbstractEmptyElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link SpecificInstanceProcessor} for the specified {@link Class}. + * + * @param clzToRealize the class that will be instantiated, injected and + * returned during processing + */ + public SpecificInstanceProcessor(Class clzToRealize) + { + m_clzToRealize = clzToRealize; + } + + /** + * Constructs a {@link SpecificInstanceProcessor} for the specified {@link Class}. + * + * @param clzToRealize the class that will be instantiated, injected and + * returned during processing + * @param behavior the {@link AbstractEmptyElementProcessor.EmptyElementBehavior} when an empty + * {@link XmlElement} is encountered + */ + public SpecificInstanceProcessor(Class clzToRealize, EmptyElementBehavior behavior) + { + super(behavior); + m_clzToRealize = clzToRealize; + } + + /** + * Constructs a {@link SpecificInstanceProcessor} for the specified {@link Class}. + * + * @param clzToRealize the class that will be instantiated, injected and + * returned during processing + * @param oDefaultValue the value to return when an empty {@link XmlElement} + * is encountered + */ + public SpecificInstanceProcessor(Class clzToRealize, T oDefaultValue) + { + super(oDefaultValue); + m_clzToRealize = clzToRealize; + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + /** + * {@inheritDoc} + */ + @Override + public T onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // attempt to locate a ParameterizedBuilder + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr == null) + { + throw new ConfigurationException("Invalid <" + xmlElement.getName() + "> declaration in [" + xmlElement + + "]", "Please specify a <" + xmlElement.getName() + + "> that will produce a " + m_clzToRealize.getName()); + } + else + { + Object instance = bldr.realize(context.getDefaultParameterResolver(), context.getContextClassLoader(), + null); + return m_clzToRealize.isAssignableFrom(instance.getClass()) ? (T) instance : null; + } + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Class} to create, inject (from the {@link com.tangosol.run.xml.XmlElement} being processed) and return. + */ + private Class m_clzToRealize; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/StorageAccessAuthorizerBuilderProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/StorageAccessAuthorizerBuilderProcessor.java new file mode 100644 index 0000000000000..09b30225e3d92 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/StorageAccessAuthorizerBuilderProcessor.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.net.security.StorageAccessAuthorizer; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} for <storage-authorizer> elements defined by + * a Coherence Operational Configuration file. + * + * @author bo 2014.10.27 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("storage-authorizer") +public class StorageAccessAuthorizerBuilderProcessor + extends AbstractEmptyElementProcessor> + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link StorageAccessAuthorizerBuilderProcessor}. + */ + public StorageAccessAuthorizerBuilderProcessor() + { + super(EmptyElementBehavior.IGNORE); + } + + // ----- AbstractEmptyElementProcessor methods -------------------------- + + @Override + public ParameterizedBuilder onProcess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // assume the contains a builder definition + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + if (bldr == null) + { + throw new ConfigurationException(" fails to correctly define a StorageAccessAuthorizer implementation: " + + xmlElement, "Please define a "); + } + + return (ParameterizedBuilder) bldr; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/StorageAccessAuthorizersProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/StorageAccessAuthorizersProcessor.java new file mode 100644 index 0000000000000..aeedfc3fc8ef4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/StorageAccessAuthorizersProcessor.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.ServiceSchemeRegistry; +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.ParameterizedBuilderRegistry; +import com.tangosol.coherence.config.scheme.ServiceScheme; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.security.StorageAccessAuthorizer; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +import java.util.Map; + +/** + * An {@link ElementProcessor} for the <storage-authorizers%gt; element of + * Coherence Operational Configuration files. + * + * @author bo 2014.10.28 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("storage-authorizers") +public class StorageAccessAuthorizersProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public Void process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // process the children of the + Map mapProcessedChildren = context.processElementsOf(xmlElement); + + // add all of the ParameterizedBuilders to the ParameterizedBuilderRegistry + ParameterizedBuilderRegistry registry = context.getCookie(ParameterizedBuilderRegistry.class); + + Base.azzert(registry != null); + + for (Map.Entry entry : mapProcessedChildren.entrySet()) + { + String sName = entry.getKey(); + Object oBuilder = entry.getValue(); + + if (oBuilder instanceof ParameterizedBuilder) + { + ParameterizedBuilder builder = (ParameterizedBuilder) oBuilder; + + registry.registerBuilder(StorageAccessAuthorizer.class, sName, + (ParameterizedBuilder) builder); + } + else + { + throw new ConfigurationException("The specified [" + sName + + "] is not a ParameterizedBuilder", "Use element to specify an StorageAccessAuthorizer implementation"); + + } + } + + return null; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/StorageProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/StorageProcessor.java new file mode 100644 index 0000000000000..c99521d5cf5fa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/StorageProcessor.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.UnitCalculatorBuilder; +import com.tangosol.coherence.config.scheme.CachingScheme; +import com.tangosol.coherence.config.scheme.FlashJournalScheme; +import com.tangosol.coherence.config.scheme.LocalScheme; +import com.tangosol.coherence.config.scheme.RamJournalScheme; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link StorageProcessor} is responsible for processing + * <storage> {@link XmlElement}s to produce + * a {@link CachingScheme}. + * + * @author jf 2016.02.15 + * @since Coherence 14.1.1 + */ + +@XmlSimpleName("storage") +public class StorageProcessor implements ElementProcessor { + /** + * {@inheritDoc} + */ + @Override + public CachingScheme process(ProcessingContext context, XmlElement xmlElement) throws ConfigurationException + { + String sValue = (String) xmlElement.getValue(); + UnitCalculatorBuilder bldr = getUnitCalculatorBuilder(context.getDefaultParameterResolver()); + + if (sValue == null || "on-heap".equals(sValue)) + { + LocalScheme scheme = new LocalScheme(); + scheme.setUnitCalculatorBuilder(bldr); + + return scheme; + } + else if ("flashjournal".equals(sValue)) + { + FlashJournalScheme scheme = new FlashJournalScheme(); + scheme.setUnitCalculatorBuilder(bldr); + + return scheme; + } + else if ("ramjournal".equals(sValue)) + { + RamJournalScheme scheme = new RamJournalScheme(); + scheme.setUnitCalculatorBuilder(bldr); + + return scheme; + } + else + { + // never should get here due to xml schema validation. however, make ide happy + XmlElement xmlParent = xmlElement.getParent(); + String sParent = xmlParent != null ? '<' + xmlParent.getName() + '>' : ""; + + throw new ConfigurationException("invalid value " + sValue + " for " + sParent + " element", + "Provide a valid value of \"on-heap\", \"flashjournal\" or \"ramjournal\""); + } + } + + // ----- helpers -------------------------------------------------------- + + private UnitCalculatorBuilder getUnitCalculatorBuilder(ParameterResolver resolver) + { + UnitCalculatorBuilder bldr = new UnitCalculatorBuilder(); + Parameter parm = resolver.resolve("unit-calculator"); + Expression expr = parm == null ? new LiteralExpression<>("BINARY") : parm.evaluate(resolver).as(Expression.class); + + bldr.setUnitCalculatorType(expr); + + return bldr; + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SubscriberGroupProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SubscriberGroupProcessor.java new file mode 100644 index 0000000000000..3b1c91fbcb55d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SubscriberGroupProcessor.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.SubscriberGroupBuilder; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link ElementProcessor} for the <subscriber-group> element. + * + * @author jf 2016.03.02 + * @since Coherence 14.1.1 + */ +@XmlSimpleName("subscriber-group") +public class SubscriberGroupProcessor implements ElementProcessor + { + @Override + public SubscriberGroupBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + SubscriberGroupBuilder builder = new SubscriberGroupBuilder(); + context.inject(builder, xmlElement); + return builder; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SubscriberGroupsProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SubscriberGroupsProcessor.java new file mode 100644 index 0000000000000..84b5f890934ab --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/SubscriberGroupsProcessor.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.SubscriberGroupBuilder; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import java.util.Collection; +import java.util.Map; + +/** + * A {@link ElementProcessor} for the <subscriber-groups> element. + * + * @author jf 2016.03.02 + * @since Coherence 14.1.1 + */ +@XmlSimpleName("subscriber-groups") +public class SubscriberGroupsProcessor implements ElementProcessor> + { + @Override + @SuppressWarnings("unchecked") + public Collection process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + Map map = context.processElementsOf(xmlElement); + return (Collection) map.values(); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TcpAcceptorProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TcpAcceptorProcessor.java new file mode 100644 index 0000000000000..3fda960bb9dd0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TcpAcceptorProcessor.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.peer.acceptor.DefaultTcpAcceptorDependencies; +import com.tangosol.internal.net.service.peer.acceptor.TcpAcceptorDependencies; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} that will parse an <tcp-acceptor> and + * produce a TcpAcceptorDependencies object. + * + * @author pfm 2013.08.28 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("tcp-acceptor") +public class TcpAcceptorProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public TcpAcceptorDependencies process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + DefaultTcpAcceptorDependencies deps = new DefaultTcpAcceptorDependencies(); + context.inject(deps, xmlElement); + + SocketOptionsProcessor procSocketOptions = new SocketOptionsProcessor(); + deps.setSocketOptions(procSocketOptions.process(context, xmlElement)); + return deps; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TcpInitiatorProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TcpInitiatorProcessor.java new file mode 100644 index 0000000000000..293d83a086b2d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TcpInitiatorProcessor.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.service.peer.initiator.DefaultTcpInitiatorDependencies; +import com.tangosol.internal.net.service.peer.initiator.TcpInitiatorDependencies; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} that will parse an <tcp-initiator> and + * produce a TcpInitiatorDependencies object. + * + * @author pfm 2013.08.28 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("tcp-initiator") +public class TcpInitiatorProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public TcpInitiatorDependencies process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + DefaultTcpInitiatorDependencies deps = new DefaultTcpInitiatorDependencies(); + context.inject(deps, xmlElement); + + SocketOptionsProcessor procSocketOptions = new SocketOptionsProcessor(); + deps.setSocketOptions(procSocketOptions.process(context, xmlElement)); + return deps; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TopicMappingProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TopicMappingProcessor.java new file mode 100644 index 0000000000000..b0e6f9a2ba6ed --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TopicMappingProcessor.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.ResourceMapping; +import com.tangosol.coherence.config.CacheMapping; +import com.tangosol.coherence.config.TopicMapping; +import com.tangosol.coherence.config.scheme.TopicScheme; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.internal.net.topic.impl.paged.PagedTopicCaches; + +import com.tangosol.run.xml.XmlElement; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * An {@link TopicMappingProcessor} is responsible for processing <topic-mapping> + * {@link XmlElement}s to produce a {@link TopicMapping}. + * + * @author jk 2015.05.21 + * @since Coherence 14.1.1 + */ +@XmlSimpleName("topic-mapping") +public class TopicMappingProcessor + implements ElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Create a {@link TopicMappingProcessor}. + * + * @param sNameElementName the name of the element + * @param clsScheme the type of the topic scheme + */ + public TopicMappingProcessor(String sNameElementName, Class clsScheme) + { + f_sNameElementName = sNameElementName; + f_clsScheme = clsScheme; + } + + // ----- ElementProcessor methods --------------------------------------- + + @Override + public TopicMapping process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // construct the TopicMapping with the required properties + String sCacheNamePattern = context.getMandatoryProperty(f_sNameElementName, String.class, element); + String sSchemeName = context.getMandatoryProperty("scheme-name", String.class, element); + TopicMapping mapping = new TopicMapping(sCacheNamePattern, sSchemeName, f_clsScheme); + + // now inject any other (optional) properties it may require + context.inject(mapping, element); + + List subMappings = mapping.getSubMappings(); + List list = PagedTopicCaches.Names.values().stream() + .map(queueCacheNames -> createSubMapping(mapping, queueCacheNames, sCacheNamePattern, sSchemeName)) + .collect(Collectors.toList()); + + subMappings.addAll(list); + + // add the topic-mapping as a cookie so that child processors may access it (mainly to add resources if necessary) + context.addCookie(TopicMapping.class, mapping); + + // process all of the foreign elements + // (this allows the elements to modify the configuration) + context.processForeignElementsOf(element); + + return mapping; + } + + // ----- helper methods ------------------------------------------------- + + private CacheMapping createSubMapping(TopicMapping mappingCol, PagedTopicCaches.Names type, + String sNamePattern, String sSchemeName) + { + CacheMapping mapping = new CacheMapping(type.cacheNameForTopicName(sNamePattern), sSchemeName); + + mapping.setKeyClassName(type.getKeyClass().getCanonicalName()); + mapping.setValueClassName(type.getValueClass().getCanonicalName()); + mapping.setParameterResolver(mappingCol.getParameterResolver()); + mapping.setInternal(type.isInternal()); + + return mapping; + } + + // ----- object methods ------------------------------------------------- + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + + TopicMappingProcessor that = (TopicMappingProcessor) o; + + return f_sNameElementName.equals(that.f_sNameElementName); + + } + + @Override + public int hashCode() + { + return f_sNameElementName.hashCode(); + } + + // ----- data members --------------------------------------------------- + + /** + * The name of the XML element to use to obtain the mapping name + */ + private final String f_sNameElementName; + + /** + * The type of the topic scheme. + */ + private final Class f_clsScheme; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TopicSchemeMappingProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TopicSchemeMappingProcessor.java new file mode 100644 index 0000000000000..15b83fc402c67 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TopicSchemeMappingProcessor.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.ResourceMapping; +import com.tangosol.coherence.config.CacheConfig; +import com.tangosol.coherence.config.ResourceMappingRegistry; +import com.tangosol.coherence.config.SchemeMappingRegistry; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import java.util.Map; + +/** + * An {@link TopicSchemeMappingProcessor} is responsible for processing + * <topic-scheme-mapping> {@link XmlElement}s to update the + * {@link ResourceMappingRegistry} with {@link ResourceMapping}s. + * + * @author jk 2015.05.28 + * @since Coherence 14.1.1 + */ +@XmlSimpleName("topic-scheme-mapping") +public class TopicSchemeMappingProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public ResourceMappingRegistry process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // process the children of the + Map mapProcessedChildren = context.processElementsOf(element); + + // add all of the Mappings to the ResourceMappingRegistry + CacheConfig cacheConfig = context.getCookie(CacheConfig.class); + ResourceMappingRegistry registry = cacheConfig == null + ? new SchemeMappingRegistry() + : cacheConfig.getMappingRegistry(); + + for (Object oChild : mapProcessedChildren.values()) + { + if (oChild instanceof ResourceMapping) + { + // FUTURE: handle the case when the cache mapping is a duplicate or weaker pattern? + registry.register((ResourceMapping) oChild); + } + } + + // process all of the foreign elements + // (this allows the elements to modify the configuration) + context.processForeignElementsOf(element); + + return registry; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TransformerProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TransformerProcessor.java new file mode 100644 index 0000000000000..1235334366c08 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/TransformerProcessor.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Filter; + +/** + * Responsible for processing {@code view-filter} elements. + * + * @author rlubke + * @since 12.2.1.4 + */ +@XmlSimpleName("transformer") +public class TransformerProcessor + implements ElementProcessor> + { + // ----- ElementProcessor methods --------------------------------------- + + @SuppressWarnings("unchecked") + @Override + public ParameterizedBuilder process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // fetch the builder defined in the "transformer" element + ParameterizedBuilder bldr = ElementProcessorHelper.processParameterizedBuilder(context, xmlElement); + + return (ParameterizedBuilder) bldr; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/UnitCalculatorProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/UnitCalculatorProcessor.java new file mode 100755 index 0000000000000..c2fdb9bf5648b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/UnitCalculatorProcessor.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.builder.UnitCalculatorBuilder; +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + + +import com.tangosol.net.cache.ConfigurableCacheMap.UnitCalculator; + +import com.tangosol.run.xml.XmlElement; + +import java.text.ParseException; + +/** + * A {@link UnitCalculatorProcessor} is responsible for processing a + * unit-calculator {@link XmlElement} to produce a {@link UnitCalculatorBuilder}. + * + * @author pfm 2011.12.02 + * @since Coherence 12.1.2 + */ +@XmlSimpleName("unit-calculator") +public class UnitCalculatorProcessor + implements ElementProcessor + { + /** + * {@inheritDoc} + */ + @Override + public UnitCalculatorBuilder process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + UnitCalculatorBuilder bldr = new UnitCalculatorBuilder(); + + if (element.getElementList().isEmpty()) + { + try + { + String sValue = element.getString().trim(); + + bldr.setUnitCalculatorType(context.getExpressionParser().parse(sValue, String.class)); + } + catch (ParseException e) + { + throw new ConfigurationException("Failed to parse the specifie unit-calculator", + "Please ensure a correct unit-calculator is specified", e); + } + } + else + { + bldr.setCustomBuilder((ParameterizedBuilder) context.processOnlyElementOf(element)); + } + + return bldr; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/UnsupportedFeatureProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/UnsupportedFeatureProcessor.java new file mode 100644 index 0000000000000..7c7929c21279d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/UnsupportedFeatureProcessor.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.run.xml.XmlElement; + +/** + * UnsupportedFeatureProcessor is an ElementProcessor that fails fast highlighting + * which feature is not supported in this edition of the product. + * + * @author hr 2020.01.23 + */ +public class UnsupportedFeatureProcessor + implements ElementProcessor + { + // ----- constructors --------------------------------------------------- + + /** + * Construct ElementProcessor that reports which feature is not support. + * + * @param sFeature the feature that is not supported + */ + public UnsupportedFeatureProcessor(String sFeature) + { + f_sFeature = sFeature; + } + + // ----- ElementProcess interface --------------------------------------- + + @Override + public Object process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + throw new UnsupportedOperationException(f_sFeature + " is not supported in this edition"); + } + + // ----- data members --------------------------------------------------- + + /** + * The feature that is not supported + */ + protected final String f_sFeature; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ValueStorageSchemeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ValueStorageSchemeProcessor.java new file mode 100644 index 0000000000000..48ca21b1dca1f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/ValueStorageSchemeProcessor.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; +import com.tangosol.coherence.config.scheme.BackingMapScheme; +import com.tangosol.coherence.config.scheme.CachingScheme; +import com.tangosol.coherence.config.scheme.CustomScheme; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} that creates a {@link BackingMapScheme} + * for use in a collection scheme. + * + * @author jk 2015.05.28 + * @since Coherence 14.1.1 + */ +@XmlSimpleName("value-storage-scheme") +public class ValueStorageSchemeProcessor + implements ElementProcessor + { + @Override + public BackingMapScheme process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + // create a BackingMapScheme instance and inject all annotated properties + BackingMapScheme scheme = context.inject(new BackingMapScheme(), element); + + // the remaining element must be the CachingScheme definition + Object oResult = context.processRemainingElementOf(element); + + if (oResult instanceof CachingScheme) + { + scheme.setInnerScheme((CachingScheme) oResult); + } + else if (oResult instanceof ParameterizedBuilder) + { + scheme.setInnerScheme(new CustomScheme((ParameterizedBuilder) oResult)); + } + else + { + throw new ConfigurationException("The is not a CachingScheme", + "Please ensure that the configured scheme is appropriate for the "); + } + + return scheme; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/WrapperStreamFactoryListProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/WrapperStreamFactoryListProcessor.java new file mode 100644 index 0000000000000..9f86cdc896a75 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/WrapperStreamFactoryListProcessor.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.config.xml.processor; + + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.xml.ElementProcessor; +import com.tangosol.config.xml.ProcessingContext; +import com.tangosol.config.xml.XmlSimpleName; + +import com.tangosol.io.Serializer; +import com.tangosol.io.SerializerFactory; +import com.tangosol.io.WrapperStreamFactory; + +import com.tangosol.net.OperationalContext; + +import com.tangosol.run.xml.XmlElement; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * An {@link ElementProcessor} that will parse a <use-filters> and produce + * list of {@link WrapperStreamFactory}s. + * + * @author bo 2013.06.02 + * @since Coherence 12.1.3 + */ +@XmlSimpleName("use-filters") +@Deprecated +public class WrapperStreamFactoryListProcessor + implements ElementProcessor> + { + /** + * {@inheritDoc} + */ + @Override + public List process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + // grab the operational context from which we can lookup the serializer + OperationalContext ctxOperational = context.getCookie(OperationalContext.class); + + if (ctxOperational == null) + { + throw new ConfigurationException("Attempted to resolve the OperationalContext in [" + xmlElement + + "] but it was not defined", "The registered ElementHandler for the element is not operating in an OperationalContext"); + } + + Map mapFactoriesByName = ctxOperational.getFilterMap(); + + ArrayList listFactories = new ArrayList(); + + for (Iterator iterFilterNames = xmlElement.getElements("filter-name"); iterFilterNames.hasNext(); ) + { + XmlElement xmlFilterName = iterFilterNames.next(); + String sFilterName = xmlFilterName.getString().trim(); + + if (!sFilterName.isEmpty()) + { + WrapperStreamFactory filterFactory = mapFactoriesByName.get(sFilterName); + + if (filterFactory == null) + { + throw new ConfigurationException("Could not locate the specified filter [" + sFilterName + "] in [" + + xmlElement + + "]", "Please ensure that the specified filter is correctly defined in the operational configuration"); + } + else + { + listFactories.add(filterFactory); + } + } + } + + return listFactories; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/package.html new file mode 100644 index 0000000000000..e7088fe55a3cd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/config/xml/processor/package.html @@ -0,0 +1,6 @@ + +Defines the Xml document Element and Attribute Processors for Coherence Cache +Configuration files. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/discovery/Discovery.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/discovery/Discovery.java new file mode 100644 index 0000000000000..3b3e340e07f14 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/discovery/Discovery.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.discovery; + +import com.tangosol.net.management.AnnotatedStandardMBean; + +import com.tangosol.util.Base; + +import javax.management.NotCompliantMBeanException; +import javax.management.StandardMBean; + +/** + * Implementation of {@link DiscoveryMBean} which allows Coherence to be discovered by Oracle Enterprise Manager + * and FMW Control management tools. + * + * @author dag 2012.09.20 + * + * @since Coherence 12.1.2 + */ +public class Discovery + implements DiscoveryMBean + { + // ----- DiscoveryMBean methods ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getAddOnName() + { + return ADD_ON_NAME; + } + + /** + * {@inheritDoc} + */ + @Override + public String getAddOnDisplayName() + { + return ADD_ON_DISPLAY_NAME; + } + + /** + * {@inheritDoc} + */ + @Override + public String getEMDiscoveryPluginName() + { + return getAddOnName() + "." + DISCOVERY_PLUGIN_NAME; + } + + // ----- accessors and helpers ------------------------------------------ + + /** + * Create the DiscoveryMBean + */ + public static StandardMBean createMBean() + { + StandardMBean mbean; + Discovery discovery = new Discovery(); + + try + { + mbean = new AnnotatedStandardMBean(discovery, DiscoveryMBean.class); + } + catch (NotCompliantMBeanException e) + { + throw Base.ensureRuntimeException(e); + } + + return mbean; + } + + // ---- constants ------------------------------------------------------- + + // The values of the following constants were supplied by EM and are required for EM Integration. + // This MBean is considered a "Plugin" by EM. + + /** + * String representing the root of the "name" part of ObjectName + * for the DiscoveryMBean. + */ + private static final String ADD_ON_NAME = "oracle.sysman.emas"; + + /** + * The name displayed by the management tool. + */ + private static final String ADD_ON_DISPLAY_NAME = "Oracle Fusion Middleware"; + + /** + * String representing the Coherence specific part of the "name" value of ObjectName + * for the DiscoveryMBean. + */ + private static final String DISCOVERY_PLUGIN_NAME = "CoherenceDiscovery"; + + /** + * String representing the "domain" part of ObjectName for the + * DiscoveryMBean. + */ + private static final String DISCOVERY_DOMAIN = "EMDomain"; + + /** + * String representing the "type" part of ObjectName for the + * DiscoveryMBean. + */ + public final static String DISCOVERY_TYPE = "type=EMDiscoveryIntegration"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/discovery/DiscoveryMBean.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/discovery/DiscoveryMBean.java new file mode 100644 index 0000000000000..5b3186f64c679 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/discovery/DiscoveryMBean.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.discovery; + +import com.tangosol.net.management.annotation.Description; + +/** + * A {@link DiscoveryMBean} is an MBean interface which defines attributes required by Oracle Enterprise Manager + * and FMW Control management tools to discover running instances of Coherence. + * + * @author dag 2012.09.20 + * + * @since Coherence 12.1.2 + */ +@Description("Allows Coherence to be discovered by Oracle Enterprise Manager and FMW Control management tools.") +public interface DiscoveryMBean + { + /** + * Return the name of the Add On Plugin. + * + * @return the name of the Add On Plugin. + */ + @Description("The name of the Add On Plugin.") + public String getAddOnName(); + + /** + * Return the name displayed by the management tool. + * + * @return the name displayed by the management tool + */ + @Description("The name displayed by the management tool.") + public String getAddOnDisplayName(); + + /** + * Return the full name of the Plugin. + * + * @return the full name of the Plugin + */ + @Description("The full name of the Plugin.") + public String getEMDiscoveryPluginName(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ChainedExtractorBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ChainedExtractorBuilder.java new file mode 100644 index 0000000000000..619d373adc883 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ChainedExtractorBuilder.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import com.tangosol.util.ValueExtractor; + +import java.util.Arrays; +import java.util.List; + +/** + * An {@link ExtractorBuilder} implementation that delegates building + * {@link ValueExtractor}s to a chain of ExtractorBuilders + * and returns the result from the first ExtractorBuilder to return a + * non-null value from its {@link #realize(String, int, String)} method. + * + * @author jk 2014.07.15 + * @since Coherence 12.2.1 + */ +public class ChainedExtractorBuilder + implements ExtractorBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a ChainedExtractorBuilder that uses the specified chain of + * {@link ExtractorBuilder}s to realize instances of {@link ValueExtractor}s. + * + * @param aBuilders the chain of ExtractorBuilder to use + */ + public ChainedExtractorBuilder(ExtractorBuilder...aBuilders) + { + f_listBuilders = Arrays.asList(aBuilders); + } + + // ----- ExtractorBuilder interface ------------------------------------- + + /** + * Call the realize(String, int, String) method on each {@link ExtractorBuilder} + * in the chain returning the result of the first ExtractorBuilder to return a non-null + * value. + * + * @param sCacheName the name of the cache the ValueExtractor will be invoked against + * @param nTarget the target for the ValueExtractor + * @param sProperties the path to the property value to extract + * + * @return a {@link ValueExtractor} for the given cache name, target and properties + * or null if non of the ExtractorBuilder returned a non-null value + */ + @Override + public ValueExtractor realize(String sCacheName, int nTarget, String sProperties) + { + for (ExtractorBuilder builder : f_listBuilders) + { + ValueExtractor extractor = builder.realize(sCacheName, nTarget, sProperties); + + if (extractor != null) + { + return extractor; + } + } + + return null; + } + + // ----- data members --------------------------------------------------- + + /** + * The chain of {@link ExtractorBuilder}s to delegate to. + */ + protected final List f_listBuilders; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/CohQLException.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/CohQLException.java new file mode 100644 index 0000000000000..50c355b0b9067 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/CohQLException.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +/** + * An exception thrown when errors occur building CohQL queries. + * This exception would usually signify that a CohQL statement has + * been executed with the incorrect syntax. + * + * @author jk 2013.12.09 + * @since Coherence 12.2.1 + */ +public class CohQLException + extends RuntimeException + { + /** + * Construct a new exception with the specified detail message. + * + * @param sMessage the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method + */ + public CohQLException(String sMessage) + { + super(sMessage); + } + + /** + * Construct a new exception with the specified detail message and + * cause. + *

+ * Note that the detail message associated with + * cause is not automatically incorporated in + * this exception's detail message. + * + * @param sMessage the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method) + * @param t the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown) + */ + public CohQLException(String sMessage, Throwable t) + { + super(sMessage, t); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/CoherenceQueryLanguage.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/CoherenceQueryLanguage.java new file mode 100644 index 0000000000000..ba7f63a6963a3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/CoherenceQueryLanguage.java @@ -0,0 +1,689 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.coherence.dslquery.function.FunctionBuilders; + +import com.tangosol.coherence.dslquery.statement.BackupStatementBuilder; +import com.tangosol.coherence.dslquery.statement.CreateCacheStatementBuilder; +import com.tangosol.coherence.dslquery.statement.CreateIndexStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DeleteStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DropCacheStatementBuilder; +import com.tangosol.coherence.dslquery.statement.TruncateCacheStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DropIndexStatementBuilder;; +import com.tangosol.coherence.dslquery.statement.InsertStatementBuilder; +import com.tangosol.coherence.dslquery.statement.QueryRecorderStatementBuilder; +import com.tangosol.coherence.dslquery.statement.RestoreStatementBuilder; +import com.tangosol.coherence.dslquery.statement.SelectStatementBuilder; +import com.tangosol.coherence.dslquery.statement.SourceStatementBuilder; +import com.tangosol.coherence.dslquery.statement.UpdateStatementBuilder; + +import com.tangosol.coherence.dslquery.statement.persistence.ArchiveSnapshotStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.CreateSnapshotStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.ForceRecoveryStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.ListArchiverStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.ListServicesStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.ListSnapshotsStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.RecoverSnapshotStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.RemoveSnapshotStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.ResumeServiceStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.RetrieveSnapshotStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.SuspendServiceStatementBuilder; +import com.tangosol.coherence.dslquery.statement.persistence.ValidateSnapshotStatementBuilder; + +import com.tangosol.coherence.dslquery.operator.AdditionOperator; +import com.tangosol.coherence.dslquery.operator.AndOperator; +import com.tangosol.coherence.dslquery.operator.BaseOperator; +import com.tangosol.coherence.dslquery.operator.BetweenOperator; +import com.tangosol.coherence.dslquery.operator.ContainsAllOperator; +import com.tangosol.coherence.dslquery.operator.ContainsAnyOperator; +import com.tangosol.coherence.dslquery.operator.ContainsOperator; +import com.tangosol.coherence.dslquery.operator.DivisionOperator; +import com.tangosol.coherence.dslquery.operator.EqualsOperator; +import com.tangosol.coherence.dslquery.operator.GreaterEqualsOperator; +import com.tangosol.coherence.dslquery.operator.GreaterOperator; +import com.tangosol.coherence.dslquery.operator.InOperator; +import com.tangosol.coherence.dslquery.operator.LessEqualsOperator; +import com.tangosol.coherence.dslquery.operator.LessOperator; +import com.tangosol.coherence.dslquery.operator.LikeOperator; +import com.tangosol.coherence.dslquery.operator.MultiplicationOperator; +import com.tangosol.coherence.dslquery.operator.NotEqualsOperator; +import com.tangosol.coherence.dslquery.operator.OrOperator; +import com.tangosol.coherence.dslquery.operator.SubtractionOperator; +import com.tangosol.coherence.dslquery.operator.XorOperator; + +import com.tangosol.coherence.dslquery.token.SQLBackupOPToken; +import com.tangosol.coherence.dslquery.token.SQLCreateCacheOPToken; +import com.tangosol.coherence.dslquery.token.SQLCreateIndexOPToken; +import com.tangosol.coherence.dslquery.token.SQLDeleteOPToken; +import com.tangosol.coherence.dslquery.token.SQLDropCacheOPToken; +import com.tangosol.coherence.dslquery.token.SQLDropIndexOPToken; +import com.tangosol.coherence.dslquery.token.SQLExplainOPToken; +import com.tangosol.coherence.dslquery.token.SQLInsertOPToken; +import com.tangosol.coherence.dslquery.token.SQLPeekOPToken; +import com.tangosol.coherence.dslquery.token.SQLRestoreOPToken; +import com.tangosol.coherence.dslquery.token.SQLSelectOPToken; +import com.tangosol.coherence.dslquery.token.SQLSourceOPToken; +import com.tangosol.coherence.dslquery.token.SQLTraceOPToken; +import com.tangosol.coherence.dslquery.token.SQLTruncateCacheOPToken; +import com.tangosol.coherence.dslquery.token.SQLUpdateOPToken; + +import com.tangosol.coherence.dslquery.token.persistence.SQLArchiveSnapshotOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLCreateSnapshotOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLForceRecoveryOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLListArchivedSnapshotsOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLListArchiverOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLListServicesOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLListSnapshotsOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLRecoverSnapshotOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLRemoveSnapshotOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLResumeServiceOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLRetrieveSnapshotOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLSuspendServiceOPToken; +import com.tangosol.coherence.dslquery.token.persistence.SQLValidateSnapshotOPToken; + +import com.tangosol.coherence.dsltools.base.LiteralBaseToken; + +import com.tangosol.coherence.dsltools.precedence.EndOfStatementOPToken; +import com.tangosol.coherence.dsltools.precedence.InfixRightOPToken; +import com.tangosol.coherence.dsltools.precedence.KeywordOPToken; +import com.tangosol.coherence.dsltools.precedence.ListOpToken; +import com.tangosol.coherence.dsltools.precedence.LiteralOPToken; +import com.tangosol.coherence.dsltools.precedence.NotOPToken; +import com.tangosol.coherence.dsltools.precedence.ParenOPToken; +import com.tangosol.coherence.dsltools.precedence.PathOPToken; +import com.tangosol.coherence.dsltools.precedence.PrefixOPToken; +import com.tangosol.coherence.dsltools.precedence.PunctuationOPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.coherence.dsltools.termlanguage.ColonToken; +import com.tangosol.coherence.dsltools.termlanguage.CurlyToken; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import java.util.concurrent.ConcurrentHashMap; + +import static com.tangosol.coherence.dsltools.precedence.OPToken.*; + +/** + * CoherenceQueryLanguage is a simple language for building + * Filters and doing simple queries that are similar in style to SQL. + * + * This class configures CohQL and contains various attributes + * that control CohQL execution. + * + * @author djl 2009.08.31 + * @author jk 2013.12.02 + */ +public class CoherenceQueryLanguage + { + // ----- Language table API --------------------------------------------- + + /** + * Construct a CoherenceQueryLanguage instance. + */ + public CoherenceQueryLanguage() + { + m_tokenTableSQL = getSqlTokenTable(false); + m_tokenTableExtendedSQL = getSqlTokenTable(true); + m_tokenTableForFilter = createCommonTokens(); + m_mapOperators = initializeOperatorMap(); + m_mapFunctions = initializeFunctionMap(); + m_mapStatementBuilders = createStatements(); + } + + // ----- Language table API --------------------------------------------- + + /** + * Return an initialized standard {@link TokenTable} known by this + * CoherenceQueryLanguage. + * + * @return an initialized standard TokenTable known by this + * CoherenceQueryLanguage + */ + public TokenTable filtersTokenTable() + { + return m_tokenTableForFilter; + } + + /** + * Return an initialized extended {@link TokenTable} known by this + * CoherenceQueryLanguage. + * + * @return an initialized extended TokenTable known by this + * CoherenceQueryLanguage + */ + public TokenTable sqlTokenTable() + { + return m_tokenTableSQL; + } + + /** + * Return an initialized TokenTable for the full CoherenceQueryLanguage. + * + * @return a TokenTable for the CoherenceQueryLanguage + */ + public TokenTable extendedSqlTokenTable() + { + return m_tokenTableExtendedSQL; + } + + /** + * Return an initialized TokenTable for the full CoherenceQueryLanguage. + * + * @param fExtended flag that enables extended and experimental features + * + * @return a TokenTable for the CoherenceQueryLanguage + */ + protected TokenTable getSqlTokenTable(boolean fExtended) + { + // Create tokens with their respective ids and precedences + TokenTable tokens = createCommonTokens(); + + // CohQL Language Elements with custom OPToken implementations + tokens.addToken(new SQLPeekOPToken("alter")); + tokens.addToken(new SQLBackupOPToken("backup")); + tokens.addToken(new SQLPeekOPToken("create", new SQLCreateCacheOPToken(), new SQLCreateIndexOPToken(), + new SQLCreateSnapshotOPToken())); + tokens.alias("ensure", "create"); + tokens.addToken(new SQLDeleteOPToken("delete")); + tokens.addToken(new SQLPeekOPToken("drop", new SQLDropCacheOPToken(), new SQLDropIndexOPToken())); + tokens.addToken(new SQLTruncateCacheOPToken("truncate")); + tokens.addToken(new SQLExplainOPToken("explain")); + tokens.addToken(new SQLInsertOPToken("insert")); + tokens.addToken(new SQLRestoreOPToken("restore")); + tokens.addToken(new SQLSelectOPToken("select")); + tokens.addToken(new SQLSourceOPToken("@")); + tokens.alias("source", "@"); + tokens.addToken(new SQLTraceOPToken("trace")); + tokens.addToken(new SQLUpdateOPToken("update")); + + // persistence related commands + tokens.addToken(new SQLPeekOPToken("list", new SQLListServicesOPToken(), new SQLListSnapshotsOPToken(), + new SQLListArchivedSnapshotsOPToken(), new SQLListArchiverOPToken())); + tokens.addToken(new SQLRemoveSnapshotOPToken("remove")); + tokens.addToken(new SQLRecoverSnapshotOPToken("recover")); + tokens.addToken(new SQLValidateSnapshotOPToken("validate")); + tokens.addToken(new SQLArchiveSnapshotOPToken("archive")); + tokens.addToken(new SQLRetrieveSnapshotOPToken("retrieve")); + tokens.addToken(new SQLResumeServiceOPToken("resume")); + tokens.addToken(new SQLSuspendServiceOPToken("suspend")); + tokens.addToken(new SQLForceRecoveryOPToken("force")); + + // Keywords + tokens.addToken(new KeywordOPToken("by")); + tokens.addToken(new KeywordOPToken("cache")); + tokens.addToken(new KeywordOPToken("check")); + tokens.addToken(new KeywordOPToken("distinct")); + tokens.addToken(new KeywordOPToken("escape")); + tokens.addToken(new KeywordOPToken("file")); + tokens.addToken(new KeywordOPToken("from")); + tokens.addToken(new KeywordOPToken("group")); + tokens.addToken(new KeywordOPToken("having")); + tokens.addToken(new KeywordOPToken("index")); + tokens.addToken(new KeywordOPToken("into")); + tokens.addToken(new KeywordOPToken("key")); + tokens.addToken(new KeywordOPToken("off")); + tokens.addToken(new KeywordOPToken("on")); + tokens.addToken(new KeywordOPToken("order")); + tokens.addToken(new KeywordOPToken("plan")); + tokens.addToken(new KeywordOPToken("service")); + tokens.addToken(new KeywordOPToken("set")); + tokens.addToken(new KeywordOPToken("show")); + tokens.addToken(new KeywordOPToken("to")); + tokens.addToken(new KeywordOPToken("value")); + tokens.addToken(new KeywordOPToken("where")); + + if (fExtended) + { + tokens.addToken(new ListOpToken("[", PRECEDENCE_PARENTHESES, ".list.")); + tokens.addToken(new CurlyToken("{", PRECEDENCE_PARENTHESES)); + tokens.addToken(new ColonToken(":", PRECEDENCE_PARENTHESES)); + } + + return tokens; + } + + /** + * There are three token tables in CohQL, the one used to build {@link com.tangosol.util.Filter} + * instances, the standard CohQL table and the extended CohQL table. All three share a set + * of common tokens which are initialised by this method. Examples of common tokens would + * be operators such as '+', '-', '/', literals such as 'true', 'false' etc. + * + * @return a {@link TokenTable} initialised with the common CohQL tokens + */ + private TokenTable createCommonTokens() + { + TokenTable tokenTable = new TokenTable(IDENTIFIER_NODE, LITERAL_NODE); + + tokenTable.setIgnoreCase(true); + + tokenTable.addToken(new InfixRightOPToken("**", PRECEDENCE_EXPONENT, BINARY_OPERATOR_NODE)); + + tokenTable.addToken(new NotOPToken("!", PRECEDENCE_UNARY, UNARY_OPERATOR_NODE, UNARY_OPERATOR_NODE)); + tokenTable.addToken(new PrefixOPToken("new", PRECEDENCE_UNARY, UNARY_OPERATOR_NODE)); + tokenTable.addToken(new PrefixOPToken("~", PRECEDENCE_UNARY, UNARY_OPERATOR_NODE)); + tokenTable.addToken(new PrefixOPToken("?", PRECEDENCE_UNARY, BINDING_NODE)); + tokenTable.addToken(new PrefixOPToken(":", PRECEDENCE_UNARY, BINDING_NODE)); + tokenTable.addToken(new ParenOPToken("(", PRECEDENCE_PARENTHESES, CALL_NODE, LIST_NODE)); + tokenTable.addToken(new PathOPToken(".", PRECEDENCE_PARENTHESES, DEREF_NODE)); + tokenTable.addToken(new PunctuationOPToken(",")); + tokenTable.addToken(EndOfStatementOPToken.INSTANCE); + + tokenTable.addToken("true", new LiteralOPToken(LiteralBaseToken.createBoolean("true")), null, LITERAL_NODE); + tokenTable.addToken("false", new LiteralOPToken(LiteralBaseToken.createBoolean("false")), null, LITERAL_NODE); + tokenTable.addToken("null", new LiteralOPToken(LiteralBaseToken.createNull("null")), null, LITERAL_NODE); + tokenTable.addToken("nan", new LiteralOPToken(LiteralBaseToken.createDouble("NaN")), null, LITERAL_NODE); + tokenTable.addToken("infinity", + new LiteralOPToken(LiteralBaseToken.createDouble("Infinity")), null, LITERAL_NODE); + + tokenTable.alias("not", "!"); + + if (m_mapOperators != null) + { + for (BaseOperator op : m_mapOperators.values()) + { + op.addToTokenTable(tokenTable); + } + } + + return tokenTable; + } + + /** + * Creates the Map holding CohQL functions and populates it + * with the default functions available in CohQL. + * CohQL functions are elements such as sum(), avg(), max() etc. + * + * @return the function Map populated with the default functions + */ + protected Map> initializeFunctionMap() + { + Map> map = new ConcurrentHashMap<>(); + + map.put("max", FunctionBuilders.DOUBLE_MAX_FUNCTION_BUILDER); + map.put("min", FunctionBuilders.DOUBLE_MIN_FUNCTION_BUILDER); + map.put("sum", FunctionBuilders.DOUBLE_SUM_FUNCTION_BUILDER); + map.put("avg", FunctionBuilders.DOUBLE_AVERAGE_FUNCTION_BUILDER); + map.put("bd_max", FunctionBuilders.BIG_DECIMAL_MAX_FUNCTION_BUILDER); + map.put("bd_min", FunctionBuilders.BIG_DECIMAL_MIN_FUNCTION_BUILDER); + map.put("bd_sum", FunctionBuilders.BIG_DECIMAL_SUM_FUNCTION_BUILDER); + map.put("bd_avg", FunctionBuilders.BIG_DECIMAL_AVERAGE_FUNCTION_BUILDER); + map.put("long_max", FunctionBuilders.LONG_MAX_FUNCTION_BUILDER); + map.put("long_min", FunctionBuilders.LONG_MIN_FUNCTION_BUILDER); + map.put("long_sum", FunctionBuilders.LONG_SUM_FUNCTION_BUILDER); + map.put("count", FunctionBuilders.COUNT_FUNCTION_BUILDER); + map.put("value", FunctionBuilders.VALUE_FUNCTION_BUILDER); + map.put("key", FunctionBuilders.KEY_FUNCTION_BUILDER); + + return map; + } + + /** + * Return the function mapped to the given function name or + * null if no function mapping exists. + * + * @param sName the name of the function to return + * + * @return the function mapped to the given function name or + * null if no function mapping exists + */ + public ParameterizedBuilder getFunction(String sName) + { + return m_mapFunctions.get(sName); + } + + /** + * Map the specified CohQL {@link ParameterizedBuilder} to + * the specified function name. If either the name of the implementation + * is null then no mapping will occur. + * + * @param sName the name of the function + * @param bldrFunction the implementation of the function + */ + public void addFunction(String sName, ParameterizedBuilder bldrFunction) + { + if (sName == null || bldrFunction == null) + { + throw new IllegalArgumentException( + "Both name and function must be supplied to add a function"); + } + m_mapFunctions.put(sName, bldrFunction); + } + + /** + * Remove the CohQL function mapping for the specified function name. + * The removed {@link ParameterizedBuilder} will be returned, or null if there + * was no mapping for the specified name. + * + * @param sName the name of the function to remove + * + * @return the removed ParameterizedBuilder will be returned, or null if there + * was no mapping for the specified name. + */ + public ParameterizedBuilder removeFunction(String sName) + { + return m_mapFunctions.remove(sName); + } + + /** + * Remove all custom function mappings that have been registered. + */ + public synchronized void clearCustomFunctions() + { + m_mapFunctions = null; + initializeFunctionMap(); + } + + /** + * Initialize the CohQL operators Map with all of the built-in CohQL + * operators and return the Map. + * + * @return the Map of CohQL operators + */ + protected Map initializeOperatorMap() + { + Map map = m_mapOperators = new HashMap<>(); + + addOperatorInternal(map, AndOperator.INSTANCE); // && and + addOperatorInternal(map, BetweenOperator.INSTANCE); // between + addOperatorInternal(map, ContainsAllOperator.INSTANCE); // contains all + addOperatorInternal(map, ContainsAnyOperator.INSTANCE); // contains any + addOperatorInternal(map, ContainsOperator.INSTANCE); // contains + addOperatorInternal(map, EqualsOperator.INSTANCE); // == + addOperatorInternal(map, GreaterEqualsOperator.INSTANCE); // >= + addOperatorInternal(map, GreaterOperator.INSTANCE); // > + addOperatorInternal(map, InOperator.INSTANCE); // in + addOperatorInternal(map, LessEqualsOperator.INSTANCE); // <= + addOperatorInternal(map, LessOperator.INSTANCE); // < + addOperatorInternal(map, LikeOperator.INSTANCE); // like + addOperatorInternal(map, NotEqualsOperator.INSTANCE); // != + addOperatorInternal(map, OrOperator.INSTANCE); // || + addOperatorInternal(map, XorOperator.INSTANCE); // xor + addOperatorInternal(map, AdditionOperator.INSTANCE); // - + addOperatorInternal(map, SubtractionOperator.INSTANCE); // - + addOperatorInternal(map, MultiplicationOperator.INSTANCE); // * + addOperatorInternal(map, DivisionOperator.INSTANCE); // / + + return map; + } + + /** + * Add an operator to the specified operator Map and to all of the + * internal {@link TokenTable}s. + * + * @param map the operator map to add the operator to + * @param op the operator to add + */ + private void addOperatorInternal(Map map, BaseOperator op) + { + map.put(op.getSymbol(), op); + + TokenTable tokenTable = m_tokenTableForFilter; + if (tokenTable != null) + { + op.addToTokenTable(tokenTable); + } + + tokenTable = m_tokenTableSQL; + if (tokenTable != null) + { + op.addToTokenTable(tokenTable); + } + + tokenTable = m_tokenTableExtendedSQL; + if (tokenTable != null) + { + op.addToTokenTable(tokenTable); + } + } + + /** + * Add the custom operator to the CohQL language. + * + * @param operator the operator to add + */ + public void addOperator(BaseOperator operator) + { + addOperatorInternal(m_mapOperators, operator); + } + + /** + * Return the set of CohQL Operators characters. + * + * @return the set of CohQL operators characters + */ + public Set getOperators() + { + return Collections.unmodifiableSet(m_mapOperators.keySet()); + } + + /** + * Return the {@link BaseOperator} mapped to the given symbol. + * + * @param sSymbol the symbol of the BaseOperator to get + * + * @return the BaseOperator mapped to the specified symbol or + * null if there i sno mapping for that symbol + */ + public BaseOperator getOperator(String sSymbol) + { + return m_mapOperators.get(sSymbol); + } + + /** + * Return the map of {@link StatementBuilder} instances. + * The map is keyed on the functor used to represent a particular + * query type in a CohQL AST. + * + * @return the map of StatementBuilder instances + */ + public Map> getStatementBuilders() + { + return Collections.unmodifiableMap(m_mapStatementBuilders); + } + + /** + * Create the CohQL {@link StatementBuilder} map and initialise + * it with the standard CohQL statements. + * + * @return the standard CohQL statement map + */ + protected Map> createStatements() + { + Map> map = new LinkedHashMap<>(); + + map.put(SQLCreateCacheOPToken.FUNCTOR, CreateCacheStatementBuilder.INSTANCE); + map.put(SQLCreateIndexOPToken.FUNCTOR, CreateIndexStatementBuilder.INSTANCE); + map.put(SQLDropCacheOPToken.FUNCTOR, DropCacheStatementBuilder.INSTANCE); + map.put(SQLTruncateCacheOPToken.FUNCTOR, TruncateCacheStatementBuilder.INSTANCE); + map.put(SQLDropIndexOPToken.FUNCTOR, DropIndexStatementBuilder.INSTANCE); + map.put(SQLBackupOPToken.FUNCTOR, BackupStatementBuilder.INSTANCE); + map.put(SQLRestoreOPToken.FUNCTOR, RestoreStatementBuilder.INSTANCE); + map.put(SQLInsertOPToken.FUNCTOR, InsertStatementBuilder.INSTANCE); + map.put(SQLDeleteOPToken.FUNCTOR, DeleteStatementBuilder.INSTANCE); + map.put(SQLUpdateOPToken.FUNCTOR, UpdateStatementBuilder.INSTANCE); + map.put(SQLSelectOPToken.FUNCTOR, SelectStatementBuilder.INSTANCE); + map.put(SQLSourceOPToken.FUNCTOR, SourceStatementBuilder.INSTANCE); + map.put(SQLExplainOPToken.FUNCTOR, QueryRecorderStatementBuilder.EXPLAIN_INSTANCE); + map.put(SQLTraceOPToken.FUNCTOR, QueryRecorderStatementBuilder.TRACE_INSTANCE); + + // persistence commands + map.put(SQLListServicesOPToken.FUNCTOR, ListServicesStatementBuilder.INSTANCE); + map.put(SQLListSnapshotsOPToken.FUNCTOR, ListSnapshotsStatementBuilder.INSTANCE); + map.put(SQLListArchiverOPToken.FUNCTOR, ListArchiverStatementBuilder.INSTANCE); + map.put(SQLCreateSnapshotOPToken.FUNCTOR, CreateSnapshotStatementBuilder.INSTANCE); + map.put(SQLRecoverSnapshotOPToken.FUNCTOR, RecoverSnapshotStatementBuilder.INSTANCE); + map.put(SQLRemoveSnapshotOPToken.FUNCTOR, RemoveSnapshotStatementBuilder.INSTANCE); + map.put(SQLValidateSnapshotOPToken.FUNCTOR, ValidateSnapshotStatementBuilder.INSTANCE); + map.put(SQLArchiveSnapshotOPToken.FUNCTOR, ArchiveSnapshotStatementBuilder.INSTANCE); + map.put(SQLRetrieveSnapshotOPToken.FUNCTOR, RetrieveSnapshotStatementBuilder.INSTANCE); + map.put(SQLResumeServiceOPToken.FUNCTOR, ResumeServiceStatementBuilder.INSTANCE); + map.put(SQLSuspendServiceOPToken.FUNCTOR, SuspendServiceStatementBuilder.INSTANCE); + map.put(SQLForceRecoveryOPToken.FUNCTOR, ForceRecoveryStatementBuilder.INSTANCE); + + return map; + } + + /** + * Return the {@link StatementBuilder} for a given CohQL AST functor. + * The returned value may be null if no builder has been registered + * for a given functor. + * + * @param sFunctor the functor representing the statement who's builder + * should be returned + * + * @return the StatementBuilder for a given CohQL AST functor + */ + public StatementBuilder getStatementBuilder(String sFunctor) + { + return m_mapStatementBuilders.get(sFunctor); + } + + /** + * Realize an instance of the {@link Statement} that will execute the CohQL statement + * represented by the AST node. + * + * @param term the parsed AST node representing the CohQL statement + * @param context the {@link ExecutionContext} to use to realize the command + * @param listBindVars the indexed bind variables to use for the query + * @param namedBindVars the named bind variables to use for the query + * + * @return the Statement to execute the CohQL statement + * + * @throws CohQLException if there is an error building the statement + * or there are no registered builders for the statement + */ + public Statement prepareStatement(NodeTerm term, ExecutionContext context, + List listBindVars, ParameterResolver namedBindVars) + { + StatementBuilder bldrStatement = getStatementBuilder(term.getFunctor()); + + if (bldrStatement == null) + { + throw new CohQLException("Unknown translation tree: " + term.getFunctor()); + } + + return bldrStatement.realize(context, term, listBindVars, namedBindVars); + } + + /** + * Register the given {@link StatementBuilder} to the specified functor name. + * The specified StatementBuilder will then be used to build any + * {@link Statement} instances when the given functor is present in + * a query AST. + * + * @param sFunctor the functor to map the StatementBuilder to + * @param builder the StatementBuilder to be mapped + */ + public void addStatement(String sFunctor, StatementBuilder builder) + { + m_mapStatementBuilders.put(sFunctor, builder); + } + + /** + * Remove the {@link StatementBuilder} associated with the given functor. + *

+ * Note: removal of a functor may cause errors if subsequent queries + * refer to the functor. + * + * @param sFunctor the functor mapping to remove + * + * @return the removed StatementBuilder or null + */ + public StatementBuilder removeStatementBuilder(String sFunctor) + { + return m_mapStatementBuilders.remove(sFunctor); + } + + /** + * Remove all customisations that have been added to the CohQL language. + */ + public void clearCustomOperators() + { + m_tokenTableSQL = null; + m_tokenTableExtendedSQL = null; + m_tokenTableForFilter = null; + m_mapOperators = null; + m_mapStatementBuilders = null; + } + + /** + * Set the {@link ExtractorBuilder} that will be used by CohQL queries to + * build {@link com.tangosol.util.ValueExtractor}s. + * + * @param builder the ExtractorBuilder to use + */ + public void setExtractorBuilder(ExtractorBuilder builder) + { + m_bldrExtractor = builder == null ? new ReflectionExtractorBuilder() : builder; + } + + /** + * Return the {@link ExtractorBuilder} to use to build + * {@link com.tangosol.util.ValueExtractor}s. + * + * @return the ExtractorBuilder to use to build ValueExtractors + */ + public ExtractorBuilder getExtractorBuilder() + { + return m_bldrExtractor; + } + + + // ----- data members --------------------------------------------------- + + /** + * The TokenTable for Filters. + */ + protected TokenTable m_tokenTableForFilter; + + /** + * The TokenTable for the full language that resembles SQL. + */ + protected TokenTable m_tokenTableSQL; + + /** + * The extended TokenTable for the full language that resembles SQL. + */ + protected TokenTable m_tokenTableExtendedSQL; + + /** + * The map of CohQL functions. The key is the function name + * and the value is the function implementation. + */ + protected Map> m_mapFunctions; + + /** + * The map of CohQL operators. The key is the operator name + * and the value is the {@link BaseOperator} implementation. + */ + protected Map m_mapOperators; + + /** + * The map of CohQL query builders. The key is the CohQL token name + * that the parser produces to represent a particular query. + */ + protected Map> m_mapStatementBuilders; + + /** + * The {@link ExtractorBuilder} that will be used to realize + * {@link com.tangosol.util.ValueExtractor}s to be used by CohQL. + */ + protected ExtractorBuilder m_bldrExtractor = new ReflectionExtractorBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ExecutionContext.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ExecutionContext.java new file mode 100644 index 0000000000000..26d1ef2f3f4ee --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ExecutionContext.java @@ -0,0 +1,458 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import com.oracle.coherence.common.util.Duration; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.Cluster; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.DefaultCacheServer; + +import com.tangosol.util.Base; +import com.tangosol.util.ResourceRegistry; +import com.tangosol.util.SimpleResourceRegistry; + +import java.io.BufferedReader; +import java.io.PrintWriter; +import java.io.Reader; + +/** + * Instances of this context are passed to {@link Statement}s to allow + * commands to discern execution conditions, altering their behavior / result + * as needed. In addition, commands may wish to alter the execution conditions + * or hold state across executions; the latter is possible via {@link + * #getResourceRegistry()}. + * + * @author jk 2014.07.10 + * @since Coherence 12.2.1 + */ +public class ExecutionContext + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an ExecutionContext. + */ + public ExecutionContext() + { + f_resourceRegistry = new SimpleResourceRegistry(); + } + + + // ----- ExecutionContext methods ------------------------------------------------ + + /** + * Return the {@link ResourceRegistry} that may be used to register + * ad-hoc resources with this context as a way to maintain state between + * different command executions. + * + * @return the ResourceRegistry that may be used to register ad-hoc resources + */ + public ResourceRegistry getResourceRegistry() + { + return f_resourceRegistry; + } + + /** + * Set the current {@link ConfigurableCacheFactory} to be used by commands + * executed under this context. + * + * @param ccf the ConfigurableCacheFactory to be used by commands + * executed with this context + */ + public void setCacheFactory(ConfigurableCacheFactory ccf) + { + m_cacheFactory = ccf; + } + + /** + * Return the current {@link ConfigurableCacheFactory} to be used + * by commands executed under this context. + * + * @return the current ConfigurableCacheFactory + */ + public ConfigurableCacheFactory getCacheFactory() + { + if (m_cacheFactory == null) + { + m_cacheFactory = CacheFactory.getConfigurableCacheFactory(); + } + + return m_cacheFactory; + } + + /** + * Set the current {@link Cluster} to be used by commands + * executed under this context. + * + * @param cluster the Cluster to be used by commands + * executed with this context + */ + public void setCluster(Cluster cluster) + { + m_cluster = cluster; + } + + /** + * Return the current {@link Cluster} to be used + * by commands executed under this context. + * + * @return the current Cluster + */ + public Cluster getCluster() + { + if (m_cluster == null) + { + m_cluster = CacheFactory.ensureCluster(); + } + + return m_cluster; + } + + /** + * Set the {@link CoherenceQueryLanguage} that will be used by commands. + * + * @param language the CoherenceQueryLanguage to be used by commands + */ + public void setCoherenceQueryLanguage(CoherenceQueryLanguage language) + { + m_language = language; + } + + /** + * Return an instance of {@link CoherenceQueryLanguage} to be + * used by commands. + * + * @return the instance of CoherenceQueryLanguage to be + * used by commands + */ + public CoherenceQueryLanguage getCoherenceQueryLanguage() + { + return m_language; + } + + /** + * Set whether to display trace output when executing commands. + * + * @param fTrace whether to display trace output when executing commands + */ + public void setTraceEnabled(boolean fTrace) + { + m_fTrace = fTrace; + } + + /** + * Return whether trace to display output when executing commands. + * + * @return true if trace is enabled, otherwise returns false + */ + public boolean isTraceEnabled() + { + return m_fTrace; + } + + /** + * Set whether sanity checking is enabled when executing commands. + * + * @param fSanity whether sanity checking is enabled when executing commands + */ + public void setSanityCheckingEnabled(boolean fSanity) + { + m_fSanity = fSanity; + } + + /** + * Return whether sanity checking should be enabled when executing commands. + * + * @return whether sanity checking should be enabled when executing commands + */ + public boolean isSanityChecking() + { + return m_fSanity; + } + + /** + * Set whether "Extended Language" features are enabled. + * + * @param fExtendedLanguage whether "Extended Language" features are enabled + */ + public void setExtendedLanguage(boolean fExtendedLanguage) + { + m_fExtendedLanguage = fExtendedLanguage; + } + + /** + * Return whether "Extended Language" features are enabled. + * + * @return whether "Extended Language" features are enabled + */ + public boolean isExtendedLanguageEnabled() + { + return m_fExtendedLanguage; + } + + /** + * Set the {@link PrintWriter} that can be used by commands to display output. + * + * @param writer the PrintWriter that can be used by commands to display output + */ + public void setWriter(PrintWriter writer) + { + m_writer = writer; + } + + /** + * Return the {@link PrintWriter} that can be used by commands to display output. + * + * @return the PrintWriter that can be used by commands to display output + */ + public PrintWriter getWriter() + { + return m_writer; + } + + /** + * Return the flag indicating whether CohQL should stop processing multiple + * statements if an error occurs (for example when processing a file), or + * whether errors should be logged and statement processing continue. + * + * @return true if statement processing should stop when errors occur. + */ + public boolean isStopOnError() + { + return m_fStopOnError; + } + + /** + * Set the flag indicating whether CohQL should stop processing multiple + * statements if an error occurs (for example when processing a file), or + * whether errors should be logged and statement processing continue. + * + * @param fStopOnError true if statement processing should stop when + * errors occur + */ + public void setStopOnError(boolean fStopOnError) + { + m_fStopOnError = fStopOnError; + } + + /** + * Set the title that will be displayed as the results heading. + * + * @param sTitle the title that will be displayed as the + * results heading + */ + public void setTitle(String sTitle) + { + m_sTitle = sTitle; + } + + /** + * Return the String to use for title that heads each result. + * + * @return the String to use for title that heads each result + */ + public String getTitle() + { + return m_sTitle; + } + + /** + * Set the {@link StatementExecutor} to use to parse and + * execute statements. + * + * @param executor the StatementExecutor to use + */ + public void setStatementExecutor(StatementExecutor executor) + { + Base.azzert(executor != null); + m_executor = executor; + } + + /** + * Return the {@link StatementExecutor} to use to parse and + * execute statements. + * + * @return the StatementExecutor to use to parse and + * execute statements + */ + public StatementExecutor getStatementExecutor() + { + return m_executor; + } + + /** + * Instantiate an instance of an {@link OPParser} to parse + * the CohQL statements provided by the specified {@link Reader}. + * + * @param reader the {@link java.io.Reader} containing the statements to execute + * + * @return an instance of an OPParser to parse the CohQL statements + */ + public OPParser instantiateParser(Reader reader) + { + TokenTable tokenTable = m_fExtendedLanguage + ? m_language.extendedSqlTokenTable() + : m_language.sqlTokenTable(); + + return new OPParser(reader, tokenTable, m_language.getOperators()); + } + + /** + * Return true if the current query session is running in silent mode. + * + * @return true if the current session is running in silent mode + */ + public boolean isSilent() + { + return m_fSilent; + } + + /** + * Set the flag indicating that the QueryPlus session is running + * in silent mode. + * + * @param fSilent true to indicate that the QueryPlus session is + * running in silent mode. + */ + public void setSilentMode(boolean fSilent) + { + m_fSilent = fSilent; + } + + /** + * Return the {@link BufferedReader} that can be used to obtain user input, + * typically from {@link System#in}. + * + * @return Return the BufferedReader that can be used to obtain user input + */ + public BufferedReader getReader() + { + return m_reader; + } + + /** + * Set the {@link BufferedReader} that should be used to obtain user input. + * + * @param reader the BufferedReader to use to obtain user input + */ + public void setReader(BufferedReader reader) + { + m_reader = reader; + } + + /** + * Obtain the number of mill-seconds to wait for a {@link Statement} to execute + * before timing out. + * + * @return the number of mill-seconds to wait for a {@link Statement} to execute + * before timing out + */ + public Duration getTimeout() + { + return m_timeout; + } + + /** + * Set the {@link Duration} to wait for a {@link Statement} to execute + * before timing out. + * + * @param timeout the timeout duration + * + * @throws IllegalArgumentException if the timeout value is null + */ + public void setTimeout(Duration timeout) + { + if (timeout == null) + { + throw new IllegalArgumentException("Timeout duration cannot be null"); + } + + m_timeout = timeout; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link ResourceRegistry} used to store various resources + * used by queries managed by this context. + */ + protected final ResourceRegistry f_resourceRegistry; + + /** + * The {@link ConfigurableCacheFactory} to use to access caches. + */ + protected ConfigurableCacheFactory m_cacheFactory; + + /** + * The {@link Cluster} to use to access services. + */ + protected Cluster m_cluster; + + /** + * Flag that indicates tracing mode. + */ + protected boolean m_fTrace; + + /** + * A flag that enables sanity checking. + */ + protected boolean m_fSanity = true; + + /** + * Flag that controls whether we except Map, and List as literals. + * This gives a json like feel to the language. + */ + protected boolean m_fExtendedLanguage = false; + + /** + * The {@link CoherenceQueryLanguage} to use. + */ + protected CoherenceQueryLanguage m_language; + + /** + * The {@link PrintWriter} that can be used by commands to display output. + */ + protected PrintWriter m_writer; + + /** + * A flag indicating when true that CohQL should stop processing multiple + * statements if an error occurs (for example when processing a file), or + * when false that errors will be logged and statement processing will continue. + */ + protected boolean m_fStopOnError = false; + + /** + * String to use for Title that heads each result displayed. + */ + protected String m_sTitle = "Results"; + + /** + * The {@link StatementExecutor} to use to execute statements. + */ + protected StatementExecutor m_executor = new StatementExecutor(); + + /** + * A flag indicating whether the query session is running in silent mode. + */ + protected boolean m_fSilent = false; + + /** + * The reader to use to get user input. + */ + protected BufferedReader m_reader; + + /** + * The number of milli-seconds to wait for a {@link Statement} to execute + * before timing out. + */ + protected Duration m_timeout = new Duration(Long.MAX_VALUE); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ExtractorBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ExtractorBuilder.java new file mode 100644 index 0000000000000..dd73e0f355b7d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ExtractorBuilder.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import com.tangosol.util.ValueExtractor; + +/** + * ExtractorBuilders provide a mechanism to construct a {@link ValueExtractor} + * for a provided cache name, target and property chain ({@link + * #realize(String, int, String)}). + *

+ * To determine the appropriate ValueExtractor implementations may + * need to decipher cache type information, which should be discernible from + * the provided cache name and {@code nTarget} ({@link + * com.tangosol.util.extractor.AbstractExtractor#KEY KEY} or {@link + * com.tangosol.util.extractor.AbstractExtractor#VALUE VALUE}). The property + * chain {@code (sProperties)} represents a chain of calls from the root type + * (key or value) down the type hierarchy. + *

+ * Implementations may be able to optimize the ValueExtractors used by CohQL + * by providing a mapping of a logical property name to a ValueExtractor that + * can optimally (without deserializing the entire key or value) extract the + * relevant property. For example, an implementation able to map properties + * to POF indices could convert a property chain to a POF path. Assuming a + * Person object is the value stored in a cache the table below illustrates + * possible implementations: + * + * + * + * + * + * + * + * + * + * + * + * + *
Implementation Examples
Property ChainUnoptimizedOptimized
{@code value().address.homeTel.areaCode}{@code ChainedExtractor(ReflectionExtractor(getAddress), + * ReflectionExtractor(getHomeTel), ReflectionExtractor(getAreaCode))}{@code PofExtractor(PofNavigator(2, 5, 7))}
+ * + * @author jk 2014.07.10 + * @since Coherence 12.2.1 + * + * @see ValueExtractor + */ +public interface ExtractorBuilder + { + /** + * Create a {@link ValueExtractor} for the given cache name, target and property chain. + * + * @param sCacheName the name of the cache the ValueExtractor will be invoked against + * @param nTarget the target for the ValueExtractor + * @param sProperties the path to the property value to extract + * + * @return a {@link ValueExtractor} for the given cache name, target and properties + */ + public ValueExtractor realize(String sCacheName, int nTarget, String sProperties); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/FilterBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/FilterBuilder.java new file mode 100644 index 0000000000000..ec0b9e5365249 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/FilterBuilder.java @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import com.tangosol.coherence.config.ParameterList; +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.SimpleParameterList; + +import com.tangosol.coherence.dslquery.internal.AbstractCoherenceQueryWalker; +import com.tangosol.coherence.dslquery.internal.ConstructorQueryWalker; + +import com.tangosol.coherence.dslquery.operator.BaseOperator; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +import com.tangosol.config.expression.NullParameterResolver; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.Filter; +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.extractor.AbstractExtractor; +import com.tangosol.util.extractor.ReflectionExtractor; + +import com.tangosol.util.filter.AlwaysFilter; +import com.tangosol.util.filter.NeverFilter; +import com.tangosol.util.filter.NotFilter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * FilterBuilder is a visitor class that converts a given Abstract Syntax + * Tree into a Filter. The Filter can be a deep nesting of Filters. + * + * @author djl 2009.08.31 + * @author jk 2013.12.02 + */ +public class FilterBuilder + extends AbstractCoherenceQueryWalker + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new FilterBuilder. + */ + public FilterBuilder() + { + this(Collections.emptyList(), new NullParameterResolver(), new CoherenceQueryLanguage()); + } + + /** + * Construct a new FilterBuilder. + * + * @param language the {@link CoherenceQueryLanguage} instance to use + */ + public FilterBuilder(CoherenceQueryLanguage language) + { + this(Collections.emptyList(), new NullParameterResolver(), language); + } + + /** + * Construct a new FilterBuilder with given binding environment. + * + * @param aoBindVars an Object array of indexed bind variables + */ + public FilterBuilder(Object[] aoBindVars) + { + this(Arrays.asList(aoBindVars), new NullParameterResolver(), new CoherenceQueryLanguage()); + } + + /** + * Construct a new FilterBuilder that can construct a Filter from the + * given Term. + * + * @param term the Term to use in the construction of a filter + */ + public FilterBuilder(Term term) + { + this(); + m_term = term; + } + + /** + * Construct a new FilterBuilder with given binding environment. + * + * @param aoBindVars indexed bind variables + * @param mapNamedBindVars named bind variables + */ + public FilterBuilder(Object[] aoBindVars, Map mapNamedBindVars) + { + this(Arrays.asList(aoBindVars), + new ResolvableParameterList(newParameterListFromMap(mapNamedBindVars)), + new CoherenceQueryLanguage()); + } + + /** + * Construct a new FilterBuilder with given default bind variables. + * + * @param indexedBindVars the indexed bind variables + * @param namedBindVars the named bind variables + * @param language the {@link CoherenceQueryLanguage} instance to use + */ + public FilterBuilder(List indexedBindVars, + ParameterResolver namedBindVars, + CoherenceQueryLanguage language) + { + super(indexedBindVars, namedBindVars, language); + f_listDefaultBindVars = m_listBindVars; + f_defaultNamedBindVars = m_namedBindVars; + } + + // ----- Filter construction API ---------------------------------------- + + /** + * Make a new Filter from the set AST. + * + * @return the constructed Filter + */ + public Filter makeFilter() + { + return makeFilter(m_term, f_listDefaultBindVars, f_defaultNamedBindVars); + } + + /** + * Make a new Filter from the given AST. + * + * @param term the AST to turn into a Filter + * + * @return the constructed Filter + */ + public Filter makeFilter(Term term) + { + return makeFilter(term, f_listDefaultBindVars, f_defaultNamedBindVars); + } + + /** + * Make a new Filter from the given AST using given array for Bind vars. + * + * @param term the AST to turn into a Filter + * @param aoIndexedBindVars the array of Objects to use for Bind vars + * + * @return the constructed Filter + */ + public Filter makeFilter(Term term, Object[] aoIndexedBindVars) + { + return makeFilter(term, Arrays.asList(aoIndexedBindVars), f_defaultNamedBindVars); + } + + /** + * Make a new Filter from the given AST using the given bind variables. + * + * @param term the AST to turn into a Filter + * @param aoIndexedBindVars the array of Objects to use for bind variables + * @param mapNamedBindVars the named bind variables to use + * + * @return the constructed Filter + */ + public Filter makeFilter(Term term, Object[] aoIndexedBindVars, Map mapNamedBindVars) + { + return makeFilter(term, Arrays.asList(aoIndexedBindVars), + new ResolvableParameterList((mapNamedBindVars))); + } + + /** + * Make a new Filter from the given AST using the given bind variables. + * + * @param term the AST to turn into a Filter + * @param listBindVars the indexed bind variables + * @param namedBindVars the named bind variables + * + * @return the constructed Filter + */ + public Filter makeFilter(Term term, List listBindVars, ParameterResolver namedBindVars) + { + return makeFilterForCache(null, term, listBindVars, namedBindVars); + } + + /** + * Make a new Filter from the given AST using given array for Bind vars. + * + * @param sCacheName the name of the cache the Filter is to query + * @param term the AST to turn into a Filter + * @param indexedBindVars the indexed bind variables to use + * @param namedBindVars the named bind variables to use + * + * @return the constructed Filter + */ + public Filter makeFilterForCache(String sCacheName, Term term, List indexedBindVars, + ParameterResolver namedBindVars) + { + m_sCacheName = sCacheName; + m_term = term; + m_listBindVars = indexedBindVars == null + ? f_listDefaultBindVars + : indexedBindVars; + m_namedBindVars = namedBindVars == null + ? f_defaultNamedBindVars + : namedBindVars; + + setResult(null); + + m_term.accept(this); + + Object oResult = getResult(); + if (oResult instanceof Filter) + { + return (Filter) oResult; + } + + if (oResult instanceof Boolean) + { + return ((Boolean) oResult).booleanValue() + ? AlwaysFilter.INSTANCE + : NeverFilter.INSTANCE; + } + + throw new RuntimeException("Filter not specified. " + oResult + " Found instead!"); + } + + /** + * Process the AST Tree using the given Term that represents getter. + * + * @param term the AST used + * + * @return the resulting ValueExtractor + */ + public ValueExtractor makeExtractor(NodeTerm term) + { + m_term = term; + + setResult(null); + term.accept(this); + + return (ValueExtractor) getResult(); + } + + // ----- DefaultCoherenceQueryWalker API --------------------------------------- + + @Override + protected void acceptList(NodeTerm termList) + { + int cTerms = termList.length(); + Object[] ao = new Object[cTerms]; + + for (int i = 1; i <= cTerms; i++) + { + termList.termAt(i).accept(this); + ao[i - 1] = getResult(); + } + + setResult(ao); + } + + @Override + protected void acceptIdentifier(String sIdentifier) + { + if (acceptIdentifierInternal(sIdentifier)) + { + return; + } + + ExtractorBuilder builder = f_language.getExtractorBuilder(); + + setResult(builder.realize(m_sCacheName, AbstractExtractor.VALUE, sIdentifier)); + } + + /** + * This method will take a Binary Operator and the left and right {@link Term}s + * for the operator and result in the creation of a {@link Filter} or a {@link ValueExtractor}. + */ + @Override + protected void acceptBinaryOperator(String sOperator, Term termLeft, Term termRight) + { + BaseOperator operator = f_language.getOperator(sOperator); + + if (operator == null) + { + throw new RuntimeException("Cannot build filter from unknown operator " + sOperator); + } + + if (operator.isConditional()) + { + setResult(operator.makeFilter(termLeft, termRight, this)); + } + else + { + setResult(operator.makeExtractor(termLeft, termRight, this)); + } + } + + @Override + protected void acceptUnaryOperator(String sOperator, Term t) + { + switch (sOperator) + { + case "new" : + ConstructorQueryWalker walker = new ConstructorQueryWalker(m_listBindVars, m_namedBindVars, f_language); + + t.accept(walker); + setResult(reflectiveMakeObject(true, walker.getResult())); + break; + + case "!" : + t.accept(this); + NotFilter filter = new NotFilter((Filter) getResult()); + setResult(filter); + break; + + case "-" : + t.accept(this); + + AtomicTerm atomicTerm = m_atomicTerm; + if (atomicTerm.isNumber()) + { + Number number = atomicTerm.negativeNumber((Number) getResult()); + setResult(number); + } + break; + + case "+" : + t.accept(this); + break; + + default : + setResult(null); + break; + } + } + + @Override + protected void acceptPath(NodeTerm term) + { + acceptPathAsChainedExtractor(m_sCacheName, term); + } + + + // ----- static helpers ------------------------------------------------- + + /** + * Convert the given {@link Map} into a {@link ParameterList}. + * The key of the map is used as the parameter name and the + * corresponding value as the parameter value. + * + * @param map the Map to convert to a ParameterList + * + * @return a ParameterList made up of the contents of the specified Map + */ + protected static ParameterList newParameterListFromMap(Map map) + { + SimpleParameterList list = new SimpleParameterList(); + + for (Map.Entry entry : map.entrySet()) + { + list.add(new Parameter(String.valueOf(entry.getKey()), entry.getValue())); + } + + return list; + } + + // ----- data members --------------------------------------------------- + + /** + * The Term that is the AST that encodes the Filter to be made. + */ + protected Term m_term; + + /** + * The cache name this FilterBuilder is building {@link Filter}s to query + * against. + *

+ * This is used by the {@link CoherenceQueryLanguage language's} {@link + * ExtractorBuilder} to map attribute names to POF indices. A null cache + * name results in the use of {@link ReflectionExtractor}s. + */ + protected String m_sCacheName; + + /** + * The default indexed bind variables. + */ + protected final List f_listDefaultBindVars; + + /** + * The default named bind variables. + */ + protected final ParameterResolver f_defaultNamedBindVars; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/QueryPlus.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/QueryPlus.java new file mode 100644 index 0000000000000..ed20336a8c239 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/QueryPlus.java @@ -0,0 +1,1094 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import com.oracle.coherence.common.util.Duration; +import com.tangosol.coherence.dslquery.queryplus.AbstractQueryPlusStatementBuilder; +import com.tangosol.coherence.dslquery.queryplus.CommandsStatementBuilder; +import com.tangosol.coherence.dslquery.queryplus.ExtendedLanguageStatementBuilder; +import com.tangosol.coherence.dslquery.queryplus.HelpStatementBuilder; +import com.tangosol.coherence.dslquery.queryplus.SanityCheckStatementBuilder; +import com.tangosol.coherence.dslquery.queryplus.ServicesStatementBuilder; +import com.tangosol.coherence.dslquery.queryplus.SetTimeoutStatementBuilder; +import com.tangosol.coherence.dslquery.queryplus.TraceStatementBuilder; +import com.tangosol.coherence.dslquery.queryplus.WheneverStatementBuilder; + +import com.tangosol.coherence.dslquery.token.SQLPeekOPToken; + +import com.tangosol.coherence.dsltools.precedence.PeekOPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.dev.tools.CommandLineTool; + +import com.tangosol.util.Base; +import com.tangosol.util.ClassHelper; +import com.tangosol.util.ListMap; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringReader; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * QueryPlus implements a simple command line processor for a sql like + * language. + * + * @author djl 2009.08.31 + * @author jk 2014.01.02 + */ +public class QueryPlus + { + // ----- constructors --------------------------------------------------- + + /** + * Create a QueryPlus instance that uses the specified {@link Dependencies}. + * + * @param dependencies the Dependencies that will control the QueryPlus session + */ + public QueryPlus(Dependencies dependencies) + { + Base.azzert(dependencies != null); + f_dependencies = dependencies; + f_executor = dependencies.getStatementExecutor(); + f_context = new ExecutionContext(); + + f_context.setTimeout(dependencies.getTimeout()); + f_context.setTraceEnabled(dependencies.isTraceEnabled()); + f_context.setSanityCheckingEnabled(dependencies.isSanityChecking()); + f_context.setExtendedLanguage(dependencies.isExtendedLanguageEnabled()); + f_context.setWriter(dependencies.getOutputWriter()); + f_context.setCoherenceQueryLanguage(dependencies.getCoherenceQueryLanguage()); + f_context.setTitle(dependencies.getTitle()); + f_context.setSilentMode(dependencies.isSilent()); + f_context.setReader(dependencies.getReader()); + + initializeLanguage(); + } + + /** + * Run this instance of QueryPlus. + */ + public void run() + { + PrintWriter out = f_dependencies.getOutputWriter(); + String sGarFile = f_dependencies.getGarFileName(); + + // execute any statements contained in the dependencies + for (String sStatement : f_dependencies.getStatements()) + { + if (!evalLine(sStatement)) + { + break; + } + } + + // execute any statement script files contained in the dependencies + for (String sFile : f_dependencies.getFiles()) + { + if (!processFile(sFile)) + { + break; + } + } + + out.flush(); + + if (f_dependencies.isExitWhenProcessingComplete()) + { + return; + } + + if (!f_context.isSilent()) + { + out.println("Coherence Command Line Tool"); + out.flush(); + } + + repl(); + } + + /** + * Start a statement processing loop. + */ + public void repl() + { + BufferedReader reader = f_context.getReader(); + PrintWriter writer = f_context.getWriter(); + boolean fSilent = f_context.isSilent(); + boolean fWorking = true; + + while (fWorking) + { + try + { + if (!fSilent) + { + writer.println(); + writer.print("CohQL> "); + } + + writer.flush(); + + String sLine = reader.readLine(); + + fWorking = sLine != null && evalLine(sLine); + } + catch (IOException e) + { + if (fSilent) + { + return; + } + else + { + writer.println("\n" + e.getMessage()); + } + } + } + } + + // ----- helper methods ------------------------------------------------- + + /** + * Return the current {@link ExecutionContext} passed to {@link Statement}s. + * + * @return the current ExecutionContext passed to Statements + */ + public ExecutionContext getExecutionContext() + { + return f_context; + } + + /** + * Initialize the {@link CoherenceQueryLanguage} with any QueryPlus + * {@link Statement} extensions. + */ + protected void initializeLanguage() + { + // Add the QueryPlus "commands" statement to CohQL + addStatement(new CommandsStatementBuilder()); + + // Add the QueryPlus "extended language" statement to CohQL + addStatement(new ExtendedLanguageStatementBuilder()); + + // Add the QueryPlus "help" statement to CohQL + addStatement(new HelpStatementBuilder()); + + // Add the QueryPlus "sanity check (on/off)" statement to CohQL + addStatement(new SanityCheckStatementBuilder()); + + // Add the QueryPlus "services" statement to CohQL + addStatement(new ServicesStatementBuilder()); + + // Add the QueryPlus "trace on/off" statement to CohQL + CoherenceQueryLanguage language = f_context.getCoherenceQueryLanguage(); + addStatement(new TraceStatementBuilder(language.sqlTokenTable().lookup("trace"))); + + // Add the QueryPlus "whenever" statement to CohQL + addStatement(new WheneverStatementBuilder()); + + // Add the QueryPlus "alter session" statement to CohQL + addAlterSessionStatement(); + } + + /** + * Add a new QueryPlus statement. + * + * @param builder the statement builder to add + */ + protected void addStatement(AbstractQueryPlusStatementBuilder builder) + { + AbstractQueryPlusStatementBuilder.AbstractOPToken token = builder.instantiateOpToken(); + CoherenceQueryLanguage language = f_context.getCoherenceQueryLanguage(); + + language.addStatement(token.getFunctor(), builder); + language.extendedSqlTokenTable().addToken(token); + language.sqlTokenTable().addToken(token); + } + + /** + * Add the QueryPlus ALTER SESSION statements + */ + protected void addAlterSessionStatement() + { + CoherenceQueryLanguage language = f_context.getCoherenceQueryLanguage(); + SetTimeoutStatementBuilder bldrTimeout = new SetTimeoutStatementBuilder(); + AbstractQueryPlusStatementBuilder.AbstractOPToken tokenTimeout = bldrTimeout.instantiateOpToken(); + + // Add the SET TIMEOUT statement to the CohQL language + language.addStatement(tokenTimeout.getFunctor(), bldrTimeout); + + // Add the tokens to the language + SQLPeekOPToken tokenSet = new SQLPeekOPToken("set", tokenTimeout); + + SQLPeekOPToken tokenSession = new SQLPeekOPToken("session", tokenSet); + + TokenTable tokenTableExt = language.extendedSqlTokenTable(); + PeekOPToken tokenExtAlter = (PeekOPToken) tokenTableExt.lookup("alter"); + tokenExtAlter.addOPToken(tokenSession); + + TokenTable tokenTableSql = language.sqlTokenTable(); + PeekOPToken tokenSqlAlter = (PeekOPToken) tokenTableSql.lookup("alter"); + tokenSqlAlter.addOPToken(tokenSession); + } + + /** + * Process the specified query. + * + * @param sQuery a String that represents the query + * + * @return the results of the query + */ + protected Object query(String sQuery) + { + return f_context.getStatementExecutor().execute(new StringReader(sQuery), f_context); + } + + /** + * Process the given file of CohQL statements. + * + * @param sFileName the name of the file CohQL containing the + * statements to execute + * + * @return true if all statements in the file were processed or + * false if an error occurred + */ + protected boolean processFile(String sFileName) + { + String sLine = "@ " + "'" + sFileName.trim() + "'"; + boolean fSavedSilent = f_context.isSilent(); + + f_context.setSilentMode(true); + + try + { + f_context.getStatementExecutor().execute(new StringReader(sLine), f_context); + } + catch (Exception e) + { + PrintWriter out = f_context.getWriter(); + + out.println(e.getMessage()); + + if (f_context.isTraceEnabled()) + { + e.printStackTrace(out); + } + + if (f_context.isStopOnError()) + { + return false; + } + } + finally + { + f_context.setSilentMode(fSavedSilent); + } + + return true; + } + + /** + * Evaluate the given CohQL statement. + * + * @param sLine the CohQL statement String to be evaluated + * + * @return a flag indicating whether to continue processing statements + */ + protected boolean evalLine(String sLine) + { + try + { + if (sLine.trim().isEmpty()) + { + return true; + } + + if (sLine.equals("quit") || sLine.equals("bye")) + { + return false; + } + + if (sLine.startsWith(".")) + { + String ln = sLine.substring(1).trim(); + + sLine = "@ " + "'" + ln + "'"; + } + + query(sLine); + } + catch (Exception e) + { + PrintWriter writer = f_context.getWriter(); + + writer.println(e.getMessage()); + + if (f_context.isTraceEnabled()) + { + e.printStackTrace(writer); + } + + if(f_context.isStopOnError()) + { + return false; + } + } + + return true; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Return an instance of BufferedReader if JLine is present. + * + * @param output the {@link OutputStream} that will provide output + * @param input the {@link InputStream} that will provide input + * @param fSilent if true no message will be displayed if JLine is unavailable. + * + * @return an instance of BufferedReader or null + */ + public static BufferedReader getJlineReader(OutputStream output, InputStream input, boolean fSilent) + { + try + { + Class clzJLineReader = Class.forName("jline.console.ConsoleReader"); + final Object jlineReader = ClassHelper.newInstance(clzJLineReader, + new Object[]{input, output}); + + File fileHistory = new File(".cohql-history"); + if (!fileHistory.exists()) + { + fileHistory.createNewFile(); + } + + Class clzJlineHistory = Class.forName("jline.console.history.FileHistory"); + final Object oHistory = ClassHelper.newInstance(clzJlineHistory, new Object[] {fileHistory}); + + ClassHelper.invoke(jlineReader, "setHistory", new Object[] {oHistory}); + ClassHelper.invoke(jlineReader, "setExpandEvents", new Object[] {Boolean.FALSE}); + + return new BufferedReader(new InputStreamReader(input)) + { + public String readLine() + throws IOException + { + try + { + String sLine = (String) ClassHelper.invoke(jlineReader, "readLine", ClassHelper.VOID); + ClassHelper.invoke(oHistory, "flush", ClassHelper.VOID); + return sLine; + } + catch (Throwable e) + { + throw Base.ensureRuntimeException(e); + } + } + }; + } + catch (Exception e) // IOException, ClassNotFoundException, etc. + { + if (!fSilent) + { + PrintWriter writer = new PrintWriter(output); + writer.println("jline library cannot be loaded, so you cannot " + + "use the arrow keys for line editing and history."); + } + return null; + } + } + + /** + * The main application for CohQL statement processing. + * + * @param asArgs an array of Strings that represents arguments + */ + public static void main(String[] asArgs) + { + PrintWriter writer = new PrintWriter(System.out); + CoherenceQueryLanguage language = new CoherenceQueryLanguage(); + QueryPlus.Dependencies deps = DependenciesHelper.newInstance(writer, System.in, language, asArgs); + + new QueryPlus(deps).run(); + } + + // ----- inner interface: Dependencies ---------------------------------- + + /** + * The Dependencies for QueryPlus. + */ + public interface Dependencies + { + /** + * Return an instance of {@link CoherenceQueryLanguage} to be + * used by the QueryPlus session. + * + * @return the instance of CoherenceQueryLanguage to be + * used by the QueryPlus session + */ + public CoherenceQueryLanguage getCoherenceQueryLanguage(); + + /** + * Return an instance of a {@link PrintWriter} that should be + * used to display query output. + * + * @return the current PrintWriter to be used to display output + */ + public PrintWriter getOutputWriter(); + + /** + * Return whether trace is enabled. Enabling trace displays verbose + * output when executing statements. + * + * @return true if trace is enabled, otherwise returns false + */ + public boolean isTraceEnabled(); + + /** + * Return whether sanity checking should be enabled when executing statements. + * + * @return whether sanity checking should be enabled when executing statements + */ + public boolean isSanityChecking(); + + /** + * Return whether "Extended Language" features are enabled. + * + * @return whether "Extended Language" features are enabled + */ + public boolean isExtendedLanguageEnabled(); + + /** + * Return whether the QueryPlus session should exit once all of the statements + * added to the statements list have been executed. + * + * @return whether the QueryPlus session should exit once all of the statements + * added to the statements list have been executed + */ + public boolean isExitWhenProcessingComplete(); + + /** + * Return the list of statements that should be executed prior to the + * start of the CohQL session. + * + * @return the list of statements that should be executed prior to the + * start of the CohQL session + */ + public List getStatements(); + + /** + * Return the list of statement script files that should be executed prior to the + * start of the CohQL session. + * + * @return the list of statements that should be executed prior to the + * start of the CohQL session + */ + public List getFiles(); + + /** + * Return true if the current query session is running in silent mode. + * + * @return true if the current session is running in silent mode + */ + public boolean isSilent(); + + /** + * Return the String to use for title that heads each result. + * + * @return the String to use for title that heads each result + */ + public String getTitle(); + + /** + * Return the {@link BufferedReader} to use to obtain user input. + * + * @return the BufferedReader to use to obtain user input + */ + public BufferedReader getReader(); + + /** + * Return the {@link StatementExecutor} to use to parse and + * execute statements. + * + * @return the StatementExecutor to use to parse and + * execute statements + */ + public StatementExecutor getStatementExecutor(); + + /** + * Return the name of the optional GAR file to load + * before running QueryPlus. + * + * @return the name of the optional GAR file to load + * before running QueryPlus + */ + public String getGarFileName(); + + /** + * Return the optional application name to use if + * loading a GAR file. + * + * @return the optional application name to use if + * loading a GAR file + */ + public String getApplicationName(); + + /** + * Return the optional array of domain partition names + * to use if loading a GAR file. + * + * @return the optional array of domain partition names + * to use if loading a GAR file + */ + public String[] getDomainPartitions(); + + /** + * Return the initial value that will be set as the + * CohQL statement timeout. + * + * @return the initial value that will be set as the + * CohQL statement timeout. + */ + public Duration getTimeout(); + } + + // ----- inner class: DefaultDependencies ------------------------------- + + /** + * A default implementation of {@link QueryPlus.Dependencies}. + */ + public static class DefaultDependencies + implements Dependencies + { + // ----- constructors ----------------------------------------------- + + /** + * Create a DefaultDependencies instance that will use the specified + * {@link PrintWriter} and {@link BufferedReader} for output and input. + * + * @param writer the PrintWriter to use to display output + * @param reader the reader to obtain user input + * @param language an instance of CoherenceQueryLanguage + */ + public DefaultDependencies(PrintWriter writer, BufferedReader reader, CoherenceQueryLanguage language) + { + f_writer = writer; + m_reader = reader; + f_language = language; + } + + // ----- QueryPlus.Dependencies interface --------------------------- + + @Override + public CoherenceQueryLanguage getCoherenceQueryLanguage() + { + return f_language; + } + + @Override + public PrintWriter getOutputWriter() + { + return f_writer; + } + + /** + * Set whether trace logging is enabled. + * + * @param fTraceEnabled is trace logging enabled + */ + public void setTraceEnabled(boolean fTraceEnabled) + { + m_fTraceEnabled = fTraceEnabled; + } + + @Override + public boolean isTraceEnabled() + { + return m_fTraceEnabled; + } + + /** + * Set whether sanity checking is enabled. + * + * @param fSanity is sanity checking enabled + */ + public void setSanityCheckingEnabled(boolean fSanity) + { + m_fSanity = fSanity; + } + + @Override + public boolean isSanityChecking() + { + return m_fSanity; + } + + /** + * Set whether extended language features should be enabled. + * + * @param fExtendedLanguage whether extended language features should be enabled + */ + public void setExtendedLanguage(boolean fExtendedLanguage) + { + m_fExtendedLanguage = fExtendedLanguage; + } + + @Override + public boolean isExtendedLanguageEnabled() + { + return m_fExtendedLanguage; + } + + /** + * Set the flag that indicates the QueryPlus process should exit + * after processing the statements from the command line. + * + * @param fExit true if QueryPlus should exit after processing the + * command line statements + */ + public void setExitWhenProcessingComplete(boolean fExit) + { + m_fExitWhenProcessingComplete = fExit; + } + + @Override + public boolean isExitWhenProcessingComplete() + { + return m_fExitWhenProcessingComplete; + } + + /** + * Set the list of statements to execute before the QueryPlus + * session starts. + * + * @param listStatements the list of statements to execute before the + * QueryPlus session starts + */ + public void setStatements(List listStatements) + { + m_listStatements = listStatements; + } + + @Override + public List getStatements() + { + return m_listStatements; + } + + /** + * Set the list of statement files to execute before the QueryPlus + * session starts. + * + * @param listFiles the list of flies of QueryPlus statements + * to execute before the QueryPlus session starts. + */ + public void setFiles(List listFiles) + { + m_listFiles = listFiles; + } + + @Override + public List getFiles() + { + return m_listFiles; + } + + @Override + public boolean isSilent() + { + return m_fSilent; + } + + /** + * Set the flag indicating that the QueryPlus session is running + * in silent mode. + * + * @param fSilent true to indicate that the QueryPlus session is + * running in silent mode. + */ + public void setSilentMode(boolean fSilent) + { + m_fSilent = fSilent; + } + + /** + * Set the title that will be displayed as the results heading. + * + * @param sTitle the title that will be displayed as the + * results heading + */ + public void setTitle(String sTitle) + { + m_sTitle = sTitle; + } + + @Override + public String getTitle() + { + return m_sTitle; + } + + @Override + public BufferedReader getReader() + { + return m_reader; + } + + /** + * Set the {@link StatementExecutor} to use to parse and + * execute statements. + * + * @param executor the StatementExecutor to use + */ + public void setStatementExecutor(StatementExecutor executor) + { + Base.azzert(executor != null); + m_executor = executor; + } + + @Override + public StatementExecutor getStatementExecutor() + { + return m_executor; + } + + /** + * Set the name of the GAR file to load before + * starting the QueryPlus session. This name + * should point to an existing GAR file or an + * exploded GAR file directory. + * + * @param sGarFile the name of the GAR file to load + */ + public void setGarFileName(String sGarFile) + { + m_sGarFileName = sGarFile; + } + + @Override + public String getGarFileName() + { + return m_sGarFileName; + } + + /** + * Set the application name to use. This name only applies + * if the {@link #m_sGarFileName} has also been set. + * + * @param sApplicationName the application name to use + */ + public void setApplicationName(String sApplicationName) + { + m_sApplicationName = sApplicationName; + } + + @Override + public String getApplicationName() + { + return m_sApplicationName; + } + + /** + * Set the array of domain partition names to use. + * This list only applies if the {@link #m_sGarFileName} + * has also been set. + * + * @param asDomainPartitions the comma delimited list of domain partition names + */ + public void setDomainPartitions(String[] asDomainPartitions) + { + m_sDomainPartitions = asDomainPartitions; + } + + @Override + public String[] getDomainPartitions() + { + return m_sDomainPartitions; + } + + /** + * Set the timeout value for CohQL statement execution. + * + * @param timeout timeout value for CohQL statement execution + */ + public void setTimeout(Duration timeout) + { + m_timeout = timeout; + } + + @Override + public Duration getTimeout() + { + return m_timeout; + } + + // ----- data members ----------------------------------------------- + + /** + * The {@link CoherenceQueryLanguage} to use. + */ + protected final CoherenceQueryLanguage f_language; + + /** + * Flag that indicates tracing mode. + */ + protected boolean m_fTraceEnabled; + + /** + * A flag that enables sanity checking. + */ + protected boolean m_fSanity = false; + + /** + * Flag that controls whether we except Map, and List as literals. + * This gives a json like feel to the language. + */ + protected boolean m_fExtendedLanguage = false; + + /** + * The {@link PrintWriter} to use to display output. + */ + protected final PrintWriter f_writer; + + /** + * A flag indicating whether the query session is running in silent mode. + */ + protected boolean m_fSilent = false; + + /** + * String to use for Title that heads each result displayed. + */ + protected String m_sTitle = "Results"; + + /** + * A flag indicating whether a CohQL session should be exited when the list of + * statements has been executed. + */ + protected boolean m_fExitWhenProcessingComplete; + + /** + * A list of statements to execute when the CohQL session starts. + */ + protected List m_listStatements = new LinkedList<>(); + + /** + * A list of statement script files to execute when the CohQL session starts. + */ + protected List m_listFiles = new LinkedList<>(); + + /** + * The {@link BufferedReader} to use to obtain user input. + */ + protected BufferedReader m_reader; + + /** + * The {@link StatementExecutor} to use to execute statements. + */ + protected StatementExecutor m_executor = new StatementExecutor(); + + /** + * The name of an optional GAR file to load. + */ + protected String m_sGarFileName; + + /** + * An optional application name to use. This is only used in combination + * with the GAR file named in {@link #m_sGarFileName}. + */ + protected String m_sApplicationName; + + /** + * A comma delimited list of domain partition names. This is only used in + * combination with the GAR file named in {@link #m_sGarFileName}. + */ + protected String[] m_sDomainPartitions; + + /** + * The timeout value to use for CohQL statement execution. + */ + protected Duration m_timeout = new Duration(1, Duration.Magnitude.MINUTE); + } + + // ----- inner class: DependenciesHelper -------------------------------- + + /** + * The DependenciesHelper provides helper method for constructing + * {@link Dependencies} implementations for {@link QueryPlus}. + */ + public static class DependenciesHelper + { + /** + * Create a new instance of {@link Dependencies}. + *

+ * If the JLine library is present on the classpath and the -nojline argument + * is not passed in the asArgs array then the specified {@link InputStream} + * will be wrapped in a jline.ConsoleReaderInputStream. + * + * @param writer the PrintWriter to use to display output + * @param inputStream the InputStream that will be used to supply input to QueryPlus + * @param asArgs the command line arguments to use to configure the dependencies + * @param language the instance of {link CoherenceQueryLanguage} to be used by QueryPlus + * + * @return a new instance of Dependencies + */ + public static Dependencies newInstance(PrintWriter writer, InputStream inputStream, + CoherenceQueryLanguage language, String[] asArgs) + { + String[] asValidArgs = new String[]{"c", "e", "extend", "f", "l", "s", "t", "trace", "nojline", + "g", "a", "dp", "timeout"}; + + try + { + ListMap map = CommandLineTool.parseArguments(asArgs, asValidArgs, false); + boolean fExitOnCompletion = map.containsKey("c"); + boolean fSilent = map.containsKey("s"); + BufferedReader reader = null; + + if (!fExitOnCompletion && !map.containsKey("nojline")) + { + reader = getJlineReader(System.out, inputStream, fSilent); + } + + if (reader == null) + { + reader = new BufferedReader(new InputStreamReader(inputStream)); + } + + DefaultDependencies deps = new DefaultDependencies(writer, reader, language); + + deps.setExitWhenProcessingComplete(fExitOnCompletion); + deps.setExtendedLanguage(map.containsKey("e") || map.containsKey("extend")); + + if (map.containsKey("f")) + { + Object oFiles = map.get("f"); + deps.setFiles(oFiles instanceof List + ? (List) oFiles : Collections.singletonList((String) oFiles)); + } + + if (map.containsKey("l")) + { + Object oStatements = map.get("l"); + deps.setStatements(oStatements instanceof List + ? (List) oStatements : Collections.singletonList((String) oStatements)); + } + + deps.setSilentMode(fSilent); + deps.setTraceEnabled(map.containsKey("t") || map.containsKey("trace")); + + if (map.containsKey("g")) + { + deps.setGarFileName((String) map.get("g")); + deps.setApplicationName((String) map.get("a")); + } + + + if (map.containsKey("dp")) + { + String sDomainPartitions = (String) map.get("dp"); + deps.setDomainPartitions(sDomainPartitions.split(",")); + } + + if (map.containsKey("timeout")) + { + String sTimeout = (String) map.get("timeout"); + if (sTimeout.matches("\\d+$")) + { + deps.setTimeout(new Duration(sTimeout, Duration.Magnitude.MILLI)); + } + else + { + throw new IllegalArgumentException("Invalid timeout value"); + } + } + + return deps; + } + catch (IllegalArgumentException e) + { + usage(writer); + writer.flush(); + throw e; + } + } + + /** + * Print the command line usage message to the specified writer. + * + * @param writer the {@link PrintWriter} to print the usage message to + */ + public static void usage(PrintWriter writer) + { + writer.println("java " + + QueryPlus.class.getCanonicalName() + " [-t] [-c] [-s] [-e] [-l ]*\n" + + " [-f ]* [-g ] [-a ] [-dp ] [-timeout ]"); + + /** + * The lines below should try not to exceed 80 characters + * -------------------------------------------------------------------------------- + */ + writer.println( + "\nCommand Line Arguments:\n" + + "-a the application name to use. Used in combination with the -g\n" + + " argument.\n" + + "-c exit when command line processing is finished\n" + + "-e or -extend \n" + + " extended language mode. Allows object literals in update and\n" + + " insert statements.\n" + + " elements between '[' and']'denote an ArrayList.\n" + + " elements between '{' and'}'denote a HashSet.\n" + + " elements between '{' and'}'with key/value pairs separated by\n" + + " ':' denotes a HashMap. A literal HashMap preceded by a class\n" + + " name are processed by calling a zero argument constructor then\n" + + " followed by each pair key being turned into a setter and\n" + + " invoked with the value.\n" + + "-f Each instance of -f followed by a filename load one file of\n" + + " statements.\n" + + "-g An optional GAR file to load before running QueryPlus.\n" + + " If the -a argument is not used the application name will be the\n" + + " GAR file name without the parent directory name.\n" + + "-l Each instance of -l followed by a statement will execute one\n" + + " statement.\n" + + "-s silent mode. Suppress prompts and result headings, read from\n" + + " stdin and write to stdout. Useful for use in pipes or filters\n" + + "-t or -trace \n" + + " turn on tracing. This shows information useful for debugging\n" + + "-dp A comma delimited list of domain partition names to use.\n" + + " On start-up the first domain partition in the list will be the\n" + + " current partition. The -dp argument is only applicable in\n" + + " combination with the -g argument.\n" + + "-timeout Specifies the timeout value for CohQL statements in\n" + + " milli-seconds."); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link QueryPlus.Dependencies} configuring this query session. + */ + protected final Dependencies f_dependencies; + + /** + * The {@link ExecutionContext} that will be passed to {@link Statement}s. + */ + protected final ExecutionContext f_context; + + /** + * The {@link StatementExecutor} to use to execute statements. + */ + protected final StatementExecutor f_executor; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ReflectionExtractorBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ReflectionExtractorBuilder.java new file mode 100644 index 0000000000000..eb407fe40d9b8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/ReflectionExtractorBuilder.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.extractor.AbstractExtractor; +import com.tangosol.util.extractor.ChainedExtractor; +import com.tangosol.util.extractor.ReflectionExtractor; + +import java.util.LinkedList; + +/** + * An {@link ExtractorBuilder} implementation that will build + * instances of {@link ReflectionExtractor}s. + * + * @author jk 2014.07.15 + * @since Coherence 12.2.1 + */ +public class ReflectionExtractorBuilder + implements ExtractorBuilder + { + // ----- ExtractorBuilder interface ------------------------------------- + + @Override + public ValueExtractor realize(String sCacheName, int nTarget, String sProperties) + { + LinkedList listExtractors = new LinkedList<>(); + + String[] asPath = sProperties.split("\\."); + for (int i = 0; i < asPath.length; i++) + { + String sPath = asPath[i]; + StringBuilder sb = new StringBuilder("get").append(Character.toUpperCase(sPath.charAt(0))); + + if (sPath.length() > 1) + { + sb.append(sPath.substring(1)); + } + + listExtractors.add(new ReflectionExtractor(sb.toString(), null, nTarget)); + nTarget = AbstractExtractor.VALUE; + } + + if (listExtractors.size() == 1) + { + return listExtractors.getFirst(); + } + + return new ChainedExtractor(listExtractors.toArray(new ValueExtractor[listExtractors.size()])); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/Statement.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/Statement.java new file mode 100644 index 0000000000000..a59c2eb55a02f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/Statement.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import java.io.PrintWriter; + +/** + * Implementations of this interface are able to execute CohQL statements, + * for example a Select, Update, a backup command etc. + *

+ * Each {@link #execute(ExecutionContext) execution} is provided a {@link + * ExecutionContext context} in which to execute the statement and is obliged + * to return a {@link StatementResult result} to the caller. Ths allows the + * caller to invoke statement agnostic operations, which each implementation + * can specialize based on the format of the results. + * + * @author jk 2013.12.09 + * @since Coherence 12.2.1 + * + * @see StatementResult + */ +public interface Statement + { + /** + * Execute a CohQL query or command and return the relevant {@link + * StatementResult result}. + * + * @param ctx the {@link ExecutionContext context} to use + * + * @return a StatementResult containing the results of executing the statement + */ + public StatementResult execute(ExecutionContext ctx); + + // ----- validation methods --------------------------------------------- + + /** + * Perform sanity checks on the statement that will be executed. + *

+ * Implementations can fail sanity checking by throwing an unchecked exception + * (RuntimeException). + * + * @param ctx the {@link ExecutionContext context} to use + * + * @throws RuntimeException if sanity checking fails + */ + public void sanityCheck(ExecutionContext ctx); + + /** + * Output to the provided {@link PrintWriter} a human readable trace of the + * actions that will be taken by this statement if or when executed. + * + * @param out the PrintWriter to write the trace to + */ + public void showPlan(PrintWriter out); + + /** + * Return a string that will be used as a question to confirm execution of + * a statement. If null is returned then no confirmation is required. + * + * @param ctx the {@link ExecutionContext context} to use + * + * @return a String that will be used to confirm execution of a statement + */ + public String getExecutionConfirmation(ExecutionContext ctx); + + /** + * Obtain a flag indicating whether this Statement will manage its own + * timeout handling. + * + * @return true if this Statement manages timeout handling or false if + * the StatementExecutor should manage timeouts. + */ + public default boolean isManagingTimeout() + { + return false; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/StatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/StatementBuilder.java new file mode 100644 index 0000000000000..8b7c95b2225be --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/StatementBuilder.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.List; + +/** + * Classes implementing this interface build instances of + * {@link Statement} implementations. + * + * @author jk 2013.12.09 + * @since Coherence 12.2.1 + */ +public interface StatementBuilder + { + /** + * Realizes an implementation of a {@link Statement} that can be + * executed to perform a specific CohQL command. + * + * @param ctx the {@link ExecutionContext} to use to create commands + * @param term the parsed {@link NodeTerm} used to create the relevant Statement + * @param listBindVars the indexed bind variables + * @param namedBindVars the named bind variables + * + * @return an executable instance of a Statement + */ + public T realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars); + + /** + * Return the syntax of the CohQL command. + * + * @return the syntax of the CohQL command + */ + public String getSyntax(); + + /** + * Return a description of the CohQL command suitable for displaying + * as help text. + * + * @return a description of the CohQL command suitable for displaying + * as help text + */ + public String getDescription(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/StatementExecutor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/StatementExecutor.java new file mode 100644 index 0000000000000..9182edf860bfd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/StatementExecutor.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import com.oracle.coherence.common.base.Timeout; + +import com.oracle.coherence.common.util.Duration; + +import com.tangosol.coherence.dsltools.precedence.EndOfStatementOPToken; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +import com.tangosol.util.Base; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; + +/** + * A class that parses and executes CohQL statements read from + * an instance of a {@link Reader}. + * + * @author jk 2014.08.06 + */ +public class StatementExecutor + { + // ----- constructors --------------------------------------------------- + + /** + * Create a StatementExecutor + */ + public StatementExecutor() + { + } + + // ----- StatementExecutor methods -------------------------------------- + + /** + * Parse and execute all of the CohQL statements read from the specified + * {@link Reader}. + * + * @param reader the {@link Reader} containing the statements to execute + * @param ctx the {@link ExecutionContext} that will be used + * + * @return the result of the last statement executed + */ + public Object execute(Reader reader, ExecutionContext ctx) + { + CoherenceQueryLanguage language = ctx.getCoherenceQueryLanguage(); + OPParser parser = ctx.instantiateParser(reader); + OPScanner scanner = parser.getScanner(); + PrintWriter out = ctx.getWriter(); + Object oResult = null; + + do + try + { + // make sure we skip over any terminator from the previous statement + scanner.advanceWhenMatching(EndOfStatementOPToken.INSTANCE.getValue()); + + // If we have finished all the tokens then exit + if (scanner.isEnd()) + { + break; + } + + boolean fShowPlan = false; + boolean fTrace = ctx.isTraceEnabled(); + boolean fExecute = true; + + // Check whether we are doing a show plan statement + while (scanner.matches("show") || scanner.matches("plan")) + { + scanner.advance(); + fShowPlan = true; + } + + // Get the statement AST from the parser + Term term = parser.parse(); + + if (fTrace) + { + out.println("\nParsed: " + term); + } + + // Get the Statement for the AST + Statement statement = language.prepareStatement((NodeTerm) term, ctx, null, null); + + if (fShowPlan || fTrace) + { + out.print("plan: "); + statement.showPlan(out); + out.println(); + + if (fShowPlan) + { + continue; + } + } + + if (ctx.isSanityChecking()) + { + statement.sanityCheck(ctx); + } + + // check to see if this statement has a confirmation requirement + // but only display confirmation if we are not in silent mode + String sConfirmation = statement.getExecutionConfirmation(ctx); + if (sConfirmation != null && !ctx.isSilent()) + { + fExecute = confirmExecution(ctx.getReader(), out, sConfirmation); + } + + if (fExecute) + { + StatementResult result; + + if (statement.isManagingTimeout()) + { + // Execute the statement + result = statement.execute(ctx); + } + else + { + try (Timeout t = Timeout.after(ctx.getTimeout().as(Duration.Magnitude.MILLI))) + { + // Execute the statement + result = statement.execute(ctx); + } + } + + // Print the result + String sTitle = ctx.isSilent() ? null : ctx.getTitle(); + + result.print(out, sTitle); + + oResult = result.getResult(); + } + else + { + oResult = null; + } + } + catch (Throwable e) + { + if (ctx.isStopOnError()) + { + throw Base.ensureRuntimeException(e); + } + String sError = "Error: " + e.getMessage(); + out.println(sError); + } + while (!scanner.isEnd()); + + return oResult; + } + + // ----- helpers -------------------------------------------------------- + + /** + * Confirm execution of a {@link Statement} which has a getExecutionConfirmation() + * return a non null value. + * + * @param reader the {@link BufferedReader} to read responses from + * @param out the {@link PrintWriter} to write messages + * @param sPrompt the prompt to display + * + * @return true if 'Y' or 'y' was entered otherwise false + */ + private boolean confirmExecution(BufferedReader reader, PrintWriter out, String sPrompt) + { + String sLine; + try + { + while (true) + { + out.print(sPrompt); + out.flush(); + sLine = reader.readLine(); + if (sLine == null) + { + out.println("Please answer either y or n"); + } + else + { + return (sLine.equals("y") || sLine.equals("Y")); + } + } + } + catch (IOException ioe) + { + throw new CohQLException("IOException reading confirmation " + ioe.getMessage()); + } + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/StatementResult.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/StatementResult.java new file mode 100644 index 0000000000000..e3910ee167774 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/StatementResult.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery; + +import java.io.PrintWriter; + +/** + * The result of executing a CohQL {@link Statement}. + *

+ * A StatementResult encapsulates the raw result when executing a Statement + * allowing a generic mechanism for callers to request certain operations + * such as displaying the result {@link PrintWriter} ({@link + * #print(PrintWriter, String)}). + * + * @author jk 2014.07.15 + * @since Coherence 12.2.1 + */ +public interface StatementResult + { + /** + * Return the actual result Object that this StatementResult wraps. + * + * @return the actual result Object that this StatementResult wraps + */ + public Object getResult(); + + /** + * Print the result object to the specified {@link PrintWriter}. + * + * @param writer the PrintWriter to print the results to + * @param sTitle the title to print before the results + */ + public void print(PrintWriter writer, String sTitle); + + + // ----- inner class: NullResult ---------------------------------------- + + /** + * A StatementResult with a null result value. + */ + public StatementResult NULL_RESULT = new StatementResult() + { + @Override + public Object getResult() + { + return null; + } + + @Override + public void print(PrintWriter writer, String sTitle) + { + } + }; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/function/FunctionBuilders.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/function/FunctionBuilders.java new file mode 100644 index 0000000000000..1eabc634b658c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/function/FunctionBuilders.java @@ -0,0 +1,350 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.function; + +import com.tangosol.coherence.config.ParameterList; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.expression.Value; + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.aggregator.BigDecimalAverage; +import com.tangosol.util.aggregator.BigDecimalMax; +import com.tangosol.util.aggregator.BigDecimalMin; +import com.tangosol.util.aggregator.BigDecimalSum; +import com.tangosol.util.aggregator.Count; +import com.tangosol.util.aggregator.DoubleAverage; +import com.tangosol.util.aggregator.DoubleMax; +import com.tangosol.util.aggregator.DoubleMin; +import com.tangosol.util.aggregator.DoubleSum; +import com.tangosol.util.aggregator.LongMax; +import com.tangosol.util.aggregator.LongMin; +import com.tangosol.util.aggregator.LongSum; + +import com.tangosol.util.extractor.ChainedExtractor; +import com.tangosol.util.extractor.IdentityExtractor; +import com.tangosol.util.extractor.KeyExtractor; +import com.tangosol.util.extractor.PofExtractor; +import com.tangosol.util.extractor.ReflectionExtractor; + +/** + * This class contains a number of {@link ParameterizedBuilder} + * implementations for the standard built-in CohQL functions. + * + * @author jk 2014.05.07 + * @since Coherence 12.2.1 + */ +public final class FunctionBuilders + { + /** + * This builder will realize instances of the {@link BigDecimalAverage} aggregator. + * This builder is called as a result of the CohQL bd_avg() function. + */ + public static ParameterizedBuilder BIG_DECIMAL_AVERAGE_FUNCTION_BUILDER = + new ParameterizedBuilder() + { + @Override + public BigDecimalAverage realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new BigDecimalAverage(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link BigDecimalMax} aggregator. + * This builder is called as a result of the CohQL bd_max() function. + */ + public static ParameterizedBuilder BIG_DECIMAL_MAX_FUNCTION_BUILDER = + new ParameterizedBuilder() + { + @Override + public BigDecimalMax realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new BigDecimalMax(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link BigDecimalMin} aggregator. + * This builder is called as a result of the CohQL bd_min() function. + */ + public static ParameterizedBuilder BIG_DECIMAL_MIN_FUNCTION_BUILDER = + new ParameterizedBuilder() + { + @Override + public BigDecimalMin realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new BigDecimalMin(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link BigDecimalSum} aggregator. + * This builder is called as a result of the CohQL bd_sum() function. + */ + public static ParameterizedBuilder BIG_DECIMAL_SUM_FUNCTION_BUILDER = + new ParameterizedBuilder() + { + @Override + public BigDecimalSum realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new BigDecimalSum(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link Count} aggregator. + * This builder is called as a result of the CohQL count() function. + */ + public static ParameterizedBuilder COUNT_FUNCTION_BUILDER = new ParameterizedBuilder() + { + @Override + public Count realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new Count(); + } + }; + + /** + * This builder will realize instances of the {@link DoubleAverage} aggregator. + * This builder is called as a result of the CohQL avg() function. + */ + public static ParameterizedBuilder DOUBLE_AVERAGE_FUNCTION_BUILDER = + new ParameterizedBuilder() + { + @Override + public DoubleAverage realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new DoubleAverage(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link DoubleMax} aggregator. + * This builder is called as a result of the CohQL max() function. + */ + public static ParameterizedBuilder DOUBLE_MAX_FUNCTION_BUILDER = new ParameterizedBuilder() + { + @Override + public DoubleMax realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new DoubleMax(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link DoubleMin} aggregator. + * This builder is called as a result of the CohQL min() function. + */ + public static ParameterizedBuilder DOUBLE_MIN_FUNCTION_BUILDER = new ParameterizedBuilder() + { + @Override + public DoubleMin realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new DoubleMin(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link DoubleSum} aggregator. + * This builder is called as a result of the CohQL sum() function. + */ + public static ParameterizedBuilder DOUBLE_SUM_FUNCTION_BUILDER = new ParameterizedBuilder() + { + @Override + public DoubleSum realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new DoubleSum(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link LongMax} aggregator. + * This builder is called as a result of the CohQL long_max() function. + */ + public static ParameterizedBuilder LONG_MAX_FUNCTION_BUILDER = new ParameterizedBuilder() + { + @Override + public LongMax realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new LongMax(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link LongMin} aggregator. + * This builder is called as a result of the CohQL long_min() function. + */ + public static ParameterizedBuilder LONG_MIN_FUNCTION_BUILDER = new ParameterizedBuilder() + { + @Override + public LongMin realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new LongMin(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of the {@link LongSum} aggregator. + * This builder is called as a result of the CohQL long_sum() function. + */ + public static ParameterizedBuilder LONG_SUM_FUNCTION_BUILDER = new ParameterizedBuilder() + { + @Override + public LongSum realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return new LongSum(getFirstParameter(resolver, listParameters, ValueExtractor.class)); + } + }; + + /** + * This builder will realize instances of a {@link ReflectionExtractor} that will call + * a specific method. + */ + public static ParameterizedBuilder METHOD_CALL_FUNCTION_BUILDER = + new ParameterizedBuilder() + { + @Override + public ReflectionExtractor realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + Parameter paramFunction = resolver.resolve("functionName"); + Value value = paramFunction.evaluate(resolver); + String sFunction = (String) value.get(); + + if (listParameters.isEmpty()) + { + return new ReflectionExtractor(sFunction); + } + + Object[] ao = new Object[listParameters.size()]; + int i = 0; + + for (Parameter parameter : listParameters) + { + ao[i++] = parameter.evaluate(resolver).get(); + } + + return new ReflectionExtractor(sFunction, ao); + } + }; + + /** + * This builder will realize instances of the {@link IdentityExtractor} aggregator. + * This builder is called as a result of the CohQL value() function. + */ + public static ParameterizedBuilder VALUE_FUNCTION_BUILDER = + new ParameterizedBuilder() + { + @Override + public IdentityExtractor realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + return IdentityExtractor.INSTANCE; + } + }; + + /** + * This {@link ParameterizedBuilder} handles the key() function. + * The type of {@link ValueExtractor} realized will depend on + * the type of the first element in the args array passed to the + * realize method. + * + * This builder is called as a result of the CohQL key() function. + */ + public static ParameterizedBuilder KEY_FUNCTION_BUILDER = + new ParameterizedBuilder() + { + @Override + public ValueExtractor realize(ParameterResolver resolver, ClassLoader loader, ParameterList listParameters) + { + ValueExtractor extractor = listParameters.isEmpty() + ? IdentityExtractor.INSTANCE + : getFirstParameter(resolver, listParameters, ValueExtractor.class); + + return realizeExtractor(extractor); + } + + /** + * Make sure that specified {@link ValueExtractor} targets the + * key if a cache entry. + * + * @param extractorOrig the ValueExtractor to target at the key + * + * @return the ValueExtractor targeted at a cache entry's key + */ + protected ValueExtractor realizeExtractor(ValueExtractor extractorOrig) + { + Class clsExtractor = extractorOrig.getClass(); + + if (ReflectionExtractor.class.equals(clsExtractor)) + { + ReflectionExtractor extractor = (ReflectionExtractor) extractorOrig; + + return new ReflectionExtractor(extractor.getMethodName(), extractor.getParameters(), + ReflectionExtractor.KEY); + } + + if (PofExtractor.class.equals(clsExtractor)) + { + PofExtractor extractor = (PofExtractor) extractorOrig; + + return new PofExtractor(extractor.getClassExtracted(), extractor.getNavigator(), PofExtractor.KEY); + } + + if (ChainedExtractor.class.equals(clsExtractor)) + { + ValueExtractor[] extractors = ((ChainedExtractor) extractorOrig).getExtractors(); + + // ensure that the first ValueExtractor in the chain is targeted at the key + // by recursively calling back into this method + extractors[0] = realizeExtractor(extractors[0]); + ((ChainedExtractor) extractorOrig).ensureTarget(); + + // return the ChainedExtractor with the first ValueExtractor re-targeted at the Key + return extractorOrig; + } + + return new KeyExtractor(extractorOrig); + } + }; + + // ---- helper methods -------------------------------------------------- + + /** + * Extract the first parameter from the listParameters argument. + * + * @param the parameter type + * @param resolver the {@link ParameterResolver} for resolving named {@link Parameter}s + * @param listParameters an optional {@link ParameterList} (may be null) to be used + * for realizing the instance, eg: used as constructor parameters + * @param clzExpected the expected type of the first parameter + * + * @return the first parameter from the listParameters argument + * + * @throws com.tangosol.util.AssertionException if the listParameters list is empty or if the + * first parameter resolved from the list is null or not of the expected type. + */ + protected static V getFirstParameter(ParameterResolver resolver, ParameterList listParameters, + Class clzExpected) + { + Base.azzert(!listParameters.isEmpty()); + + Parameter parameter = listParameters.iterator().next(); + Value value = parameter.evaluate(resolver); + Object oResult = value.get(); + + Base.azzert(oResult != null); + Base.azzert(clzExpected.isAssignableFrom(oResult.getClass())); + + return (V) oResult; + } + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/function/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/function/package.html new file mode 100644 index 0000000000000..85753d1ec7761 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/function/package.html @@ -0,0 +1,5 @@ + +This package contains builders for the various CohQL functions. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/AbstractCoherenceQueryWalker.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/AbstractCoherenceQueryWalker.java new file mode 100644 index 0000000000000..0287399e3b935 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/AbstractCoherenceQueryWalker.java @@ -0,0 +1,708 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.internal; + +import com.tangosol.coherence.config.ResolvableParameterList; +import com.tangosol.coherence.config.SimpleParameterList; + +import com.tangosol.coherence.config.builder.ParameterizedBuilder; + +import com.tangosol.coherence.dslquery.CoherenceQueryLanguage; +import com.tangosol.coherence.dslquery.ExtractorBuilder; + +import com.tangosol.coherence.dslquery.function.FunctionBuilders; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.TermWalker; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.NullParameterResolver; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.Base; +import com.tangosol.util.ClassHelper; +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.extractor.AbstractExtractor; +import com.tangosol.util.extractor.ChainedExtractor; +import com.tangosol.util.extractor.KeyExtractor; +import com.tangosol.util.extractor.ReflectionExtractor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * AbstractCoherenceTermWalker is a visitor class that provides a framework + * for walking Term Trees by providing classification methods based on the + * Abstract Syntax Tree vocabulary for the Coherence Query expression + * Language. These classification methods are passed values extracted from the + * AST so tht they may remain ignorant of the AST to Term tree representation. + * Subclasses may ignore classifications for which they are not + * interested. + * + * @author djl 2009.08.31 + * @author jk 2013.12.02 + */ +public abstract class AbstractCoherenceQueryWalker + implements TermWalker + { + // ----- constructors --------------------------------------------------- + + /** + * Creates a AbstractCoherenceQueryWalker with the specified + * bind variables. + * + * @param listBindVars the indexed ind variables + * @param namedBindVars the named bind variables + * @param language the {@link CoherenceQueryLanguage} instance to use + */ + protected AbstractCoherenceQueryWalker(List listBindVars, ParameterResolver namedBindVars, + CoherenceQueryLanguage language) + { + m_listBindVars = new ArrayList<>(); + + if (listBindVars != null) + { + m_listBindVars.addAll(listBindVars); + } + + if (namedBindVars == null) + { + namedBindVars = new NullParameterResolver(); + } + + m_namedBindVars = namedBindVars; + f_language = language; + f_propertyBuilder = new PropertyBuilder(); + f_termKeyFunction = Terms.create("callNode(key())", language); + } + + // ----- TermWalker API ------------------------------------------------- + + @Override + public void acceptNode(String sFunctor, NodeTerm term) + { + String sBindingType; + AtomicTerm atomicTerm; + String sAlias = m_sAlias; + + switch (sFunctor) + { + case "literal" : + acceptLiteral((AtomicTerm) term.termAt(1)); + break; + + case "listNode" : + acceptList(term); + break; + + case "identifier" : + if (sAlias != null) + { + String sid = ((AtomicTerm) term.termAt(1)).getValue(); + + if (sAlias.equals(sid)) + { + acceptCall("value", new NodeTerm("value")); + + return; + } + } + + acceptIdentifier(((AtomicTerm) term.termAt(1)).getValue()); + break; + + case "binaryOperatorNode" : + acceptBinaryOperator(((AtomicTerm) term.termAt(1)).getValue(), term.termAt(2), term.termAt(3)); + break; + + case "unaryOperatorNode" : + acceptUnaryOperator(((AtomicTerm) term.termAt(1)).getValue(), term.termAt(2)); + break; + + case "bindingNode" : + sBindingType = ((AtomicTerm) term.termAt(1)).getValue(); + atomicTerm = (AtomicTerm) term.termAt(2).termAt(1); + + if ("?".equals(sBindingType)) + { + acceptNumericBinding(atomicTerm.getNumber().intValue()); + } + else + { + acceptKeyedBinding(atomicTerm.getValue()); + } + + break; + + case "callNode" : + acceptCall((term.termAt(1)).getFunctor(), (NodeTerm) term.termAt(1)); + break; + + case "derefNode" : + if (sAlias != null) + { + Term termChild = term.termAt(1); + + if ("identifier".equals(termChild.getFunctor())) + { + atomicTerm = (AtomicTerm) termChild.termAt(1); + + if (sAlias.equals(atomicTerm.getValue())) + { + int nTerms = term.length() - 1; + + if (nTerms == 1) + { + Term t2 = term.termAt(2); + + t2.accept(this); + + return; + } + + Term[] aTerms = new Term[term.length() - 1]; + + System.arraycopy(term.children(), 1, aTerms, 0, term.length() - 1); + acceptPath(new NodeTerm(sFunctor, aTerms)); + + return; + } + } + } + + acceptPath(term); + break; + + default : + throw new RuntimeException("Unknown AST node: " + term.fullFormString()); + } + } + + @Override + public void acceptAtom(String sFunctor, AtomicTerm atomicTerm) + { + m_oResult = atomicTerm.getObject(); + m_atomicTerm = atomicTerm; + } + + @Override + public void acceptTerm(String sFunctor, Term term) + { + } + + @Override + public Object walk(Term term) + { + term.accept(this); + + return getResult(); + } + + // ----- AbstractCoherenceQueryWalker API --------------------------------------- + + /** + * The receiver has classified a literal node. + * + * @param atom the term representing the literal + */ + protected void acceptLiteral(AtomicTerm atom) + { + m_oResult = atom.getObject(); + m_atomicTerm = atom; + } + + /** + * The receiver has classified a list node. + * + * @param termList the Term whose children represent the elements of the list + */ + protected void acceptList(NodeTerm termList) + { + } + + /** + * The receiver has classified an identifier node. + * + * @param sIdentifier the String representing the identifier + */ + protected void acceptIdentifier(String sIdentifier) + { + } + + /** + * Return true if the identifier specified is a well known identifier + * ('null', 'true' or 'false'), with a side-affect of {@code m_oResult} + * being set appropriately. + * + * @param sIdentifier the identifier to accept + * + * @return true if the identifier was a well known value otherwise false + */ + protected boolean acceptIdentifierInternal(String sIdentifier) + { + switch (sIdentifier.toLowerCase()) + { + case "null" : + m_oResult = null; + + return true; + + case "true" : + m_oResult = Boolean.TRUE; + + return true; + + case "false" : + m_oResult = Boolean.FALSE; + + return true; + } + + return false; + } + + /** + * The receiver has classified a binary operation node. + * + * @param sOperator the string representing the operator + * @param termLeft the left Term of the operation + * @param termRight the right Term of the operation + */ + protected void acceptBinaryOperator(String sOperator, Term termLeft, Term termRight) + { + } + + /** + * The receiver has classified a unary operation node. + * + * @param sOperator the string representing the operator + * @param term the Term being operated upon + */ + protected void acceptUnaryOperator(String sOperator, Term term) + { + } + + /** + * The receiver has classified a bind slot. + * + * @param iVar the 1-based index into the bind variables + */ + protected void acceptNumericBinding(int iVar) + { + m_oResult = m_listBindVars.get(iVar - 1); + } + + /** + * The receiver has classified a bind slot. + * + * @param sName the name of the bind variable to use + */ + protected void acceptKeyedBinding(String sName) + { + Parameter p = m_namedBindVars.resolve(sName); + if (p == null) + { + throw new RuntimeException("Unable to resolve named bind variable: " + sName); + } + else + { + m_oResult = p.evaluate(m_namedBindVars).get(); + } + } + + /** + * The receiver has classified a call node. + * + * @param sFunctionName the function name + * @param term a Term whose children are the parameters to the call + */ + protected void acceptCall(String sFunctionName, NodeTerm term) + { + ParameterizedBuilder functionBuilder = f_language.getFunction(sFunctionName); + + if (functionBuilder == null) + { + functionBuilder = FunctionBuilders.METHOD_CALL_FUNCTION_BUILDER; + } + + ResolvableParameterList resolver = new ResolvableParameterList(); + SimpleParameterList listParameters = new SimpleParameterList(); + + resolver.add(new Parameter("functionName", sFunctionName)); + + for (int i = 1, cTerms = term.length(); i <= cTerms; i++) + { + term.termAt(i).accept(this); + listParameters.add(m_oResult); + } + + m_oResult = functionBuilder.realize(resolver, null, listParameters); + } + + /** + * The receiver has classified a path node. + * + * @param term a Term whose children are the elements of the path + */ + protected void acceptPath(NodeTerm term) + { + } + + /** + * Process the specified path term as a {@link ChainedExtractor}. + * + * @param sCacheName the cache name the extractor will be executed on + * @param nodeTerm the {@link NodeTerm} containing the path to use + * to build the ChainedExtractor + */ + protected void acceptPathAsChainedExtractor(String sCacheName, NodeTerm nodeTerm) + { + List listExtractors = new ArrayList<>(); + StringBuilder sbPath = new StringBuilder(); + int nTarget = AbstractExtractor.VALUE; + boolean fIdentifier = true; + + for (Term term : nodeTerm) + { + if (f_termKeyFunction.termEqual(term)) + { + nTarget = AbstractExtractor.KEY; + continue; + } + + fIdentifier = fIdentifier && "identifier".equals(term.getFunctor()); + + if (fIdentifier) + { + if (sbPath.length() > 0) + { + sbPath.append("."); + } + + sbPath.append(((AtomicTerm) term.termAt(1)).getValue()); + } + else + { + term.accept(this); + listExtractors.add((ValueExtractor) getResult()); + } + } + + if (sbPath.length() == 0 && nTarget == AbstractExtractor.KEY) + { + // The target is KEY and there were no identifiers in the NodeTerm tree + // so insert a KeyExtractor at the front of the chain + listExtractors.add(0, new KeyExtractor()); + } + else if (sbPath.length() > 0) + { + // Build a chain of ValueExtractors from the sbPath property list + ExtractorBuilder builder = f_language.getExtractorBuilder(); + ValueExtractor extractor = builder.realize(sCacheName, nTarget, sbPath.toString()); + + listExtractors.add(0, extractor); + } + + m_oResult = buildExtractor(listExtractors); + } + + /** + * Create a single {@link ValueExtractor} from the {@link List} of + * ValueExtractors. + *

+ * If the List contains a single ValueExtractor then that is returned + * from this method. If the List contains multiple ValueExtractors + * then these are combined into a {@link ChainedExtractor}. + * + * @param listExtractors the List of ValueExtractors to use + * + * @return a single ValueExtractor built from the List of ValueExtractors + */ + protected ValueExtractor buildExtractor(List listExtractors) + { + if (listExtractors.size() == 1) + { + return listExtractors.get(0); + } + + List list = new ArrayList<>(); + + for (ValueExtractor extractor : listExtractors) + { + if (extractor instanceof ChainedExtractor) + { + list.addAll(Arrays.asList(((ChainedExtractor) extractor).getExtractors())); + } + else + { + list.add(extractor); + } + } + + return new ChainedExtractor(list.toArray(new ValueExtractor[list.size()])); + } + + // ----- accessors ----------------------------------------------------- + + /** + * Set the flag that controls whether to process an "Extended Language" statement. + * + * @param fExtendedLanguage flag that determines whether to process + * an extended language + */ + public void setExtendedLanguage(boolean fExtendedLanguage) + { + m_fExtendedLanguage = fExtendedLanguage; + } + + /** + * Set the value for the result object. + * + * @param oResult the value to set as the result + */ + public void setResult(Object oResult) + { + m_oResult = oResult; + } + + @Override + public Object getResult() + { + return m_oResult; + } + + // ----- helper methods ------------------------------------------------ + + /** + * Use reflection to make Object either my calling a constructor or + * static method. + * + * @param fUseNew flag that controls whether to use constructor + * @param oExtractor the ReflectionExtractor or array of ReflectionExtractors + * + * @return the constructed object + */ + protected Object reflectiveMakeObject(boolean fUseNew, Object oExtractor) + { + String sName = null; + StringBuilder sbPath = new StringBuilder(); + ReflectionExtractor extractor = null; + Class cls; + + if (oExtractor instanceof ReflectionExtractor) + { + extractor = (ReflectionExtractor) oExtractor; + } + else if (oExtractor instanceof ChainedExtractor) + { + ChainedExtractor extractorChained = (ChainedExtractor) oExtractor; + ValueExtractor[] aExtractors = extractorChained.getExtractors(); + + for (int i = 0; i < aExtractors.length - 1; i++) + { + ReflectionExtractor reflectionExtractor = (ReflectionExtractor) aExtractors[i]; + + if (sbPath.length() > 0) + { + sbPath.append('.'); + } + + String sMethodName = reflectionExtractor.getMethodName(); + + sbPath.append(f_propertyBuilder.plainName(sMethodName)); + } + + extractor = (ReflectionExtractor) aExtractors[aExtractors.length - 1]; + } + else if (oExtractor instanceof Object[]) + { + Object[] ao = (Object[]) oExtractor; + + for (int i = 0; i < ao.length - 1; ++i) + { + if (sbPath.length() > 0) + { + sbPath.append('.'); + } + + sbPath.append(ao[i]); + } + + extractor = (ReflectionExtractor) ao[ao.length - 1]; + } + + String sMethod = extractor.getMethodName(); + Object[] aoArgs = extractor.getParameters(); + + try + { + if (fUseNew) + { + sName = sbPath.length() > 0 + ? sbPath + "." + sMethod + : sMethod; + cls = Class.forName(sName); + + return ClassHelper.newInstance(cls, aoArgs); + } + else if (m_fExtendedLanguage && sMethod.equals(".object.")) + { + sName = sbPath.length() > 0 + ? sbPath + "." + aoArgs[0] + : (String) aoArgs[0]; + cls = Class.forName(sName); + + Object oInstance = ClassHelper.newInstance(cls, new Object[0]); + + if (aoArgs.length != 2) + { + throw new RuntimeException("Malformed object creation " + sName); + } + + if (aoArgs[1] instanceof Map) + { + Map map = (Map) aoArgs[1]; + + for (Map.Entry e : map.entrySet()) + { + String setter = f_propertyBuilder.updaterStringFor((String) e.getKey()); + + ClassHelper.invoke(cls, oInstance, setter, new Object[] {e.getValue()}); + } + } + + return oInstance; + } + else + { + if (sbPath.length() == 0) + { + throw new RuntimeException("Malformed static call " + sMethod); + } + + sName = sbPath.toString(); + cls = Class.forName(sName); + + return ClassHelper.invokeStatic(cls, sMethod, aoArgs); + } + } + catch (InstantiationException e) + { + StringBuilder sb = new StringBuilder(" Unable to instantiate ").append(sName).append(" with "); + + append(sb, aoArgs, ", "); + + throw Base.ensureRuntimeException(e, sb.toString()); + } + catch (NoSuchMethodException e) + { + StringBuilder sb = + new StringBuilder("Unable to find method ").append(sMethod).append(" on ").append(sName) + .append(" with: "); + + append(sb, aoArgs, ", "); + + throw Base.ensureRuntimeException(e, sb.toString()); + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e); + } + } + + /** + * Append each of the values in the aoObjects array to the {@link StringBuilder} + * appending the specified separator between each value. + * + * @param sb the StringBuilder to append the values to + * @param aoObjects the values to append to the StringBuilder + * @param sSeparator the separator to use + */ + protected void append(StringBuilder sb, Object[] aoObjects, String sSeparator) + { + boolean fFirst = true; + + for (Object o : aoObjects) + { + if (!fFirst) + { + sb.append(sSeparator); + fFirst = false; + } + + sb.append(o); + } + } + + // ----- accessors ------------------------------------------------------ + + /** + * Set the alias that was used in naming caches. Allowed to be used in + * path expressions. + * + * @param sAlias The String that is the alias + */ + public void setAlias(String sAlias) + { + m_sAlias = sAlias; + } + + // ----- data members --------------------------------------------------- + + /** + * The instance of {@link CoherenceQueryLanguage} to use. + */ + protected final CoherenceQueryLanguage f_language; + + /** + * The alias of the cache name being used. + */ + protected String m_sAlias = null; + + /** + * An Object that is the result of each classified dispatch as the + * tree of Objects is built + */ + protected Object m_oResult; + + /** + * The AtomicTerm that was last processed + */ + protected AtomicTerm m_atomicTerm; + + /** + * The PropertyBuilder used to make getters and setters + */ + protected final PropertyBuilder f_propertyBuilder; + + /** + * Flag that controls whether we except Map, and List as literals. + * This gives a json like feel to the language. + */ + protected boolean m_fExtendedLanguage = false; + + /** + * The current indexed list of bind variables. + */ + protected List m_listBindVars; + + /** + * The current named bind variables. + */ + protected ParameterResolver m_namedBindVars; + + /** + * Constant equal to an empty Key function term. + */ + protected final Term f_termKeyFunction; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/ConstructorQueryWalker.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/ConstructorQueryWalker.java new file mode 100644 index 0000000000000..3cedc71c2e8bd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/ConstructorQueryWalker.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.internal; + +import com.tangosol.coherence.dslquery.CoherenceQueryLanguage; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.List; + +/** + * This is a specialized query walker that walks an AST + * representing a constructor statement in the form of + * package.ClassName(args) and results in an Object array + * where the last entry in the array is a + * {@link com.tangosol.util.extractor.ReflectionExtractor}. + * The method name in the ReflectionExtractor is the class name + * and the arguments are any constructor args. Any preceding + * elements in the array are the package name elements. + * + * @author jk 2013.12.06 + */ +public class ConstructorQueryWalker + extends AbstractCoherenceQueryWalker + { + // ----- constructors --------------------------------------------------- + + /** + * Creates a ConstructorQueryWalker that uses the specified + * bindings to replace any bind variables in the ASTs walked. + * + * @param indexedBindVars indexed bind variables + * @param namedBindVars named bind variables + * @param language the CoherenceQueryLanguage to use + */ + public ConstructorQueryWalker(List indexedBindVars, ParameterResolver namedBindVars, + CoherenceQueryLanguage language) + { + super(indexedBindVars, namedBindVars, language); + } + + // ----- TermWalker methods ---------------------------------------------- + + /** + * The receiver has classified an identifier node. + * + * @param sIdentifier the String representing the identifier + */ + @Override + protected void acceptIdentifier(String sIdentifier) + { + setResult(sIdentifier); + } + + /** + * The receiver has classified a path node. + * + * @param term a Term whose children are the elements of the path + */ + @Override + protected void acceptPath(NodeTerm term) + { + Object[] aoPath = new Object[term.length()]; + int i = 0; + + for (; i < term.length() - 1; i++) + { + term.termAt(i + 1).accept(this); + aoPath[i] = getResult(); + } + + term.termAt(i + 1).accept(this); + aoPath[i] = getResult(); + + setResult(aoPath); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/PersistenceToolsHelper.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/PersistenceToolsHelper.java new file mode 100644 index 0000000000000..e32a0204bb561 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/PersistenceToolsHelper.java @@ -0,0 +1,1076 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.internal; + +import com.oracle.coherence.common.base.Blocking; + +import com.oracle.coherence.persistence.PersistenceException; +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.io.FileHelper; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.Cluster; +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.DistributedCacheService; +import com.tangosol.net.ExtensibleConfigurableCacheFactory; +import com.tangosol.net.Member; +import com.tangosol.net.Service; +import com.tangosol.net.management.MBeanServerProxy; +import com.tangosol.net.management.Registry; + +import com.tangosol.persistence.CachePersistenceHelper; +import com.tangosol.persistence.PersistenceEnvironmentInfo; + +import com.tangosol.util.Base; +import com.tangosol.util.WrapperException; + +import java.io.File; +import java.io.PrintWriter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.management.MBeanException; + +/** + * Various helper classes to support calling Persistence operations + * from within CohQL. + * + * @author tam 2014.02.14 + * @since 12.2.1 + */ +public class PersistenceToolsHelper + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new PersistenceToolsHelper which can be used to issue + * persistence related commands from CohQL. No-args constructor is used + * when no tracing is required. + */ + public PersistenceToolsHelper() + { + this(null); + } + + /** + * Construct a new PersistenceToolsHelper which can be used to issue + * persistence related commands from CohQL. + * + * @param out the PrintWriter to write trace messages to + */ + public PersistenceToolsHelper(PrintWriter out) + { + Cluster cluster = CacheFactory.ensureCluster(); + + m_registry = cluster.getManagement(); + m_out = out; + + if (m_registry == null) + { + throw new CohQLException("Unable to retrieve Registry from cluster"); + } + + m_mbsProxy = m_registry.getMBeanServerProxy(); + + ensureMBeanRegistration(Registry.CLUSTER_TYPE); + } + + // ----- PersistenceToolsHelper methods---------------------------------- + + /** + * Ensure a {@link PersistenceToolsHelper} exists within the CohQL {@link ExecutionContext} + * which can be used to issue cluster related Persistence commands. + * If it doesn't, then create a new one. + * + * @param ctx current CohQL {@link ExecutionContext} + * + * @return the existing PersistenceToolsHelper or a new one if doesn't exist + * + * @throws CohQLException if we are unable to retrieve a new API + */ + public static PersistenceToolsHelper ensurePersistenceToolsHelper(ExecutionContext ctx) + throws CohQLException + { + PersistenceToolsHelper helper = ctx.getResourceRegistry().getResource(PersistenceToolsHelper.class, HELPER); + + try + { + if (helper == null) + { + helper = new PersistenceToolsHelper(ctx.isTraceEnabled() ? ctx.getWriter() : null); + + ctx.getResourceRegistry().registerResource(PersistenceToolsHelper.class, HELPER, helper); + } + } + catch (Exception e) + { + throw ensureCohQLException(e, "Unable to instantiate PersistenceToolsHelper"); + } + + return helper; + } + + /** + * Issue an operation and wait for the operation to be complete by + * polling the "Idle" attribute of the PersistenceCoordinator for the service. + * This method will poll continuously until an "Idle" status has been reached + * or until timeout set by a calling thread has been raised. e.g.
+ *

+     * try (Timeout t = Timeout.after(120, TimeUnit.SECONDS))
+     *     {
+     *     helper.invokeOperationWithWait("createSnapshot", "snapshot", "Service");
+     *     }
+     * 
+ * When called from CohQL, the TIMEOUT value set in CohQL will be used to interrupt + * the operation if it has not completed.
+ * Note: Even though and exception is raised, the MBean operation will still + * execute to completion, but CohQL will return immediately without waiting. + * + * @param sOperation the operation to execute + * @param sSnapshot the snapshot name + * @param sServiceName the name of the service to execute operation on + * + * @throws MBeanException if any MBean related errors + */ + public void invokeOperationWithWait(String sOperation, String sSnapshot, String sServiceName) + throws MBeanException + { + boolean fisIdle; + + try + { + invokeOperation(sOperation, sServiceName, new String[] {sSnapshot}, new String[] {"java.lang.String"}); + + String sBeanName = getPersistenceMBean(sServiceName); + + while (true) + { + Blocking.sleep(SLEEP_TIME); + fisIdle = (boolean) getAttribute(sBeanName, "Idle"); + traceMessage("Idle = " + fisIdle); + + if (fisIdle) + { + // idle means the operation has completed as we are guaranteed an up-to-date + // attribute value just after an operation was called + return; + } + + traceMessage("Operation " + sOperation + " not yet complete, waiting " + + SLEEP_TIME + "ms"); + } + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e, "Unable to complete operation " + + sOperation + " for service " + sServiceName); + } + } + + /** + * Invoke an operation against a PersistenceManagerMBean associated to the + * given service name. + * + * @param sOperation the operation to execute + * @param sServiceName the name of the service to execute operation on + * @param aoParams the parameters of the operation + * @param asParamTypes the parameter types of the operation + * + * @throws MBeanException if an error occurred invoking the MBean + */ + public void invokeOperation(String sOperation, String sServiceName, Object[] aoParams, String[] asParamTypes) + throws MBeanException + { + String sBeanName = getPersistenceMBean(sServiceName); + + traceMessage("Invoking " + sOperation + " on " + sBeanName + + " using params = " + Arrays.toString(aoParams)); + + m_mbsProxy.invoke(sBeanName, sOperation, aoParams, asParamTypes); + } + + /** + * Validate that a service name exists for the current cluster. + * + * @param sServiceName the service name to check + * + * @return true if the service exists + */ + public boolean serviceExists(String sServiceName) + { + try + { + Map mapServices = listServices(); + + return mapServices != null && mapServices.containsKey(sServiceName); + } + + catch (Exception e) + { + throw Base.ensureRuntimeException(e, "Error validating service"); + } + } + + /** + * Validate that a snapshot exists for a given service. + * + * @param sServiceName the service name to check + * @param sSnapshotName the snapshot name to check + * + * @return true if the snapshot exists for the service + */ + public boolean snapshotExists(String sServiceName, String sSnapshotName) + { + try + { + String[] asSnapshots = listSnapshots(sServiceName); + + return asSnapshots != null && Arrays.asList(asSnapshots).contains(sSnapshotName); + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e, "Error validating snapshot"); + } + } + + /** + * Validate that an archived snapshot exists for a given service. + * + * @param sServiceName the service name to check + * @param sSnapshotName the archived snapshot name to check + * + * @return true if the archived snapshot exists for the service + */ + public boolean archivedSnapshotExists(String sServiceName, String sSnapshotName) + { + try + { + String[] asSnapshots = listArchivedSnapshots(sServiceName); + + return asSnapshots != null && Arrays.asList(asSnapshots).contains(sSnapshotName); + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e, "Error validating archived snapshots"); + } + } + + /** + * Validate that a snapshot exists across all services. + * + * @param sSnapshotName the snapshot name to check + * + * @throws CohQLException if the condition is not met + */ + public void validateSnapshotExistsForAllServices(String sSnapshotName) + { + StringBuilder sb = new StringBuilder(); + + try + { + for (Map.Entry entry : listSnapshots().entrySet()) + { + String[] asSnapshots = entry.getValue(); + + if (!Arrays.asList(asSnapshots).contains(sSnapshotName)) + { + sb.append("The snapshot ").append(sSnapshotName) + .append(" does not exist on service ").append(entry.getKey()) + .append('\n'); + } + } + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e, "Error validating snapshot"); + } + + if (sb.length() > 0) + { + throw new CohQLException(sb.toString()); + } + } + + /** + * Validate that an archived snapshot exists across all services to ensure + * success for a retrieve or purge operation. + * + * @param sSnapshotName the archived snapshot name to check + * + * @throws CohQLException if the condition is met + */ + public void validateArchivedSnapshotExistsForAllServices(String sSnapshotName) + { + StringBuilder sb = new StringBuilder(); + + try + { + for (Map.Entry entry : listServices().entrySet()) + { + String sServiceName = entry.getKey(); + String[] asArchivedSnapshots = listArchivedSnapshots(sServiceName); + + if (!Arrays.asList(asArchivedSnapshots).contains(sSnapshotName)) + { + sb.append("The archived snapshot ").append(sSnapshotName) + .append(" does not exist on service ").append(sServiceName) + .append('\n'); + } + } + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e, "Error validating snapshot"); + } + + if (sb.length() > 0) + { + throw new CohQLException(sb.toString()); + } + } + + /** + * List all the services configured for active or on-demand mode and information + * about them including: persistence-mode, QuorumStatus and current operation status + * from the PersistenceSnapshotMBean. + * + * @return a {@link Map} of services and related information + */ + public Map listServices() + { + Map mapResults = new HashMap<>(); + + for (Map.Entry entry : getPersistenceServices().entrySet()) + { + String sServiceName = entry.getKey(); + String sPersistenceMode = entry.getValue(); + + String[] asResults = getServiceInfo(sServiceName); + + mapResults.put(sServiceName, new String[] {sPersistenceMode, asResults[0], asResults[1]}); + } + + return mapResults; + } + + /** + * List all the services configured for active or on-demand mode and display the + * persistence environment implementation. + * + * @return a {@link List} of services and related information + */ + public List listServicesEnvironment() + { + List listInfo = new ArrayList<>(); + + for (String sServiceName : getPersistenceServices().keySet()) + { + Member member = getStorageEnabledMember(sServiceName); + + if (member == null) + { + throw new RuntimeException("Unable to find storage-enabled members for service " + sServiceName); + } + + String sEnvironment = (String) getAttribute( + getServiceMBean(sServiceName, member), "PersistenceEnvironment"); + + listInfo.add(sServiceName + " - " + sEnvironment); + } + + return listInfo; + } + + /** + * List the snapshots for the specified service. + * + * @param sServiceName the name of the service to list snapshots for + * + * @return the snapshots for the specified service or an empty String[] + * if none exist + */ + public String[] listSnapshots(String sServiceName) + { + try + { + String[] asSnapshots = (String[]) getAttribute( + getPersistenceMBean(sServiceName), "Snapshots"); + + return asSnapshots == null ? NO_SNAPSHOTS : asSnapshots; + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e); + } + } + + /** + * List the snapshots for all services. + * + * @return a {@link java.util.Map} of services and their snapshots. + */ + public Map listSnapshots() + { + Map mapResults = new HashMap<>(); + + for (Map.Entry entry : getPersistenceServices().entrySet()) + { + String sServiceName = entry.getKey(); + + mapResults.put(sServiceName, listSnapshots(sServiceName)); + + } + + return mapResults; + } + + /** + * Return a list of archived snapshots for a given service. + * + * @param sServiceName the name of the service to query + * + * @return a {@link String}[] of archived snapshots for the given service + */ + public String[] listArchivedSnapshots(String sServiceName) + { + try + { + return (String[]) m_mbsProxy.invoke( + getPersistenceMBean(sServiceName), "listArchivedSnapshots", + new String[] {}, new String[] {}); + } + catch (Exception e) + { + throw new RuntimeException("Unable to execute listArchivedSnapshots for service " + + sServiceName + ": " + e.getMessage()); + } + } + + /** + * List the archived snapshots for all services. + * + * @return a {@link java.util.Map} of services and their archived snapshots. + */ + public Map listArchivedSnapshots() + { + Map mapResults = new HashMap<>(); + + // go through each of the services returned and retrieve the snapshots. + for (String sServiceName : getPersistenceServices().keySet()) + { + try + { + mapResults.put(sServiceName, listArchivedSnapshots(sServiceName)); + } + catch (Exception e) + { + if (e instanceof RuntimeException && e.getMessage().contains("MBeanException")) + { + // ignore as we may not have an archiver defined for the service + } + else + { + throw ensureCohQLException(e, "Unable to list archived snapshots"); + } + } + } + + return mapResults; + } + + /** + * Return the archiver configured for the given service. + * + * @param sServiceName the name of the service to query + * + * @return the archiver configured for the given services or 'n/a' if none exists + */ + public String getArchiver(String sServiceName) + { + Member member = getStorageEnabledMember(sServiceName); + + if (member == null) + { + throw new RuntimeException("Unable to find storage-enabled members for service " + sServiceName); + } + + return (String) getAttribute( + getServiceMBean(sServiceName, member), "PersistenceSnapshotArchiver"); + } + + /** + * Resume a given service. + * + * @param sServiceName the service to resume + */ + public void resumeService(String sServiceName) + { + try + { + m_mbsProxy.invoke(Registry.CLUSTER_TYPE, RESUME_SERVICE, new String[] {sServiceName}, + new String[] {"java.lang.String"}); + } + catch (Exception e) + { + throw new RuntimeException("Unable to resume service " + e.getMessage()); + } + } + + /** + * Suspend a given service. + * + * @param sServiceName the service to suspend + */ + public void suspendService(String sServiceName) + { + try + { + m_mbsProxy.invoke(Registry.CLUSTER_TYPE, SUSPEND_SERVICE, new String[] {sServiceName}, + new String[] {"java.lang.String"}); + } + catch (Exception e) + { + throw new RuntimeException("Unable to resume service " + e.getMessage()); + } + } + + /** + * Ensures that the specified service is in a ready state to begin snapshot operations. + * Ie. The service should not have operations that are running. This call will + * wait for any processes to complete if fWait is true.
+ * This method will poll continuously until an "Idle" status has been reached + * or until timeout set by a calling thread has been raised. + * + * @param fWait if true and the service is not Idle then wait, otherwise + * throw an exception + * @param sServiceToCheck the service to check for or null for all services + * + * @throws CohQLException if any services are not in a proper state + */ + public void ensureReady(boolean fWait, String sServiceToCheck) + { + try + { + while (true) + { + String sStatus = getOperationStatus(sServiceToCheck); + + if (STATUS_IDLE.equals(sStatus)) + { + // operation is Idle + break; + } + else + { + if (fWait) + { + Blocking.sleep(SLEEP_TIME); + } + else + { + throw new CohQLException("The service " + sServiceToCheck + + " currently has an operation in progress: \n" + sStatus + + "\nPlease use LIST SERVICES to determine when service is ready."); + } + } + } + } + catch (Exception e) + { + throw ensureCohQLException(e, "Error during ensureReady"); + } + } + + /** + * Ensures that the services are in a ready state to begin snapshot operations. + * Ie. they should not have operations that are running. If the context is + * silent then we will wait, otherwise will fail fast. + * + * @param ctx context + * @param sService the service to wait to be ready or if null, then all services + * + * @throws CohQLException if any services are not in a proper state + */ + public void ensureReady(ExecutionContext ctx, String sService) + { + ensureReady(ctx.isSilent(), sService); + } + + /** + * Return a CohQLException with the given cause. If the specified + * cause is an instance of CohQLException, the given throwable will + * be returned as is; otherwise, a new CohQLException will be + * allocated and returned. + * + * @param eCause an optional cause + * @param sMsg an optional detail message + * + * @return a CohQLException with the given cause and detail message + */ + public static CohQLException ensureCohQLException(Throwable eCause, String sMsg) + { + StringBuilder sb = new StringBuilder(sMsg); + Throwable cause = eCause; + + // check for exception raised from Mbean Server or PersistenceException + if ((eCause instanceof WrapperException && eCause.getCause() instanceof RuntimeException) || + (eCause instanceof PersistenceException)) + { + Throwable t = eCause.getCause(); + sb.append(" - ").append(eCause.getMessage()); + if (t != null) + { + sb.append('\n').append(t.getMessage()); + cause = t.getCause(); + if (cause != null) + { + sb.append('\n').append(cause.getMessage()); + sb.append('\n').append(cause.getCause()); + } + } + } + + return eCause instanceof CohQLException ? + (CohQLException) eCause : new CohQLException(sb.toString(), cause); + } + + /** + * Output a trace message to the defined {@link java.io.PrintWriter}. + * + * @param sMessage the message to output + */ + private void traceMessage(String sMessage) + { + if (isTraceEnabled()) + { + m_out.println(new Date(Base.getSafeTimeMillis()) + " : " + sMessage); + m_out.flush(); + } + } + + public String getOperationStatus(String sServiceName) + { + return (String) getAttribute(getPersistenceMBean(sServiceName), "OperationStatus"); + } + + /** + * Validate that a snapshot does not exist across all services. + * + * @param sSnapshotName the snapshot name to check + * + */ + private void validateNoSnapshotExistsForAllServices(String sSnapshotName) + { + StringBuilder sb = new StringBuilder(); + + try + { + for (Map.Entry entry : listSnapshots().entrySet()) + { + String[] asSnapshots = entry.getValue(); + + if (Arrays.asList(asSnapshots).contains(sSnapshotName)) + { + sb.append("The snapshot ").append(sSnapshotName) + .append(" already exists on service ").append(entry.getKey()) + .append('\n'); + } + } + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e, "Error validating snapshot"); + } + + if (sb.length() > 0) + { + throw new CohQLException(sb.toString()); + } + } + + /** + * Return a {@link Map} of services that are configured for persistence as either + * active or on-demand. + * + * @return the {@link Map} of services with service name as key and + * persistence mode as value + */ + private Map getPersistenceServices() + { + Map mapServices = new HashMap<>(); + Cluster cluster = CacheFactory.ensureCluster(); + Enumeration e = cluster.getServiceNames(); + + while (e.hasMoreElements()) + { + String sServiceName = e.nextElement(); + + String sType = cluster.getServiceInfo(sServiceName).getServiceType(); + + // only include federated or distributed + if (isValidServiceType(sType)) + { + // to get the PersistenceMode, we need to query the ServiceMBean + // from one of the storage enabled nodes + Member member = getStorageEnabledMember(sServiceName); + + if (member == null) + { + throw new RuntimeException("Unable to find storage-enabled members for service " + sServiceName); + } + + String sPersistenceMode = (String) getAttribute( + getServiceMBean(sServiceName, member), "PersistenceMode"); + + mapServices.put(sServiceName, sPersistenceMode); + } + + } + + return mapServices; + } + + /** + * Return a member id of a storage-enable member for the given service. + * + * @param sServiceName the service name to retrieve member for + * + * @return the member id of a storage-enable member for the service or + * -1 if none found + */ + private Member getStorageEnabledMember(String sServiceName) + { + Service service = CacheFactory.getCluster().getService(sServiceName); + + if (service instanceof DistributedCacheService) + { + Set setMembers = ((DistributedCacheService) service).getOwnershipEnabledMembers(); + + for (Member member : setMembers) + { + return member; + } + return null; + } + else + { + throw CachePersistenceHelper.ensurePersistenceException(new IllegalArgumentException("Service " + sServiceName + + " is not distributed or federated service.")); + } + } + + /** + * Ensure that a object name is registered as there can be a race condition + * as some MBeans are registered async. + * + * @param sObjectName the object name to ensure + */ + private void ensureMBeanRegistration(String sObjectName) + { + boolean fLogged = false; + int nCounter = 3 * 1000; // 30 seconds , 3,000 * 10ms wait + + // wait for registration of sObjectName as the registration is done + // async and may not be complete before our first call after ensureCluster(). + while (!m_mbsProxy.isMBeanRegistered(sObjectName)) + { + if (isTraceEnabled() && !fLogged) + { + traceMessage("Waiting for " + sObjectName + " to be registered"); + fLogged = true; + } + + try + { + Blocking.sleep(10L); + } + catch (InterruptedException e) + { + } + + if (--nCounter <= 0) + { + // fail-safe in case cluster never registered + throw new RuntimeException("MBean " + sObjectName + " was not registered after 30 seconds." + + " You must be running an MBean Server within the cluster to use 'Persistence' commands."); + } + } + + if (isTraceEnabled() && fLogged) + { + traceMessage(sObjectName + " is now registered"); + } + } + + /** + * Return service information for the list services command. The values returned are: + *
    + *
  1. [0] - QuorumStatus
  2. + *
  3. [1] - OperationStatus
  4. + *
+ * + * @param sServiceName the name of the service to query + * + * @return a {@link String} array of information + */ + private String[] getServiceInfo(String sServiceName) + { + Member member = getStorageEnabledMember(sServiceName); + + if (member == null) + { + throw new RuntimeException( + "Unable to find storage-enabled members for service " + sServiceName); + } + + // the following is not quite correct, since it doesn't call ensureGlobalName(), + // which may add "extension" attributes to the original name + String sQuorumStatus = (String) getAttribute( + getServiceMBean(sServiceName, member), "QuorumStatus"); + + String sOperationStatus = (String) getAttribute( + getPersistenceMBean(sServiceName), "OperationStatus"); + + return new String[] {sQuorumStatus, sOperationStatus}; + } + + /** + * Return the PersistenceManager MBean name. + * + * @param sServiceName the name of the service to return the name for + * + * @return the MBean name + */ + public String getPersistenceMBean(String sServiceName) + { + return ensureGlobalName( + CachePersistenceHelper.getMBeanName(sServiceName)); + } + + /** + * Return the Service MBean name. + * + * @param sServiceName the name of the service to return the name for + * @param member the member of the service to return the name for + * + * @return the MBean name + */ + public String getServiceMBean(String sServiceName, Member member) + { + return m_registry.ensureGlobalName( + Registry.SERVICE_TYPE + ",name=" + sServiceName, member); + } + + /** + * Return true if the service is federated or distributed + * + * @param sType the service type + * + * @return true if the service is federated or distributed + */ + private static boolean isValidServiceType(String sType) + { + return "DistributedCache".equals(sType) || "FederatedCache".equals(sType); + } + + /** + * Return a global name for the given MBean Name. + * + * @param sName the MBean to get global name for. + * + * @return the global name. + */ + private String ensureGlobalName(String sName) + { + return m_registry.ensureGlobalName(sName); + } + + /** + * Return an attribute name from an MBean. + * + * @param sObjectName object name to query + * @param sAttribute attribute to retrieve from object name + * + * @return the value of the attribute + */ + private Object getAttribute(String sObjectName, String sAttribute) + { + return m_mbsProxy.getAttribute(sObjectName, sAttribute); + } + + // ----- helpers -------------------------------------------------------- + + /** + * Return a term for a given scanner representing the specified name. + * If the end of statement is reached then an CohQLException is raised. + * + * @param s OPScanner to use + * @param sName the name to assign the new term + * @param sDescription a description for any exception + * @param sCommand the command name + * + * @return a new term + * + * @throws CohQLException if end of statement is reached + */ + public static Term getNextTerm(OPScanner s, String sName, String sDescription, String sCommand) + { + if (s.isEndOfStatement()) + { + throw new CohQLException(sDescription + " required for " + sCommand); + } + + return Terms.newTerm(sName, AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + } + + /** + * Return the snapshot directory for a given service and snapshot. + * + * @param ccf ConfigurableCacheFactory to use to get dependencies + * @param sSnapshot the snapshot name to use + * @param sServiceName the service name to use + * + * @return a File representing the snapshot directory + */ + public static File getSnapshotDirectory(ConfigurableCacheFactory ccf, String sSnapshot, String sServiceName) + { + if (ccf instanceof ExtensibleConfigurableCacheFactory) + { + PersistenceEnvironmentInfo info = + CachePersistenceHelper.getEnvironmentInfo((ExtensibleConfigurableCacheFactory) ccf, sServiceName); + + if (info == null) + { + throw new CohQLException("Unable to get persistence environment info for service " + + sServiceName + " and snapshot " + sSnapshot); + } + + return new File(info.getPersistenceSnapshotDirectory(), FileHelper.toFilename(sSnapshot)); + } + + throw new UnsupportedOperationException("ConfigurableCacheFactory is not an instance of ExtensibleConfigurableCacheFactory"); + } + + // ----- accessors ------------------------------------------------------ + + /** + * Set the {@link java.io.PrintWriter} for any messages to go to. + * + * @param out the {@link java.io.PrintWriter} to use + */ + public void setPrintWriter(PrintWriter out) + { + m_out = out; + } + + /** + * Return if trace is enabled. + * + * @return if trace is enabled + */ + public boolean isTraceEnabled() + { + return m_out != null; + } + + // ----- constants ------------------------------------------------------ + + /** + * JMX operation to create a snapshot. + */ + public static final String CREATE_SNAPSHOT = "createSnapshot"; + + /** + * JMX operation to recover a snapshot. + */ + public static final String RECOVER_SNAPSHOT = "recoverSnapshot"; + + /** + * JMX operation to remove a snapshot. + */ + public static final String REMOVE_SNAPSHOT = "removeSnapshot"; + + /** + * JMX operation to archive a snapshot + */ + public static final String ARCHIVE_SNAPSHOT = "archiveSnapshot"; + + /** + * JMX operation to retrieve an archived snapshot + */ + public static final String RETRIEVE_ARCHIVED_SNAPSHOT = "retrieveArchivedSnapshot"; + + /** + * JMX operation to remove an archived snapshot + */ + public static final String REMOVE_ARCHIVED_SNAPSHOT = "removeArchivedSnapshot"; + + /** + * JMX operation to suspend a service. + */ + public static final String SUSPEND_SERVICE = "suspendService"; + + /** + * JMX operation to resume a service. + */ + public static final String RESUME_SERVICE = "resumeService"; + + /** + * JMX operation to force recovery. + */ + public static final String FORCE_RECOVERY = "forceRecovery"; + + /** + * Idle status. + */ + private static final String STATUS_IDLE = "Idle"; + + /** + * Sleep time between checking operation completion. + */ + private static final long SLEEP_TIME = 500L; + + /** + * Cluster Tools registry key. + */ + private static final String HELPER = "persistence_tools_helper"; + + /** + * Signifies no snapshots were found. + */ + private static final String[] NO_SNAPSHOTS = new String[0]; + + // ----- data members --------------------------------------------------- + + /** + * A PrintWriter to output any informational messages. + */ + private PrintWriter m_out = null; + + /** + * MBean server proxy for JMX operations and attribute retrieval for online mode. + */ + private MBeanServerProxy m_mbsProxy; + + /** + * Management Registry if we are connected to a cluster. + */ + private Registry m_registry; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/PropertyBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/PropertyBuilder.java new file mode 100644 index 0000000000000..aec4f9e56365d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/PropertyBuilder.java @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.internal; + +import com.tangosol.util.ValueExtractor; +import com.tangosol.util.ValueUpdater; + +import com.tangosol.util.extractor.ChainedExtractor; +import com.tangosol.util.extractor.CompositeUpdater; +import com.tangosol.util.extractor.ReflectionExtractor; +import com.tangosol.util.extractor.ReflectionUpdater; + +import java.util.ArrayList; + +/** + * PropertyBuilder is a utility class that turns property Strings into + * proper getter and setter names. + * + * @author djl 2009.08.31 + */ +public class PropertyBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new PropertyBuilder. + */ + public PropertyBuilder() + { + } + + /** + * Construct a new PropertyBuilder that will suppress the given prefix + * String. + * + * @param sPrefix the String to suppress + */ + public PropertyBuilder(String sPrefix) + { + m_sPrefixToSuppress = sPrefix; + } + + // ----- PropertyBuilder API -------------------------------------------- + + /** + * Make a ValueExtractor for the given String. Allow '.' and make + * a ChainedValueExtractor. + * + * @param sName the String used to make ValueExtractor + * + * @return the constructed ValueExtractor + */ + public ValueExtractor extractorFor(String sName) + { + return extractorFor(splitString(sName, '.')); + } + + /** + * Make a getter String suitable for use in a ValueExtractor. + * + * @param sName the property String used to make getter String + * + * @return the constructed String + */ + public String extractorStringFor(String sName) + { + return uniformStringFor(uniformArrayFor(splitString(sName, '.'), "get")); + } + + /** + * Make a setter String suitable for use in a ValueUpdater. If String + * already starts with "set" then leave it alone. + * + * @param sName the property String used to make setter String + * + * @return the constructed String + */ + public String updaterStringFor(String sName) + { + return uniformStringFor(uniformArrayFor(splitString(sName, '.'), "set")); + } + + /** + * Make a getter String suitable for use in a ValueUpdater. + * + * @param sName the property String used to make getter String + * + * @return the constructed String + */ + public String propertyStringFor(String sName) + { + return uniformStringFor(uniformArrayFor(splitString(sName, '.'), "")); + } + + /** + * Make a ValueUpdater for the given String. Allow '.' for chaining. + * + * @param sName the String used to make ValueExtractor + * + * @return the constructed ValueUpdater + */ + public ValueUpdater updaterFor(String sName) + { + return updaterFor(splitString(sName, '.')); + } + + /** + * Make a ValueExtractor for the given array of property Strings. + * + * @param asProps the String[] used to make ValueExtractor + * + * @return the constructed ValueExtractor + */ + public ValueExtractor extractorFor(String[] asProps) + { + String[] asMethodNames = uniformArrayFor(asProps, "get"); + + if (asMethodNames.length == 1) + { + return new ReflectionExtractor(asMethodNames[0]); + } + + ValueExtractor[] aExtractors = new ValueExtractor[asMethodNames.length]; + + for (int i = 0; i < aExtractors.length; i++) + { + aExtractors[i] = new ReflectionExtractor(asMethodNames[i]); + } + + return new ChainedExtractor(aExtractors); + } + + /** + * Make a ValueUpdater for the given array of property Strings. + * + * @param asProps the String[] used to make ValueUpdater + * + * @return the constructed ValueUpdater + */ + public ValueUpdater updaterFor(String[] asProps) + { + int nStart = 0; + int nPartCount = asProps.length; + String sProp = asProps[nPartCount - 1]; + ValueUpdater updater = new ReflectionUpdater(makeSimpleName("set", sProp)); + + if (m_sPrefixToSuppress != null && asProps[0].equals(m_sPrefixToSuppress)) + { + nStart = 1; + } + + if (nPartCount - nStart > 1) + { + ValueExtractor[] extractors = new ValueExtractor[nPartCount - nStart - 1]; + + for (int i = 0; i < extractors.length; i++) + { + extractors[i] = new ReflectionExtractor(makeSimpleName("get", asProps[i + nStart])); + } + + updater = new CompositeUpdater(new ChainedExtractor(extractors), updater); + } + + return updater; + } + + /** + * Make a camel case String by prefixing a given String with a given + * prefix. If name already starts with prefix and the following char is + * uppercase then just name was already a propper camel case getter or + * setter and return the given String. + * + * @param sPrefix the String to prefix, typically get or set + * @param sName the property String being transformed + * + * @return the constructed String + */ + public String makeSimpleName(String sPrefix, String sName) + { + if (sName.startsWith(sPrefix)) + { + String sn = sName.substring(sPrefix.length()); + + if (sn.length() >= 1 && Character.isUpperCase(sn.charAt(0))) + { + return sName; + } + else if (sn.length() == 0) + { + return sName; + } + else + { + return sPrefix + Character.toUpperCase(sn.charAt(0)) + sn.substring(1); + } + } + + return sPrefix + sName.substring(0, 1).toUpperCase() + sName.substring(1); + } + + /** + * Returns the specified method name with any "set" or + * "get" prefix removed and the first letter of the + * remaining String converted to lowercase. + * + * @param sName the method name to convert + * + * @return the converted method name + */ + public String plainName(String sName) + { + if (sName == null || sName.isEmpty()) + { + return sName; + } + + if (sName.startsWith("set") || sName.startsWith("get")) + { + sName = sName.substring(3); + } + + StringBuilder sb = new StringBuilder(); + + sb.append(Character.toLowerCase(sName.charAt(0))); + + if (sName.length() > 1) + { + sb.append(sName.substring(1)); + } + + return sb.toString(); + } + + // ----- helper methods ------------------------------------------------ + + /** + * Make a getter or setter String array from the given array of property + * Strings usingthe given prefix string. + * + * @param asProps the String[] used to make getters or setters + * @param sPrefix the String to be prepended. + * + * @return the constructed String array + */ + protected String[] uniformArrayFor(String[] asProps, String sPrefix) + { + int nStart = 0; + int nPartCount = asProps.length; + + if (m_sPrefixToSuppress != null && asProps[0].equals(m_sPrefixToSuppress)) + { + nStart = 1; + } + + String[] aExtractors = new String[nPartCount - nStart]; + + for (int i = 0; i < aExtractors.length; i++) + { + aExtractors[i] = makeSimpleName(sPrefix, asProps[i + nStart]); + } + + return aExtractors; + } + + /** + * Make a String from the given array of Strings parts. + * + * @param asParts the String[] used to make one string separated by '.' + * + * @return the constructed String + */ + protected String uniformStringFor(String[] asParts) + { + if (asParts == null || asParts.length == 0) + { + return ""; + } + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < asParts.length; ++i) + { + sb.append('.').append(asParts[i]); + } + + return sb.substring(1); + } + + /** + * Take a given String and bust apart into a String array by splitting at + * the given delimiter character. + * + * @param sName the String to prefix, typically get or set + * @param delim the delimiter character + * + * @return the constructed String + */ + public String[] splitString(String sName, char delim) + { + if (sName == null) + { + return null; + } + + ArrayList list = new ArrayList(); + int nPrev = -1; + + while (true) + { + int nNext = sName.indexOf(delim, nPrev + 1); + + if (nNext < 0) + { + list.add(sName.substring(nPrev + 1)); + break; + } + else + { + list.add(sName.substring(nPrev + 1, nNext)); + } + + nPrev = nNext; + } + + return (String[]) list.toArray(new String[list.size()]); + } + + // ----- data members --------------------------------------------------- + + /** + * Prefix string that should be suppressed. + */ + protected String m_sPrefixToSuppress = null; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/SelectListMaker.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/SelectListMaker.java new file mode 100644 index 0000000000000..a8d49c15e7cb1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/SelectListMaker.java @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.internal; + +import com.tangosol.coherence.dslquery.CoherenceQueryLanguage; +import com.tangosol.coherence.dslquery.ExtractorBuilder; + +import com.tangosol.coherence.dslquery.operator.BaseOperator; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.InvocableMap; +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.aggregator.CompositeAggregator; +import com.tangosol.util.aggregator.DistinctValues; +import com.tangosol.util.aggregator.GroupAggregator; +import com.tangosol.util.aggregator.ReducerAggregator; + +import com.tangosol.util.extractor.AbstractExtractor; +import com.tangosol.util.extractor.MultiExtractor; + +import com.tangosol.util.processor.CompositeProcessor; +import com.tangosol.util.processor.ExtractorProcessor; + +import java.util.List; + +/** + * SelectListMaker is a visitor class that converts a given Abstract Syntax + * Tree into implementation Objects for a select query. The implementation + * Objects are typically subclasses of InvocableMap.EntryAggregator. + * The SelectListMaker can also be used to make ValueExtractors. + * + * @author djl 2009.08.31 + */ +public class SelectListMaker + extends AbstractCoherenceQueryWalker + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SelectListMaker using given array for indexed + * Bind vars and the given Map for named bind variables. + * + * @param indexedBindVars the indexed bind variables + * @param namedBindVars the named bind variables + * @param language the CoherenceQueryLanguage instance to use + */ + public SelectListMaker(List indexedBindVars, ParameterResolver namedBindVars, + CoherenceQueryLanguage language) + { + super(indexedBindVars, namedBindVars, language); + } + + // ----- SelectListMaker API -------------------------------------------- + + /** + * Test to see if the receiver had nodes that are call which would mean + * the result is an aggregation + * + * @return the results of the test + */ + public boolean hasCalls() + { + return m_nCallCount > 0; + } + + /** + * Test to see if the results of processing resulted in an aggregation. + * + * @return the results of the test + */ + public boolean isAggregation() + { + return m_nCallCount == m_aResults.length; + } + + /** + * Get the resultant Object[] from the tree processing + * + * @return the results of processing + */ + public Object[] getResults() + { + return m_aResults; + } + + /** + * Turn the results of tree processing into a ValueExtractor + * + * @return the resulting ValueExtractor + */ + public ValueExtractor getResultsAsValueExtractor() + { + if (hasCalls() || m_aResults.length == 0) + { + return null; + } + else if (m_aResults.length == 1) + { + return (ValueExtractor) m_aResults[0]; + } + else + { + ValueExtractor[] extractors = new ValueExtractor[m_aResults.length]; + + for (int i = 0; i < m_aResults.length; i++) + { + extractors[i] = (ValueExtractor) m_aResults[i]; + } + + return new MultiExtractor(extractors); + } + } + + /** + * Turn the results of tree processing into a DistinctValues aggregator + * + * @return the resulting DistinctValues + */ + public DistinctValues getDistinctValues() + { + if (hasCalls()) + { + return null; + } + + return new DistinctValues(getResultsAsValueExtractor()); + } + + /** + * Turn the results of tree processing into an InvocableMap.EntryProcessor + * that will return the results of a query + * + * @return the resulting EntryProcessor + */ + public InvocableMap.EntryProcessor getResultsAsEntryProcessor() + { + if (hasCalls()) + { + return null; + } + + if (m_aResults.length == 1) + { + return new ExtractorProcessor((ValueExtractor) m_aResults[0]); + } + + InvocableMap.EntryProcessor[] processors = new InvocableMap.EntryProcessor[m_aResults.length]; + + for (int i = 0; i < m_aResults.length; i++) + { + processors[i] = new ExtractorProcessor((ValueExtractor) m_aResults[i]); + } + + return new CompositeProcessor(processors); + } + + /** + * Turn the results of tree processing into an InvocableMap.EntryAggregator + * that will return the results of a query. + * + * @return the resulting EntryAggregator + */ + public InvocableMap.EntryAggregator getResultsAsReduction() + { + int nIdentifierCount = m_aResults.length; + + if (hasCalls()) + { + return null; + } + + if (nIdentifierCount == 1) + { + return new ReducerAggregator((ValueExtractor) m_aResults[0]); + } + + ValueExtractor[] aExtractors = new ValueExtractor[nIdentifierCount]; + + for (int i = 0; i < nIdentifierCount; ++i) + { + aExtractors[i] = (ValueExtractor) m_aResults[i]; + } + + return new ReducerAggregator(new MultiExtractor(aExtractors)); + } + + /** + * Turn the results of tree processing into an + * InvocableMap.EntryAggregator that will perform the aggregation. + * + * @return the resulting EntryAggregator + */ + public InvocableMap.EntryAggregator getResultsAsEntryAggregator() + { + int nIdentifierCount = m_aResults.length - m_nCallCount; + + if (!hasCalls()) + { + return null; + } + + if (m_aResults.length == 1) + { + return (InvocableMap.EntryAggregator) m_aResults[0]; + } + + if (m_aResults.length == m_nCallCount) + { + InvocableMap.EntryAggregator[] aAggregators = new InvocableMap.EntryAggregator[m_aResults.length]; + + for (int i = 0; i < m_aResults.length; i++) + { + aAggregators[i] = (InvocableMap.EntryAggregator) m_aResults[i]; + } + + return CompositeAggregator.createInstance(aAggregators); + } + + ValueExtractor[] aExtractors = new ValueExtractor[nIdentifierCount]; + InvocableMap.EntryAggregator aggregator; + + for (int i = 0; i < nIdentifierCount; ++i) + { + aExtractors[i] = (ValueExtractor) m_aResults[i]; + } + + if (m_nCallCount == 1) + { + aggregator = (InvocableMap.EntryAggregator) m_aResults[nIdentifierCount]; + } + else + { + InvocableMap.EntryAggregator[] aAggregators = new InvocableMap.EntryAggregator[m_nCallCount]; + + for (int i = 0; i < m_nCallCount; i++) + { + aAggregators[i] = (InvocableMap.EntryAggregator) m_aResults[nIdentifierCount + i]; + } + + aggregator = CompositeAggregator.createInstance(aAggregators); + } + + if (nIdentifierCount == 1) + { + return GroupAggregator.createInstance(aExtractors[0], aggregator); + } + + return GroupAggregator.createInstance(new MultiExtractor(aExtractors), aggregator); + } + + /** + * Process the AST Tree using the given Term + * + * @param sCacheName the name of the cache to make select list for + * @param term the AST to walk to make the select list + * + * @return the resulting Object[] of results + */ + public Object[] makeSelectsForCache(String sCacheName, NodeTerm term) + { + m_sCacheName = sCacheName; + m_term = term; + m_aResults = new Object[m_term.length()]; + + int nCount = m_term.length(); + + setResult(null); + + for (int i = 1; i <= nCount; ++i) + { + m_term.termAt(i).accept(this); + m_aResults[i - 1] = getResult(); + } + + return m_aResults; + } + + // ----- AbstractCoherenceQueryWalker API --------------------------------------- + + /** + * The receiver has classified a binary operation node + * + * @param sOperator the String representing the operator + * @param termLeft the left Term of the operation + * @param termRight the left Term of the operation + */ + @Override + protected void acceptBinaryOperator(String sOperator, Term termLeft, Term termRight) + { + BaseOperator operator = f_language.getOperator(sOperator); + + setResult(operator.makeExtractor(termLeft, termRight, this)); + } + + /** + * The receiver has classified a call node + * + * @param sFunctionName the function name + * @param term a Term whose children are the parameters to the call + */ + @Override + protected void acceptCall(String sFunctionName, NodeTerm term) + { + super.acceptCall(sFunctionName, term); + + Object oResult = getResult(); + + if (oResult instanceof InvocableMap.EntryAggregator) + { + m_nCallCount++; + } + } + + @Override + protected void acceptIdentifier(String sIdentifier) + { + if (acceptIdentifierInternal(sIdentifier)) + { + return; + } + + ExtractorBuilder builder = f_language.getExtractorBuilder(); + + setResult(builder.realize(m_sCacheName, AbstractExtractor.VALUE, sIdentifier)); + } + + @Override + protected void acceptPath(NodeTerm term) + { + acceptPathAsChainedExtractor(m_sCacheName, term); + } + + // ----- data members --------------------------------------------------- + + /** + * The name of the cache that the select list is being built for + */ + protected String m_sCacheName; + + /** + * The Term that is the AST that encodes select list + */ + protected NodeTerm m_term; + + /** + * The count of classifications that are calls + * tree of Objects is built + */ + protected int m_nCallCount = 0; + + /** + * The results of the classification + */ + protected Object[] m_aResults; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/UpdateSetListMaker.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/UpdateSetListMaker.java new file mode 100644 index 0000000000000..5b83cf2640dd7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/internal/UpdateSetListMaker.java @@ -0,0 +1,911 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.internal; + +import com.tangosol.coherence.dslquery.CoherenceQueryLanguage; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.InvocableMap; +import com.tangosol.util.ValueExtractor; +import com.tangosol.util.ValueUpdater; + +import com.tangosol.util.extractor.ChainedExtractor; +import com.tangosol.util.extractor.CompositeUpdater; +import com.tangosol.util.extractor.IdentityExtractor; +import com.tangosol.util.extractor.KeyExtractor; +import com.tangosol.util.extractor.ReflectionExtractor; +import com.tangosol.util.extractor.ReflectionUpdater; + +import com.tangosol.util.processor.CompositeProcessor; +import com.tangosol.util.processor.NumberIncrementor; +import com.tangosol.util.processor.NumberMultiplier; +import com.tangosol.util.processor.UpdaterProcessor; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +/** + * UpdateSetListMaker is a visitor class that converts a given Abstract Syntax + * Tree into implementation Objects for a Update query. The implementation + * Objects are typically subclasses of InvocableMap.EntryProcessor. + * UpdateSetListMakers can also be used to process ASTs that are java + * constructors. + * + * @author djl 2009.08.31 + */ +public class UpdateSetListMaker + extends AbstractCoherenceQueryWalker + { + // ----- constructors ---------------------------------------------------- + + /** + * Construct a new UpdateSetListMaker using given array for indexed + * Bind vars and the given Map for named bind vars. + * + * @param indexedBindVars the indexed bind vars + * @param namedBindVars the named bind vars + * @param language the CoherenceQueryLanguage to use + */ + public UpdateSetListMaker(List indexedBindVars, ParameterResolver namedBindVars, + CoherenceQueryLanguage language) + { + super(indexedBindVars, namedBindVars, language); + } + + // ----- UpdateSetListMaker API ------------------------------------------ + + /** + * Get the resultant Object[] from the tree processing + * + * @return the results of processing + */ + public Object[] getResults() + { + return m_aoResults; + } + + /** + * Turn the results of tree processing into an InvocableMap.EntryProcessor + * that will return the results of the update + * + * @return the resulting EntryProcessor + */ + public InvocableMap.EntryProcessor getResultAsEntryProcessor() + { + if (m_aoResults.length == 1) + { + return (InvocableMap.EntryProcessor) m_aoResults[0]; + } + else + { + InvocableMap.EntryProcessor[] aProcessors = new InvocableMap.EntryProcessor[m_aoResults.length]; + + for (int i = 0; i < m_aoResults.length; ++i) + { + aProcessors[i] = (InvocableMap.EntryProcessor) m_aoResults[i]; + } + + return new CompositeProcessor(aProcessors); + } + } + + /** + * Process the AST Tree using the already set AST Tree + * + * @return the resulting Object[] of results + */ + public InvocableMap.EntryProcessor makeSetList() + { + m_aoResults = new Object[m_term.length()]; + setResult(null); + acceptTarget(); + + return getResultAsEntryProcessor(); + } + + /** + * Process the AST Tree using the given Term + * + * @param term the AST used + * + * @return the resulting Object[] of results + */ + public InvocableMap.EntryProcessor makeSetList(NodeTerm term) + { + m_term = term; + + return makeSetList(); + } + + /** + * Process the AST Tree using the given Term that that is to be turned + * into an Object. Java constructors, static calls, or Object literals + * are processed. + * + * @param term the AST used + * + * @return the resulting Object + * + * @throws RuntimeException Callers should catch exceptions + */ + public Object makeObject(NodeTerm term) + { + m_term = term; + m_aoResults = null; + m_fRValueExpected = true; + + setResult(null); + m_term.accept(this); + + String functor = term.getFunctor(); + + if (needsObjectCreation(functor, term)) + { + Object oResult = reflectiveMakeObject(false, getResult()); + + setResult(oResult); + } + + return getResult(); + } + + /** + * Process the AST Tree using the given Term that that is to be turned + * into an Object to be used as a key for the given context Object. + * Simple properties or simple calls will be make on the context Object + * Java constructors, static calls, or Object literals stand by themselves. + *

+ * This situation is complicated because we try to be helpful and allow + * sending messages to the passed context object. + * + * Cases: + * new Constructor(): + * The Constructor can be fully qualified or not but + * in any case it will already be processed it's a simple call + * in which case it has been turned into a ReflectionExtractor + * so test for it and use it. + * + * identifier: + * This is a property and you use ValueExtractor relative to + * the context object + * + * derefNode that is all properties: + * This is a ChainedExtractor relative to the context object + * + * derefNode with calls along the way in the middle: + * This is a ChainedExtractor too so make it and use it on the + * context object + * + * literal object: + * These will already have been processed. The test for + * needsReflectiveCreation will weed this case out so you can + * simply return m_out. + * + * derefNode that ends in a call: + * We can't really tell whether it is a static call or should be + * a ChainedExtractor relative to the context object so we try + * the static call which can fail so its backed by a catch that + * tries again with a ChainedExtractor unless we are trying + * something special like .object. . + *

+ * + * If we ever add static imports the calls will need to be checked + * against some Map of know static imports because the normal mechanism + * will make a ReflectionExtractor. + * + * @param term the AST used + * @param oValue the Object to extract results from + * + * @return the resulting Object + * + * @throws RuntimeException Callers should catch exceptions + */ + public Object makeObjectForKey(NodeTerm term, Object oValue) + { + m_term = term; + m_aoResults = null; + m_fRValueExpected = true; + + setResult(null); + m_term.accept(this); + + String sFunctor = term.getFunctor(); + Object oResult = getResult(); + + if (sFunctor.equals("unaryOperatorNode")) + { + return getResult(); + } + + if ((sFunctor.equals("callNode")) && oResult instanceof ValueExtractor) + { + ValueExtractor extractor = makeValueExtractor(oResult); + + oResult = extractor.extract(oValue); + setResult(oResult); + + return oResult; + } + + if (sFunctor.equals("identifier") && oResult instanceof String) + { + ValueExtractor extractor = makeValueExtractor(oResult); + + oResult = extractor.extract(oValue); + setResult(oResult); + + return oResult; + } + + if (sFunctor.equals("derefNode")) + { + Object[] aoPath = (Object[]) oResult; + int nCount = aoPath.length; + boolean fUseExtractor = false; + + if (!(aoPath[nCount - 1] instanceof ReflectionExtractor)) + { + fUseExtractor = true; + } + else + { + for (int i = 0; i < nCount - 1; i++) + { + if (aoPath[i] instanceof ReflectionExtractor) + { + fUseExtractor = true; + break; + } + } + } + + if (fUseExtractor) + { + ValueExtractor ve = makeValueExtractor(oResult); + + oResult = ve.extract(oValue); + setResult(oResult); + + return oResult; + } + } + + try + { + if (needsObjectCreation(sFunctor, term)) + { + oResult = reflectiveMakeObject(false, oResult); + setResult(oResult); + } + } + catch (Exception e) + { + if (sFunctor.equals(".object.")) + { + throw new RuntimeException(e.getMessage()); + } + + ValueExtractor extractor = makeValueExtractor(oResult); + + oResult = extractor.extract(oValue); + setResult(oResult); + } + + return oResult; + } + + // ----- DefaultCoherenceQueryWalker API --------------------------------- + + @Override + public void acceptNode(String sFunctor, NodeTerm term) + { + if (m_fExtendedLanguage) + { + switch (sFunctor) + { + case ".list." : + super.acceptNode("callNode", new NodeTerm("callNode", term)); + break; + + case ".bag." : + super.acceptNode("callNode", new NodeTerm("callNode", term)); + break; + + case ".pair." : + super.acceptNode("callNode", new NodeTerm("callNode", term)); + break; + + case ".object." : + super.acceptNode("callNode", new NodeTerm("callNode", term)); + break; + + default : + super.acceptNode(sFunctor, term); + break; + } + } + else + { + super.acceptNode(sFunctor, term); + } + } + + @Override + protected void acceptList(NodeTerm termList) + { + int cTerms = termList.length(); + Object[] ao = new Object[cTerms]; + + for (int i = 1; i <= cTerms; i++) + { + termList.termAt(i).accept(this); + ao[i - 1] = getResult(); + } + + setResult(ao); + } + + @Override + protected void acceptIdentifier(String sIdentifier) + { + if (acceptIdentifierInternal(sIdentifier)) + { + return; + } + + setResult(sIdentifier); + } + + @Override + protected void acceptBinaryOperator(String sOperator, Term termLeft, Term termRight) + { + String sIdentifier; + + switch (sOperator) + { + case "==" : + m_fRValueExpected = false; + termLeft.accept(this); + + ValueUpdater updater = makeValueUpdater(getResult()); + + m_fRValueExpected = true; + termRight.accept(this); + + Object oResult = getResult(); + + if (needsObjectCreation(termRight.getFunctor(), termRight)) + { + oResult = reflectiveMakeObject(false, getResult()); + setResult(oResult); + } + + oResult = new UpdaterProcessor(updater, oResult); + setResult(oResult); + break; + + case "+" : + termLeft.accept(this); + sIdentifier = makePathString(getResult()); + termRight.accept(this); + + if (termRight.isLeaf()) + { + NumberIncrementor incrementor = new NumberIncrementor(sIdentifier, (Number) getResult(), false); + + setResult(incrementor); + } + else + { + throw new RuntimeException("Argument to binary operator '+' not atomic"); + } + + break; + + case "*" : + termLeft.accept(this); + sIdentifier = makePathString(getResult()); + termRight.accept(this); + + if (termRight.isLeaf()) + { + NumberMultiplier multiplier = new NumberMultiplier(sIdentifier, (Number) getResult(), false); + + setResult(multiplier); + } + else + { + throw new RuntimeException("Argument to binary operator '*' not atomic"); + } + + break; + + default : + throw new RuntimeException("Unknown binary operator: " + sOperator); + } + } + + @Override + protected void acceptUnaryOperator(String sOperator, Term term) + { + switch (sOperator) + { + case "new" : + term.accept(this); + + Object oResult = reflectiveMakeObject(true, getResult()); + + setResult(oResult); + break; + + case "-" : + term.accept(this); + + if (m_atomicTerm.isNumber()) + { + Number number = m_atomicTerm.negativeNumber((Number) getResult()); + + setResult(number); + } + + break; + + case "+" : + term.accept(this); + break; + + default : + throw new RuntimeException("Unknown unary operator: " + sOperator); + } + } + + @Override + protected void acceptCall(String sFunctionName, NodeTerm term) + { + int nCount = term.length(); + Object[] aObjects = new Object[nCount]; + + for (int i = 1; i <= nCount; i++) + { + Term termChild = term.termAt(i); + + termChild.accept(this); + + Object oResult = getResult(); + + if (needsObjectCreation(termChild.getFunctor(), termChild)) + { + oResult = reflectiveMakeObject(false, oResult); + } + + aObjects[i - 1] = oResult; + setResult(oResult); + } + + if (sFunctionName.equalsIgnoreCase("key") && nCount == 0) + { + KeyExtractor keyExtractor = new KeyExtractor(IdentityExtractor.INSTANCE); + + setResult(keyExtractor); + } + else if (sFunctionName.equalsIgnoreCase("value") && nCount == 0) + { + setResult(IdentityExtractor.INSTANCE); + } + else if (m_fExtendedLanguage) + { + switch (sFunctionName) + { + case ".list." : + setResult(makeListLiteral(aObjects)); + break; + + case ".bag." : + setResult(makeSetOrMapLiteral(aObjects)); + break; + + case ".pair." : + setResult(makePairLiteral(aObjects)); + break; + + case "List" : + setResult(makeListLiteral(aObjects)); + break; + + case "Set" : + setResult(makeSetLiteral(aObjects)); + break; + + case "Map" : + setResult(makeMapLiteral(aObjects)); + break; + + default : + setResult(asReflectionExtractor(sFunctionName, aObjects)); + break; + } + } + else + { + setResult(asReflectionExtractor(sFunctionName, aObjects)); + } + } + + @Override + protected void acceptPath(NodeTerm term) + { + int cTerms = term.length(); + Object[] ao = new Object[cTerms]; + + for (int i = 1; i <= cTerms; i++) + { + term.termAt(i).accept(this); + ao[i - 1] = getResult(); + } + + setResult(ao); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Create a {@link ReflectionExtractor} with the specified method name + * and optionally the specified method parameters. + * + * @param sFunction the name of the method to use in the ReflectionExtractor + * @param aoArgs the parameters to use in the ReflectionExtractor, may be null + * + * @return a ReflectionExtractor with the specified method name and optional parameters + */ + private ReflectionExtractor asReflectionExtractor(String sFunction, Object[] aoArgs) + { + if (aoArgs == null || aoArgs.length == 0) + { + return new ReflectionExtractor(sFunction); + } + + return new ReflectionExtractor(sFunction, aoArgs); + } + + /** + * Do the Tree Walk for the set target AST + */ + protected void acceptTarget() + { + int nCount = m_term.length(); + + for (int i = 1; i <= nCount; i++) + { + m_term.termAt(i).accept(this); + m_aoResults[i - 1] = getResult(); + } + } + + /** + * Make a . separated String out of the given Object. + * + * @param oValue an Object or Object[] that is to be a ValueUpdater + * + * @return the resulting String + */ + public String makePathString(Object oValue) + { + if (oValue instanceof IdentityExtractor) + { + return null; + } + + if (oValue instanceof String) + { + return (String) oValue; + } + + if (oValue instanceof ReflectionExtractor) + { + ReflectionExtractor extractor = (ReflectionExtractor) oValue; + + return extractor.getMethodName(); + } + + if (oValue instanceof Object[]) + { + Object[] aoParts = (Object[]) oValue; + StringBuilder sb = new StringBuilder(); + + for (Object part : aoParts) + { + sb.append('.').append(makePathString(part)); + } + + return sb.substring(1); + } + + return null; + } + + /** + * Make a ValueUpdater out of the given Object. + * + * @param oValue an Object or Object[] that is to be a ValueUpdater + * + * @return the resulting ValueUpdater + */ + public ValueUpdater makeValueUpdater(Object oValue) + { + if (oValue instanceof IdentityExtractor) + { + return null; + } + + if (oValue instanceof String) + { + return new ReflectionUpdater(f_propertyBuilder.updaterStringFor((String) oValue)); + } + + if (oValue instanceof ReflectionExtractor) + { + ReflectionExtractor extractor = (ReflectionExtractor) oValue; + + return new ReflectionUpdater(extractor.getMethodName()); + } + + if (oValue instanceof ValueUpdater) + { + return (ValueUpdater) oValue; + } + + if (oValue instanceof Object[]) + { + Object[] aoObjects = (Object[]) oValue; + ValueUpdater updater = makeValueUpdater(aoObjects[aoObjects.length - 1]); + ValueExtractor[] aExtractors = new ValueExtractor[aoObjects.length - 1]; + + for (int i = 0; i < aoObjects.length - 1; ++i) + { + aExtractors[i] = makeValueExtractor(aoObjects[i]); + } + + return new CompositeUpdater(new ChainedExtractor(aExtractors), updater); + } + + throw new RuntimeException("Unable to determine field to set from: " + oValue); + } + + /** + * Make a ValueExtractor out of the given Object. + * + * @param oValue an Object or Object[] that is to be a ValueExtractor + * + * @return the resulting ValueExtractor + */ + public ValueExtractor makeValueExtractor(Object oValue) + { + if (oValue instanceof String) + { + return new ReflectionExtractor(f_propertyBuilder.extractorStringFor((String) oValue)); + } + + if (oValue instanceof ValueExtractor) + { + return (ValueExtractor) oValue; + } + + if (oValue instanceof Object[]) + { + Object[] aoObjects = (Object[]) oValue; + ValueExtractor[] aExtractors = new ValueExtractor[aoObjects.length]; + + for (int i = 0; i < aoObjects.length; ++i) + { + aExtractors[i] = makeValueExtractor(aoObjects[i]); + } + + return new ChainedExtractor(aExtractors); + } + + throw new RuntimeException("Unable to determine extractor for: " + oValue); + } + + /** + * Test to see if we need to construct object given a node type. + * + * @param sFunctor String representing node type + * @param term the Term that could result in Object creation + * + * @return result of testing + */ + protected boolean needsObjectCreation(String sFunctor, Term term) + { + if (sFunctor.equals("derefNode")) + { + return true; + } + else if (sFunctor.equals("callNode")) + { + if (m_fExtendedLanguage) + { + String sf = term.termAt(1).getFunctor(); + + return !(sf.equals("List") || sf.equals("Set") || sf.equals("Map")); + } + else + { + return true; + } + } + else if (m_fExtendedLanguage && sFunctor.equals(".object.")) + { + return true; + } + + return false; + } + + /** + * Create an Object[] from the given arguments. + * + * @param aoArgs an array of Object to be used as a pair + * + * @return a newly created ArrayList + */ + protected Object makePairLiteral(Object[] aoArgs) + { + int count = aoArgs != null + ? aoArgs.length + : 0; + + if (count == 2) + { + return aoArgs; + } + else + { + throw new RuntimeException("Pairs must be length 2 instead of length " + count); + } + } + + /** + * Create an ArrayList from the given arguments. + * + * @param aoArgs an array of Object to be added to ArrayList + * + * @return a newly created ArrayList + */ + protected Object makeListLiteral(Object[] aoArgs) + { + return Arrays.asList(aoArgs); + } + + /** + * Create an HashSet from the given arguments. + * + * @param aoArgs an array of Object to be added to ArrayList + * @return a newly created ArrayList + */ + protected Object makeSetLiteral(Object[] aoArgs) + { + return new HashSet(Arrays.asList(aoArgs)); + } + + /** + * Create a Set or Map from the given arguments. + * Make a Map if all given arguments are Object[] of length 2 + * otherwise make a Set. + * + * @param aoArgs an array of Object to be added to ArrayList + * + * @return a newly created HashSet or HashMap + */ + protected Object makeSetOrMapLiteral(Object[] aoArgs) + { + int nCount = aoArgs.length; + + if (nCount > 0 && isAllPairs(aoArgs)) + { + HashMap map = new HashMap(nCount); + + for (int i = 0; i < nCount; i++) + { + Object[] aoObjects = (Object[]) aoArgs[i]; + + map.put(aoObjects[0], aoObjects[1]); + } + + return map; + } + else + { + return makeSetLiteral(aoArgs); + } + } + + /** + * Test to see if the given argument is all Object[] of length 2. + * + * @param aoArgs an array of Object to be tested + * + * @return the boolean result + */ + private boolean isAllPairs(Object[] aoArgs) + { + int count = aoArgs.length; + + for (int i = 0; i < count; i++) + { + Object obj = aoArgs[i]; + Object[] aobj = null; + + if (obj instanceof Object[]) + { + aobj = (Object[]) obj; + } + + if (aobj == null || aobj.length != 2) + { + return false; + } + } + + return true; + } + + /** + * Create an Map from the given arguments. + * + * @param aoArgs an array of Object to be added to Map + * + * @return a newly created Map + */ + protected Object makeMapLiteral(Object[] aoArgs) + { + int nCount = aoArgs.length; + HashMap map = new HashMap(nCount); + + for (int i = 0; i < nCount; i++) + { + Object oValue = aoArgs[i]; + Object[] aoObjects = null; + + if (oValue instanceof Object[]) + { + aoObjects = (Object[]) oValue; + } + + if (aoObjects == null || aoObjects.length != 2) + { + throw new RuntimeException("Incorrect for argument to literal Map :" + Arrays.toString(aoObjects)); + } + + map.put(aoObjects[0], aoObjects[1]); + } + + return map; + } + + // ----- data members ---------------------------------------------------- + + /** + * The Term that is the AST that encodes select list + */ + protected NodeTerm m_term; + + /** + * The results of the classification + */ + protected Object[] m_aoResults; + + /** + * Flag that controls the path behaviors for lvalues and rvalues. + */ + protected boolean m_fRValueExpected = false; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/AdditionOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/AdditionOperator.java new file mode 100644 index 0000000000000..8bf3b0f026de9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/AdditionOperator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +/** + * A {@link BaseOperator} implementation representing the + * addition (+) operator. + * + * @author jk 2014.04.23 + * @since Coherence 12.2.1 + */ +public class AdditionOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an AdditionOperator. + */ + protected AdditionOperator() + { + super("+", false); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + InfixOPToken token = new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_SUM, OPToken.BINARY_OPERATOR_NODE, + OPToken.UNARY_OPERATOR_NODE); + + token.setPrefixAllowed(true); + tokenTable.addToken(token); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the AndOperator. + */ + public static final AdditionOperator INSTANCE = new AdditionOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/AndOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/AndOperator.java new file mode 100644 index 0000000000000..38ebd34a93578 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/AndOperator.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.Filter; + +import com.tangosol.util.filter.AllFilter; + +/** + * An operator representing a logical AND (&&). + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class AndOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs an AndOperator. + */ + protected AndOperator() + { + super("&&", true, "and"); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public AllFilter makeFilter(Object oLeft, Object oRight) + { + int cFilter = oLeft instanceof AllFilter + ? ((AllFilter) oLeft).getFilters().length + : 1; + + cFilter += oRight instanceof AllFilter + ? ((AllFilter) oRight).getFilters().length + : 1; + + Filter[] aFilters = new Filter[cFilter]; + + populateFilterArray(aFilters, (Filter) oLeft, (Filter) oRight); + + return new AllFilter(aFilters); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_LOGICAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Populate the specified target {@link Filter} array with the Filters in the source + * array. + * If the any of the Filters in the source array is an {@link AllFilter} then rather + * than adding the AllFilter itself to the target array all of the filters contained + * within the AllFilter are added to the array. + * + * @param aFilterDest the Filter array to be populated + * @param aFilterSrc the outer filter to add to the array + */ + protected void populateFilterArray(Filter[] aFilterDest, Filter... aFilterSrc) + { + int offset = 0; + + for (Filter filter : aFilterSrc) + { + if (filter instanceof AllFilter) + { + for (Filter innerFilter : ((AllFilter) filter).getFilters()) + { + aFilterDest[offset++] = innerFilter; + } + } + else + { + aFilterDest[offset++] = filter; + } + } + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the AndOperator. + */ + public static final AndOperator INSTANCE = new AndOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/BaseOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/BaseOperator.java new file mode 100644 index 0000000000000..12c4441ffde74 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/BaseOperator.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.TermWalker; + +import com.tangosol.util.Filter; +import com.tangosol.util.ImmutableArrayList; +import com.tangosol.util.ValueExtractor; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Set; + +/** + * A base class for CohQL Operator implementations. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public abstract class BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Create an instance of a BaseOperator with the specified + * symbol, conditional flag and aliases. + * + * @param sSymbol the symbol for this operator + * @param fConditional a flag indicating whether this operator is conditional + * @param asAlias an optional list of aliases for this operator + */ + protected BaseOperator(String sSymbol, boolean fConditional, String... asAlias) + { + f_sSymbol = sSymbol; + f_fConditional = fConditional; + f_asAlias = asAlias; + } + + // ----- BaseOperator methods ------------------------------------------- + + /** + * Return the symbol to use in CohQL that represents this operator. + * + * @return the symbol to use in CohQL that represents this operator + */ + public String getSymbol() + { + return f_sSymbol; + } + + /** + * Return the alternative symbols to use in CohQL that represent this operator. + * + * @return the alternative symbols to use in CohQL that represent this operator + */ + public String[] getAliases() + { + return f_asAlias; + } + + /** + * Create a {@link Filter} for this {@link BaseOperator} using the + * specified left and right {@link Term}s. + *

+ * Note: This method should be thread safe as operators are stored + * in a static map so may be called by multiple threads. + * + * @param termLeft the left term to use to build a Filter + * @param termRight the right term to use to build a Filter + * @param walker the {@link TermWalker} to use to process the left and + * right terms + * + * @return a Filter representing this operation. + */ + public F makeFilter(Term termLeft, Term termRight, TermWalker walker) + { + Object oLeft = walker.walk(termLeft); + Object oRight = walker.walk(termRight); + + return makeFilter(oLeft, oRight); + } + + /** + * Create a {@link Filter} for this {@link BaseOperator} using the + * specified left and right values. + *

+ * Note: This method should be thread safe as operators are stored + * in a static map so may be called by multiple threads. + * + * @param oLeft the left value to use to build a Filter + * @param oRight the right value to use to build a Filter + * + * @return a Filter representing this operation + */ + public F makeFilter(Object oLeft, Object oRight) + { + throw new UnsupportedOperationException("Unsupported binary operator (" + getSymbol() + ")"); + } + + /** + * Create a {@link ValueExtractor} for this {@link BaseOperator} using the + * specified left and right {@link Term}s. + *

+ * Note: This method should be thread safe as operators are stored + * in a static map so may be called by multiple threads. + * + * @param termLeft the left term to use to build a ValueExtractor + * @param termRight the right term to use to build a ValueExtractor + * @param walker the {@link TermWalker} to use to process the left and + * right terms + * + * @return a ValueExtractor representing this operation + */ + public ValueExtractor makeExtractor(Term termLeft, Term termRight, TermWalker walker) + { + Object oLeft = walker.walk(termLeft); + Object oRight = walker.walk(termRight); + + return makeExtractor(oLeft, oRight); + } + + /** + * Create a {@link ValueExtractor} for this {@link BaseOperator} using the + * specified left and right values. + *

+ * Note: This method should be thread safe as operators are stored + * in a static map so may be called by multiple threads. + * + * @param oLeft the left value to use to build a ValueExtractor + * @param oRight the right value to use to build a ValueExtractor + * + * @return a ValueExtractor representing this operation + */ + public ValueExtractor makeExtractor(Object oLeft, Object oRight) + { + throw new UnsupportedOperationException("Unsupported binary operator (" + getSymbol() + ")"); + } + + /** + * Return true if this operator can be used as a conditional operator. + * + * @return true if this operator can be used as a conditional operator + */ + public boolean isConditional() + { + return f_fConditional; + } + + /** + * Add this operator to the given {@link TokenTable}. + * This typically means adding this operator using its + * symbol and also adding any aliases. + * + * @param tokenTable the TokenTable to add this operator to + */ + public abstract void addToTokenTable(TokenTable tokenTable); + + // ----- Object methods ------------------------------------------------- + + @Override + public String toString() + { + return "BaseOperator(symbol=" + f_sSymbol + ", aliases=" + Arrays.toString(f_asAlias) + ", conditional=" + + f_fConditional + ')'; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Add any aliases of this operator to the specified token table. + * + * @param tokenTable the token table to add aliases to + */ + protected void addAliases(TokenTable tokenTable) + { + String sSymbol = getSymbol(); + String[] asAlias = getAliases(); + + for (String sAlias : asAlias) + { + tokenTable.alias(sAlias, sSymbol); + } + } + + /** + * Return an immutable Set accounting for the provided object being an array, + * a Collection or a single item in the returned Set. + * + * @param oValue either an object array, a collection or a single item to + * be returned as a Set + * + * @return a Set contained the provided object + */ + protected static Set unmodifiableSet(Object oValue) + { + if (oValue instanceof Set) + { + return (Set) oValue; + } + + Set set; + + if (oValue instanceof Object[]) + { + set = new ImmutableArrayList((Object[]) oValue).getSet(); + } + else if (oValue instanceof Collection) + { + set = new ImmutableArrayList((Collection) oValue).getSet(); + } + else + { + set = Collections.singleton(oValue); + } + + return set; + } + + // ----- data members --------------------------------------------------- + + /** + * The symbol for this operator. + */ + protected final String f_sSymbol; + + /** + * An array of optional aliases for this operator. + */ + protected final String[] f_asAlias; + + /** + * Flag indicating whether this operator can be used as a conditional operator, + * for example ==, >=, etc, as opposed to a non-conditional operator such as +, -, etc. + */ + protected final boolean f_fConditional; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/BetweenOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/BetweenOperator.java new file mode 100644 index 0000000000000..30842ae83e83e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/BetweenOperator.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.BetweenOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.BetweenFilter; + +/** + * An operator representing the "between" conditional operator. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class BetweenOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a BetweenOperator. + */ + protected BetweenOperator() + { + super("between", true); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public BetweenFilter makeFilter(Object oLeft, Object oRight) + { + Object[] ao = (Object[]) oRight; + + return new BetweenFilter((ValueExtractor) oLeft, (Comparable) ao[0], (Comparable) ao[1]); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new BetweenOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the BetweenOperator. + */ + public static final BetweenOperator INSTANCE = new BetweenOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ComparisonOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ComparisonOperator.java new file mode 100644 index 0000000000000..e98f1af7c85d9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ComparisonOperator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.TermWalker; + +import com.tangosol.util.filter.ComparisonFilter; + +/** + * A base class for comparison operators, which are operators + * that are used in conditional clauses such as equals, greater than, + * less than, etc. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public abstract class ComparisonOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a comparison operator with the given symbol and aliases. + * + * @param sSymbol the symbol for this operator + * @param asAliases any aliases for this operator + */ + protected ComparisonOperator(String sSymbol, String... asAliases) + { + super(sSymbol, true, asAliases); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public ComparisonFilter makeFilter(Term termLeft, Term termRight, TermWalker walker) + { + String sRightFunctor = termRight.getFunctor(); + + // Bug 27250717 - RFA: QueryHelper.createFilter causes StackOverFlow when comparing 2 identifiers + if (sRightFunctor.equals("identifier") && termLeft.getFunctor().equals("identifier")) + { + throw new UnsupportedOperationException("The use of identifier on both sides of an expression is not supported"); + } + + if (sRightFunctor.equals( + "identifier") &&!(((AtomicTerm) termRight.termAt(1)).getValue().equalsIgnoreCase( + "null") || ((AtomicTerm) termRight.termAt(1)).getValue().equalsIgnoreCase( + "true") || ((AtomicTerm) termRight.termAt(1)).getValue().equalsIgnoreCase( + "false")) || (sRightFunctor.equals("derefNode") || sRightFunctor.equals("callNode"))) + { + return flip().makeFilter(termRight, termLeft, walker); + } + + return super.makeFilter(termLeft, termRight, walker); + } + + // ----- ComparisonOperator API ----------------------------------------- + + /** + * Return the operator to use if this operation needs to + * be flipped due to the CohQL statement having the literal + * on the left hand side. + * For example if the statement was "2 == foo" this would + * need to be flipped to put the literal on the right so giving + * the statement "foo == 2" and the flipped operator is still ==. + * But for another example such as "2 >= foo" flipping this give + * the statement "foo <= 2" so the operator has changed from >= to <= + * + * @return the operator to use if this operation needs to + * be flipped due to the CohQL statement having the literal + * on the left hand side. + */ + public abstract BaseOperator flip(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ContainsAllOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ContainsAllOperator.java new file mode 100644 index 0000000000000..19a7107c6f808 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ContainsAllOperator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.ContainsOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.ContainsAllFilter; + +/** + * An operator representing the conditional "contains all" operation. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class ContainsAllOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a ContainsAllOperator. + */ + protected ContainsAllOperator() + { + super("contains_all", true); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public ContainsAllFilter makeFilter(Object oLeft, Object oRight) + { + return new ContainsAllFilter((ValueExtractor) oLeft, unmodifiableSet(oRight)); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new ContainsOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL), + OPToken.BINARY_OPERATOR_NODE); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the ContainsAllOperator. + */ + public static final ContainsAllOperator INSTANCE = new ContainsAllOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ContainsAnyOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ContainsAnyOperator.java new file mode 100644 index 0000000000000..0b5617077a088 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ContainsAnyOperator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.ContainsOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.ContainsAnyFilter; + +/** + * An operator representing the conditional "contains any" operation. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class ContainsAnyOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a ContainsAnyOperator. + */ + protected ContainsAnyOperator() + { + super("contains_any", true); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public ContainsAnyFilter makeFilter(Object oLeft, Object oRight) + { + return new ContainsAnyFilter((ValueExtractor) oLeft, unmodifiableSet(oRight)); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new ContainsOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL), + OPToken.BINARY_OPERATOR_NODE); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the ContainsAnyOperator. + */ + public static final ContainsAnyOperator INSTANCE = new ContainsAnyOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ContainsOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ContainsOperator.java new file mode 100644 index 0000000000000..841b5548a0198 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/ContainsOperator.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.ContainsOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.ContainsFilter; + +/** + * An operator representing the conditional "contains" operation. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class ContainsOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a ContainsOperator. + */ + protected ContainsOperator() + { + super("contains", true); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public ContainsFilter makeFilter(Object oLeft, Object oRight) + { + return new ContainsFilter((ValueExtractor) oLeft, oRight); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new ContainsOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL), + OPToken.BINARY_OPERATOR_NODE); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the ContainsOperator. + */ + public static final ContainsOperator INSTANCE = new ContainsOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/DivisionOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/DivisionOperator.java new file mode 100644 index 0000000000000..42a1633d70e1c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/DivisionOperator.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +/** + * An operator representing the conditional mathematical division operation. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class DivisionOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a DivisionOperator. + */ + protected DivisionOperator() + { + super("/", false); + } + + // ----- BaseOperator methods ------------------------------------------- + + /** + * Add this operator to the given {@link TokenTable}. + * This typically means adding this operator + * using its symbol and also adding any aliases. + * + * @param tokenTable the TokenTable to add this operator to. + */ + @Override + public void addToTokenTable(TokenTable tokenTable) + { + InfixOPToken token = new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_PRODUCT, OPToken.BINARY_OPERATOR_NODE); + + tokenTable.addToken(token); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the DivisionOperator. + */ + public static final DivisionOperator INSTANCE = new DivisionOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/EqualsOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/EqualsOperator.java new file mode 100644 index 0000000000000..ab5446b9aad53 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/EqualsOperator.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.TermWalker; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.ComparisonFilter; +import com.tangosol.util.filter.EqualsFilter; + +/** + * An operator implementation representing the equality operator. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class EqualsOperator + extends ComparisonOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a EqualsOperator. + */ + protected EqualsOperator() + { + super("==", "=", "is"); + } + + // ----- ComparisonOperator methods ------------------------------------- + + @Override + public ComparisonOperator flip() + { + return this; + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public ComparisonFilter makeFilter(Term termLeft, Term termRight, TermWalker walker) + { + Term termNot = Terms.newTerm(OPToken.UNARY_OPERATOR_NODE, AtomicTerm.createString("!")); + + if (termRight.getFunctor().equals(termNot.getFunctor()) && termNot.headChildrenTermEqual(termRight)) + { + // The statement was "foo is not x" but the parser thinks the + // operation is Equals when it should actually be a Not Equals + return NotEqualsOperator.INSTANCE.makeFilter(termLeft, termRight.termAt(2), walker); + } + + return super.makeFilter(termLeft, termRight, walker); + } + + @Override + public ComparisonFilter makeFilter(Object oLeft, Object oRight) + { + return new EqualsFilter((ValueExtractor) oLeft, oRight); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the EqualsOperator. + */ + public static final EqualsOperator INSTANCE = new EqualsOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/GreaterEqualsOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/GreaterEqualsOperator.java new file mode 100644 index 0000000000000..d7f5023a0c9f3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/GreaterEqualsOperator.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.ComparisonFilter; +import com.tangosol.util.filter.GreaterEqualsFilter; + +/** + * A class representing the conditional greater than or equal to operator. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class GreaterEqualsOperator + extends ComparisonOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a GreaterEqualsOperator. + */ + protected GreaterEqualsOperator() + { + super(">="); + } + + // ----- ComparisonOperator methods ------------------------------------- + + @Override + public ComparisonOperator flip() + { + return LessEqualsOperator.INSTANCE; + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public ComparisonFilter makeFilter(Object oLeft, Object oRight) + { + return new GreaterEqualsFilter((ValueExtractor) oLeft, (Comparable) oRight); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the GreaterEqualsOperator. + */ + public static final GreaterEqualsOperator INSTANCE = new GreaterEqualsOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/GreaterOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/GreaterOperator.java new file mode 100644 index 0000000000000..b370dc872ebd2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/GreaterOperator.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.ComparisonFilter; +import com.tangosol.util.filter.GreaterFilter; + +/** + * A class representing the greater than operator. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class GreaterOperator + extends ComparisonOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a GreaterOperator. + */ + protected GreaterOperator() + { + super(">"); + } + + // ----- ComparisonOperator methods ------------------------------------- + + @Override + public ComparisonOperator flip() + { + return LessOperator.INSTANCE; + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public ComparisonFilter makeFilter(Object oLeft, Object oRight) + { + return new GreaterFilter((ValueExtractor) oLeft, (Comparable) oRight); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the GreaterOperator. + */ + public static final GreaterOperator INSTANCE = new GreaterOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/InOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/InOperator.java new file mode 100644 index 0000000000000..ed15b0ab01166 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/InOperator.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.Filter; +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.InFilter; +import com.tangosol.util.filter.InKeySetFilter; + +/** + * A class representing the "in"operator. + *

+ * This operator creates instances of {@link InFilter} or + * {@link InKeySetFilter} depending on the left hand argument + * passed to the realize method. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class InOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a InOperator. + */ + protected InOperator() + { + super("in", true); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public Filter makeFilter(Object oLeft, Object oRight) + { + if (oLeft instanceof Filter) + { + return new InKeySetFilter((Filter) oLeft, unmodifiableSet(oRight)); + } + + return new InFilter((ValueExtractor) oLeft, unmodifiableSet(oRight)); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the InOperator. + */ + public static final InOperator INSTANCE = new InOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/LessEqualsOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/LessEqualsOperator.java new file mode 100644 index 0000000000000..d19915a3d3678 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/LessEqualsOperator.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.ComparisonFilter; +import com.tangosol.util.filter.LessEqualsFilter; + +/** + * A class representing the logical less than or equal to operator. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class LessEqualsOperator + extends ComparisonOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a LessEqualsOperator. + */ + protected LessEqualsOperator() + { + super("<="); + } + + // ----- ComparisonOperator methods ------------------------------------- + + @Override + public ComparisonOperator flip() + { + return GreaterEqualsOperator.INSTANCE; + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public ComparisonFilter makeFilter(Object oLeft, Object oRight) + { + return new LessEqualsFilter((ValueExtractor) oLeft, (Comparable) oRight); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the LessEqualsOperator. + */ + public static final LessEqualsOperator INSTANCE = new LessEqualsOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/LessOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/LessOperator.java new file mode 100644 index 0000000000000..3bdf1543e947d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/LessOperator.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.ComparisonFilter; +import com.tangosol.util.filter.LessFilter; + +/** + * A class representing the logical less than or equal to operator. + *

+ * This operator will produce instances of {@link LessFilter}. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class LessOperator + extends ComparisonOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a LessOperator. + */ + protected LessOperator() + { + super("<"); + } + + // ----- ComparisonOperator methods ------------------------------------- + + @Override + public ComparisonOperator flip() + { + return GreaterOperator.INSTANCE; + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public ComparisonFilter makeFilter(Object oLeft, Object oRight) + { + return new LessFilter((ValueExtractor) oLeft, (Comparable) oRight); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the LessOperator. + */ + public static final LessOperator INSTANCE = new LessOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/LikeOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/LikeOperator.java new file mode 100644 index 0000000000000..c0c0087129d8c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/LikeOperator.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.LikeOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.LikeFilter; + +/** + * A class representing the "like" operator. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class LikeOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a LikeOperator. + */ + protected LikeOperator() + { + super("like", true); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public LikeFilter makeFilter(Object oLeft, Object oRight) + { + if (oRight.getClass().isArray()) + { + Object[] ao = (Object[]) oRight; + char cEscape = ((String) ao[1]).charAt(0); + + return new LikeFilter((ValueExtractor) oLeft, (String) ao[0], cEscape, false); + } + + return new LikeFilter((ValueExtractor) oLeft, (String) oRight, (char) 0, false); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new LikeOPToken("like", OPToken.PRECEDENCE_RELATIONAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the LikeOperator. + */ + public static final LikeOperator INSTANCE = new LikeOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/MultiplicationOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/MultiplicationOperator.java new file mode 100644 index 0000000000000..178920a1251e2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/MultiplicationOperator.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +/** + * An operator representing the mathematical multiplication operation. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class MultiplicationOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a MultiplicationOperator. + */ + protected MultiplicationOperator() + { + super("*", false); + } + + // ----- BaseOperator methods ------------------------------------------- + + /** + * Add this operator to the given {@link TokenTable}. + * This typically means adding this operator + * using its symbol and also adding any aliases. + * + * @param tokenTable the TokenTable to add this operator to. + */ + @Override + public void addToTokenTable(TokenTable tokenTable) + { + InfixOPToken token = new InfixOPToken("*", OPToken.PRECEDENCE_PRODUCT, OPToken.BINARY_OPERATOR_NODE, + OPToken.UNARY_OPERATOR_NODE); + + token.setPrefixAllowed(true); + tokenTable.addToken(token); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the AndOperator. + */ + public static final MultiplicationOperator INSTANCE = new MultiplicationOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/NotEqualsOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/NotEqualsOperator.java new file mode 100644 index 0000000000000..98d1711433257 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/NotEqualsOperator.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.ValueExtractor; + +import com.tangosol.util.filter.ComparisonFilter; +import com.tangosol.util.filter.NotEqualsFilter; + +/** + * A class representing the not equals operator. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class NotEqualsOperator + extends ComparisonOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a NotEqualsOperator. + */ + protected NotEqualsOperator() + { + super("!=", "<>"); + } + + // ----- ComparisonOperator methods ------------------------------------- + + @Override + public ComparisonOperator flip() + { + return this; + } + + // ----- ComparisonOperator methods ------------------------------------- + + @Override + public ComparisonFilter makeFilter(Object oLeft, Object oRight) + { + return new NotEqualsFilter((ValueExtractor) oLeft, oRight); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_RELATIONAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the NotEqualsOperator. + */ + public static final NotEqualsOperator INSTANCE = new NotEqualsOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/OrOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/OrOperator.java new file mode 100644 index 0000000000000..0021d64765c04 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/OrOperator.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.Filter; + +import com.tangosol.util.filter.AnyFilter; + +/** + * A class representing the logical OR operator. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class OrOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an OrOperator. + */ + protected OrOperator() + { + super("||", true, "or"); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public AnyFilter makeFilter(Object oLeft, Object oRight) + { + int cFilter = oLeft instanceof AnyFilter + ? ((AnyFilter) oLeft).getFilters().length + : 1; + + cFilter += oRight instanceof AnyFilter + ? ((AnyFilter) oRight).getFilters().length + : 1; + + Filter[] aFilters = new Filter[cFilter]; + + populateFilterArray(aFilters, (Filter) oLeft, (Filter) oRight); + + return new AnyFilter(aFilters); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_LOGICAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Populate the specified target {@link Filter} array with the Filters in the source + * array. + *

+ * If the any of the Filters in the source array is an {@link AnyFilter} then rather + * than adding the AnyFilter itself to the target array all of the filters contained + * within the AnyFilter are added to the array. + * + * @param aFilterDest the Filter array to be populated + * @param aFilterSrc the outer filter to add to the array + */ + protected void populateFilterArray(Filter[] aFilterDest, Filter... aFilterSrc) + { + int offset = 0; + + for (Filter filter : aFilterSrc) + { + if (filter instanceof AnyFilter) + { + for (Filter innerFilter : ((AnyFilter) filter).getFilters()) + { + aFilterDest[offset++] = innerFilter; + } + } + else + { + aFilterDest[offset++] = filter; + } + } + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the OrOperator. + */ + public static final OrOperator INSTANCE = new OrOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/SubtractionOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/SubtractionOperator.java new file mode 100644 index 0000000000000..3007b3bdb3924 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/SubtractionOperator.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +/** + * An operator representing the conditional mathematical subtraction operation. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class SubtractionOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a SubtractionOperator. + */ + public SubtractionOperator() + { + super("-", false); + } + + // ----- BaseOperator methods ------------------------------------------- + + /** + * Add this operator to the given {@link TokenTable}. + * This typically means adding this operator + * using its symbol and also adding any aliases. + * + * @param tokenTable the TokenTable to add this operator to. + */ + @Override + public void addToTokenTable(TokenTable tokenTable) + { + InfixOPToken token = new InfixOPToken("-", OPToken.PRECEDENCE_SUM, OPToken.BINARY_OPERATOR_NODE, + OPToken.UNARY_OPERATOR_NODE); + + token.setPrefixAllowed(true); + tokenTable.addToken(token); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the AndOperator. + */ + public static final SubtractionOperator INSTANCE = new SubtractionOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/XorOperator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/XorOperator.java new file mode 100644 index 0000000000000..9bce0d8da6931 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/XorOperator.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.operator; + +import com.tangosol.coherence.dsltools.precedence.InfixOPToken; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + +import com.tangosol.util.Filter; + +import com.tangosol.util.filter.XorFilter; + +/** + * A class representing the logical XOR operator. + *

+ * This class produces instances of {@link XorFilter}. + * + * @author jk 2013.12.03 + * @since Coherence 12.2.1 + */ +public class XorOperator + extends BaseOperator + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs an XorOperator. + */ + protected XorOperator() + { + super("^^", true, "xor"); + } + + // ----- BaseOperator methods ------------------------------------------- + + @Override + public Filter makeFilter(Object oLeft, Object oRight) + { + return new XorFilter((Filter) oLeft, (Filter) oRight); + } + + @Override + public void addToTokenTable(TokenTable tokenTable) + { + tokenTable.addToken(new InfixOPToken(f_sSymbol, OPToken.PRECEDENCE_LOGICAL, OPToken.BINARY_OPERATOR_NODE)); + addAliases(tokenTable); + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of the XorOperator. + */ + public static final XorOperator INSTANCE = new XorOperator(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/package.html new file mode 100644 index 0000000000000..903e1173ffe7b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/operator/package.html @@ -0,0 +1,6 @@ + +This package contains implementations of the {@link com.tangosol.coherence.dslquery.BaseOperator} +interface. + +@serial exclude + \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/AbstractQueryPlusStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/AbstractQueryPlusStatementBuilder.java new file mode 100644 index 0000000000000..f423b6f15600c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/AbstractQueryPlusStatementBuilder.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.queryplus; + +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.Statement; +import com.tangosol.coherence.dslquery.StatementBuilder; + +import com.tangosol.coherence.dsltools.precedence.IdentifierOPToken; + +import java.io.PrintWriter; + +/** + * This is the base class for command builders that are specific to the QueryPlus + * tool rather than general CohQL statements. + * + * @author jk 2014.01.06 + * @since Coherence 12.2.1 + */ +public abstract class AbstractQueryPlusStatementBuilder + implements StatementBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a AbstractQueryPlusStatementBuilder. + */ + protected AbstractQueryPlusStatementBuilder() + { + } + + // ----- AbstractQueryPlusStatementBuilder methods ---------------------- + + /** + * Return the OPToken for this command. + * + * @return the OPToken for this command + */ + public abstract AbstractOPToken instantiateOpToken(); + + // ----- inner class: AbstractOPToken ----------------------------------- + + /** + * An {@link com.tangosol.coherence.dsltools.precedence.OPToken} implementation + * that holds the name of the functor associated to an OPToken. + */ + public abstract class AbstractOPToken + extends IdentifierOPToken + { + // ----- constructors ----------------------------------------------- + + /** + * Construct an AbstractOpToken. + * + * @param sIdentifier the identifier of this AbstractOPToken + * @param sNudASTName the name of this token in an AST + * @param sFunctor the name of this token's functor + */ + public AbstractOPToken(String sIdentifier, String sNudASTName, String sFunctor) + { + super(sIdentifier, sNudASTName); + + f_sFunctor = sFunctor; + } + + // ----- accessor methods ------------------------------------------- + + /** + * Return the functor for this OPToken. + * + * @return the functor string for this OPToken + */ + public String getFunctor() + { + return f_sFunctor; + } + + // ----- data members ----------------------------------------------- + + /** + * The Functor string used by the parser for this token. + */ + protected final String f_sFunctor; + } + + // ----- inner class: AbstractStatement ------------------------------------- + + /** + * An abstract base class that allows sub classes to implement the applicable + * methods on {@link Statement}. + */ + public abstract class AbstractStatement + implements Statement + { + // ----- Statement interface ---------------------------------------- + + @Override + public void sanityCheck(ExecutionContext ctx) + { + } + + @Override + public void showPlan(PrintWriter out) + { + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + return null; + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/CommandsStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/CommandsStatementBuilder.java new file mode 100644 index 0000000000000..6e088867b34ac --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/CommandsStatementBuilder.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.queryplus; + +import com.tangosol.coherence.dslquery.CoherenceQueryLanguage; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.QueryPlus; +import com.tangosol.coherence.dslquery.StatementBuilder; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.Collection; +import java.util.List; + +/** + * A class that builds the QueryPlus "COMMANDS" command. + * + * @author jk 2014.01.06 + * @since Coherence 12.2.1 + */ +public class CommandsStatementBuilder + extends AbstractQueryPlusStatementBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a CommandsStatementBuilder that builds + * a {@link CommandsStatementBuilder.CommandsQueryPlusStatement}. + */ + public CommandsStatementBuilder() + { + f_command = new CommandsQueryPlusStatement(); + } + + // ----- StatementBuilder interface ------------------------------------- + + @Override + public CommandsQueryPlusStatement realize(ExecutionContext ctx, NodeTerm term, + List listBindVars, ParameterResolver namedBindVars) + { + return f_command; + } + + @Override + public String getSyntax() + { + return "COMMANDS"; + } + + @Override + public String getDescription() + { + return "Print a simple list of commands without explanations."; + } + + @Override + public AbstractOPToken instantiateOpToken() + { + return new CommandsOPToken(); + } + + // ----- inner class: CommandsOPToken ----------------------------------- + + /** + * A CohQL OPToken representing the QueryPlus "commands" command. + */ + public class CommandsOPToken + extends AbstractOPToken + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a CommandsOPToken. + */ + public CommandsOPToken() + { + super("commands", OPToken.IDENTIFIER_NODE, "showCommands"); + } + + // ----- AbstractOPToken methods ---------------------------------------- + + public Term nud(OPParser parser) + { + return Terms.newTerm(getFunctor()); + } + } + + // ----- inner class: CommandsOPToken ----------------------------------- + + /** + * The implementation of the QueryPlus "commands" command. + */ + public class CommandsQueryPlusStatement + extends AbstractStatement + { + @Override + public StatementResult execute(ExecutionContext ctx) + { + PrintWriter out = ctx.getWriter(); + + QueryPlus.DependenciesHelper.usage(out); + out.println("BYE | QUIT"); + + CoherenceQueryLanguage language = ctx.getCoherenceQueryLanguage(); + Collection> colBuilders = language.getStatementBuilders().values(); + + for (StatementBuilder builder : colBuilders) + { + out.println(builder.getSyntax()); + } + + return StatementResult.NULL_RESULT; + } + } + + // ----- data members --------------------------------------------------- + + /** + * An instance of the {@link CommandsStatementBuilder.CommandsQueryPlusStatement}. + */ + protected final CommandsQueryPlusStatement f_command; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/ExtendedLanguageStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/ExtendedLanguageStatementBuilder.java new file mode 100644 index 0000000000000..bfc2c4ed6e443 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/ExtendedLanguageStatementBuilder.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.queryplus; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.List; + +/** + * A class that builds the QueryPlus "EXTENDED LANGUAGE" command. + * + * @author jk 2014.01.06 + * @since Coherence 12.2.1 + */ +public class ExtendedLanguageStatementBuilder + extends AbstractQueryPlusStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public ExtendedLanguageQueryPlusStatement realize(ExecutionContext ctx, NodeTerm term, + List listBindVars, ParameterResolver namedBindVars) + { + AtomicTerm action = (AtomicTerm) term.termAt(1); + + if ("on".equals(action.getValue())) + { + return new ExtendedLanguageQueryPlusStatement(true); + } + else if ("off".equals(action.getValue())) + { + return new ExtendedLanguageQueryPlusStatement(false); + } + + throw new CohQLException("Invalid extended language command - valid syntax is: " + getSyntax()); + } + + @Override + public String getSyntax() + { + return "EXTENDED LANGUAGE (ON | OFF)"; + } + + @Override + public String getDescription() + { + return "Controls extended language mode."; + } + + @Override + public AbstractOPToken instantiateOpToken() + { + return new ExtendedLanguageCommandOPToken(); + } + + // ----- inner class: ExtendedLanguageCommandOPToken -------------------- + + /** + * A CohQL OPToken representing the QueryPlus "extended language" command. + */ + public class ExtendedLanguageCommandOPToken + extends AbstractOPToken + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a ExtendedLanguageCommandOPToken. + */ + public ExtendedLanguageCommandOPToken() + { + super("extended", OPToken.IDENTIFIER_NODE, "extendedLanguageCommand"); + } + + // ----- OpToken methods -------------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + + scanner.advanceWhenMatching("language"); + + String action = scanner.getCurrentAsStringWithAdvance(); + + return Terms.newTerm(getFunctor(), AtomicTerm.createString(action)); + } + } + + // ----- inner class: ExtendedLanguageQueryPlusStatement ---------------- + + /** + * The command to turn on or off extended CohQL. + */ + public class ExtendedLanguageQueryPlusStatement + extends AbstractStatement + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a ExtendedLanguageQueryPlusStatement to turn on or off + * extended CohQL. + * + * @param fExtended true to turn on extended CohQL + */ + protected ExtendedLanguageQueryPlusStatement(boolean fExtended) + { + m_fExtended = fExtended; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.setExtendedLanguage(m_fExtended); + + return StatementResult.NULL_RESULT; + } + + // ----- data members ----------------------------------------------- + + /** + * Flag indicating whether this command turns trace on or off. + */ + protected boolean m_fExtended; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/HelpStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/HelpStatementBuilder.java new file mode 100644 index 0000000000000..496be357bf0f6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/HelpStatementBuilder.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.queryplus; + +import com.tangosol.coherence.dslquery.CoherenceQueryLanguage; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.QueryPlus; +import com.tangosol.coherence.dslquery.StatementBuilder; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.Collection; +import java.util.List; + +/** + * A {@link StatementBuilder} that builds the QueryPlus "HELP" command. + * + * @author jk 2014.01.06 + * @since Coherence 12.2.1 + */ +public class HelpStatementBuilder + extends AbstractQueryPlusStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public HelpQueryPlusStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + return f_command; + } + + @Override + public String getSyntax() + { + return "HELP"; + } + + @Override + public String getDescription() + { + return "Displays the syntax and description of all of the commands"; + } + + @Override + public AbstractOPToken instantiateOpToken() + { + return new HelpCommandOPToken(); + } + + // ----- inner class: HelpCommandOPToken -------------------------------- + + /** + * A CohQL OPToken representing the QueryPlus "help" command. + */ + public class HelpCommandOPToken + extends AbstractOPToken + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a HelpCommandOPToken. + */ + public HelpCommandOPToken() + { + super("help", OPToken.IDENTIFIER_NODE, "showHelp"); + } + + // ----- OpToken methods -------------------------------------------- + + public Term nud(OPParser parser) + { + return Terms.newTerm(getFunctor()); + } + } + + // ----- inner class: HelpQueryPlusStatement ---------------------------- + + /** + * A class representing the QueryPlus "HELP" command. + */ + public class HelpQueryPlusStatement + extends AbstractStatement + { + + @Override + public StatementResult execute(ExecutionContext ctx) + { + PrintWriter out = ctx.getWriter(); + + QueryPlus.DependenciesHelper.usage(out); + out.println(); + out.println(sEighty); + out.println("BYE | QUIT "); + out.println("Exits the command line tool."); + out.println(); + + CoherenceQueryLanguage language = ctx.getCoherenceQueryLanguage(); + Collection> colBuilders = language.getStatementBuilders().values(); + + for (StatementBuilder builder : colBuilders) + { + out.println(sEighty); + out.println(builder.getSyntax()); + out.println(); + out.println(builder.getDescription()); + out.println(); + } + + // Display information about the valid CohQL operators + out.println(sEighty); + out.println("For WHERE clauses the currently Supported conditionals are:\n" + + "Comparison operators: =, >, >=, <, <=, <>, [ NOT ] BETWEEN, [ NOT ] LIKE,\n" + + "[ NOT ] IN, IS [ NOT ] NULL, CONTAINS [ALL | ANY] *\n" + + "Logical operators: (AND, OR, NOT)\n" + + "Literal numbers, and the constants true, false, and null\n" + "\n" + + "Arguments to operators are properties and converted to Bean style getters and\n" + + "the \".\" operator may be used to make chains of calls. The optional alias may be\n" + + "prepended onto the beginning of these path expressions. The Pseudo functions\n" + + "key(), and value() may be used to specify the use of a key as in\n" + + "\"key() between 10 and 50\".\n" + + "The value() pseudo function is shorthand for the entire element as is the alias.\n" + + "The key() pseudo function my be specified as key(alias) for compatibility with\n" + + "JPQL."); + + return StatementResult.NULL_RESULT; + } + } + + // ----- data members --------------------------------------------------- + + private static final String sEighty = "----------------------------------------" + + "----------------------------------------"; + /** + * A {@link HelpStatementBuilder.HelpQueryPlusStatement} instance. + */ + protected final HelpQueryPlusStatement f_command = new HelpQueryPlusStatement(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/SanityCheckStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/SanityCheckStatementBuilder.java new file mode 100644 index 0000000000000..ed85b68f9a1fa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/SanityCheckStatementBuilder.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.queryplus; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.List; + +/** + * A {@link com.tangosol.coherence.dslquery.StatementBuilder} that builds + * the QueryPlus "SANITY CHECK" command. + * + * @author jk 2014.01.06 + * @since Coherence 12.2.1 + */ +public class SanityCheckStatementBuilder + extends AbstractQueryPlusStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public SanityCheckQueryPlusStatement realize(ExecutionContext ctx, NodeTerm term, + List listBindVars, ParameterResolver namedBindVars) + { + AtomicTerm action = (AtomicTerm) term.termAt(1); + + if ("on".equals(action.getValue())) + { + return new SanityCheckQueryPlusStatement(true); + } + else if ("off".equals(action.getValue())) + { + return new SanityCheckQueryPlusStatement(false); + } + + throw new CohQLException("Invalid sanity check command - valid syntax is: " + getSyntax()); + } + + @Override + public String getSyntax() + { + return "SANITY [CHECK] (ON | OFF)"; + } + + @Override + public String getDescription() + { + return "Controls sanity checking mode to verify a cache exists prior to executing an\n" + + "operation on it."; + } + + @Override + public AbstractOPToken instantiateOpToken() + { + return new SanityCheckCommandOPToken(); + } + + // ----- inner class: SanityCheckCommandOPToken ------------------------- + + /** + * A CohQL OPToken representing the QueryPlus "sanity check" command. + */ + public class SanityCheckCommandOPToken + extends AbstractOPToken + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a SanityCheckCommandOPToken. + */ + public SanityCheckCommandOPToken() + { + super("sanity", OPToken.IDENTIFIER_NODE, "sanityCheckCommand"); + } + + // ----- OpToken methods -------------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + String sAction = scanner.getCurrentAsString(); + + if ("check".equalsIgnoreCase(sAction)) + { + sAction = scanner.next().getValue(); + } + + if ("on".equals(sAction) || "off".equals(sAction)) + { + scanner.advance(); + + return Terms.newTerm(getFunctor(), AtomicTerm.createString(sAction)); + } + + return super.nud(parser); + } + } + + // ----- inner class: SanityCheckQueryPlusStatement --------------------- + + /** + * A class representing the QueryPlus "SANITY CHECK" command. + */ + public class SanityCheckQueryPlusStatement + extends AbstractStatement + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a SanityCheckQueryPlusStatement to turn on or off + * QueryPlus sanity checks. + * + * @param fSanity true to turn on sanity checking, false to turn it off. + */ + protected SanityCheckQueryPlusStatement(boolean fSanity) + { + f_fSanity = fSanity; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.setSanityCheckingEnabled(f_fSanity); + + return StatementResult.NULL_RESULT; + } + + // ----- data members ----------------------------------------------- + + /** + * Flag indicating whether this command turns sanity checking on or off. + */ + protected final boolean f_fSanity; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/ServicesStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/ServicesStatementBuilder.java new file mode 100644 index 0000000000000..93882dead97d4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/ServicesStatementBuilder.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.queryplus; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.CacheService; +import com.tangosol.net.Cluster; +import com.tangosol.net.Service; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; + +/** + * A {@link com.tangosol.coherence.dslquery.StatementBuilder} that builds + * the QueryPlus "SERVICES" command. + * + * @author jk 2014.01.06 + * @since Coherence 12.2.1 + */ +public class ServicesStatementBuilder + extends AbstractQueryPlusStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public ServicesQueryPlusStatement realize(ExecutionContext ctx, NodeTerm term, + List listBindVars, ParameterResolver namedBindVars) + { + try + { + AtomicTerm atomicTerm = (AtomicTerm) term.termAt(1); + String sAction = atomicTerm.getValue(); + + return new ServicesQueryPlusStatement(sAction); + } + catch (IllegalArgumentException e) + { + throw new CohQLException("Invalid services command - valid syntax is: " + getSyntax()); + } + } + + @Override + public String getSyntax() + { + return "SERVICES INFO"; + } + + @Override + public String getDescription() + { + return "Displays information about the Services on this member."; + } + + @Override + public AbstractOPToken instantiateOpToken() + { + return new ServicesCommandOPToken(); + } + + // ----- inner class: ServicesCommandOPToken ---------------------------- + + /** + * A CohQL OPToken representing the QueryPlus "services" command. + */ + public class ServicesCommandOPToken + extends AbstractOPToken + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a ServicesCommandOPToken. + */ + public ServicesCommandOPToken() + { + super("services", OPToken.IDENTIFIER_NODE, "servicesCommand"); + } + + // ----- OPToken methods -------------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + String sAction = scanner.getCurrentAsString(); + + if (sAction != null) + { + scanner.advance(); + + return Terms.newTerm(getFunctor(), AtomicTerm.createString(sAction)); + } + + return super.nud(parser); + } + } + + // ----- inner class: ServicesCommandOPToken ---------------------------- + + /** + * A class representing the "SERVICES" QueryPlus command. + * + * @author jk 2014.03.17 + */ + public class ServicesQueryPlusStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a ServicesQueryPlusStatement that will execute the + * specified service command action. + * + * @param sAction the action this statement will perform + */ + public ServicesQueryPlusStatement(String sAction) + { + f_sAction = sAction; + } + + // ----- Statement methods --------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + if (f_sAction.toLowerCase().equals("info")) + { + return dumpServiceInfo(ctx); + } + + return StatementResult.NULL_RESULT; + } + + // ----- helper methods --------------------------------------------- + + /** + * Return information about the current set of Coherence services. + * + * @param ctx the {@link ExecutionContext} to use to obtain the + * current Coherence {@link Cluster} + * + * @return information about the current set of Coherence services + */ + public StatementResult dumpServiceInfo(ExecutionContext ctx) + { + Cluster cluster = ctx.getCluster(); + List listInfo = new ArrayList<>(); + + for (Enumeration enumServiceNames = cluster.getServiceNames(); enumServiceNames.hasMoreElements(); ) + { + String serviceName = (String) enumServiceNames.nextElement(); + Service service = cluster.getService(serviceName); + + if (service instanceof CacheService) + { + listInfo.add(serviceName); + + CacheService cacheService = (CacheService) service; + + for (Enumeration cacheNames = cacheService.getCacheNames(); cacheNames.hasMoreElements(); ) + { + listInfo.add("\t" + String.valueOf(cacheNames.nextElement())); + } + } + } + + return new DefaultStatementResult(listInfo); + } + + // ----- data members ----------------------------------------------- + + /** + * Flag indicating whether this command turns sanity checking on or off. + */ + protected final String f_sAction; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/SetTimeoutStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/SetTimeoutStatementBuilder.java new file mode 100644 index 0000000000000..f921f7870489f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/SetTimeoutStatementBuilder.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.queryplus; + +import com.oracle.coherence.common.util.Duration; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.Statement; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; + +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.List; +import java.util.StringJoiner; + +/** + * A class that builds the QueryPlus "ALTER SESSION SET TIMEOUT millis" statement. + * + * @author jk 2014.03.05 + * @since 12.2.1 + */ +public class SetTimeoutStatementBuilder + extends AbstractQueryPlusStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public Statement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, ParameterResolver namedBindVars) + { + AtomicTerm atomicTerm = (AtomicTerm) term.termAt(1); + String sTimeout = atomicTerm.getValue(); + Duration timeout; + + if (atomicTerm.getTypeCode() == AtomicTerm.NULLLITERAL || sTimeout.isEmpty()) + { + throw new CohQLException("timeout value cannot be null or empty string"); + } + + try + { + if (sTimeout.matches("\\d+$")) + { + timeout = new Duration(sTimeout, Duration.Magnitude.MILLI); + } + else + { + timeout = new Duration(sTimeout); + } + + return new SetTimeoutStatement(timeout); + } + catch (IllegalArgumentException e) + { + throw new CohQLException("The timeout value of [" + sTimeout + "] is invalid"); + } + } + + @Override + public AbstractOPToken instantiateOpToken() + { + return new SetTimeoutOPToken(); + } + + @Override + public String getSyntax() + { + return "ALTER SESSION SET TIMEOUT "; + } + + @Override + public String getDescription() + { + return "Set the timeout value to be used by the current QueryPlus session. Statements\n" + + "will be interrupted if they take longer than this time to execute."; + } + + // ----- inner class: SetTimeoutOPToken ---------------------------------- + + /** + * A CohQL OPToken representing the QueryPlus + * "ALTER SESSION SET TIMEOUT" statement. + */ + public class SetTimeoutOPToken + extends AbstractOPToken + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a SetTimeoutOPToken. + */ + public SetTimeoutOPToken() + { + super("timeout", OPToken.IDENTIFIER_NODE, "setTimeout"); + } + + // ----- AbstractOPToken methods ------------------------------------ + + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + + if (scanner.isEndOfStatement()) + { + throw new OPException("Invalid ALTER SESSION SET TIMEOUT statement, timeout value required."); + } + + StringJoiner joiner = new StringJoiner(" "); + + while (!scanner.isEndOfStatement()) + { + joiner.add(scanner.getCurrentAsStringWithAdvance()); + } + + String sTimeout = joiner.toString(); + + return Terms.newTerm(getFunctor(), AtomicTerm.createString(sTimeout)); + } + } + + // ----- inner class: SetTimeoutStatement ----------------------------- + + /** + * The implementation of the QueryPlus "ALTER SESSION SET TIMEOUT" statement. + */ + public class SetTimeoutStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a SetTimeoutStatement that will set the + * current timeout used by the QueryPlus session to the + * specified timeout. + * + * @param timeout the value to set for the statement timeout + */ + public SetTimeoutStatement(Duration timeout) + { + f_durationTimeout = timeout; + } + + // ----- AbstractStatement methods ---------------------------------- + + /** + * Set the current timeout to be used by the CohQL session. + * + * @param ctx the {@link ExecutionContext context} to use + * + * @return Always returns {@link StatementResult#NULL_RESULT} + */ + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.setTimeout(f_durationTimeout); + + return new DefaultStatementResult("CohQL statement timeout set to " + f_durationTimeout.toString(true)); + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the timeout to set as the current timeout. + */ + protected final Duration f_durationTimeout; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/TraceStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/TraceStatementBuilder.java new file mode 100644 index 0000000000000..644643296ff47 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/TraceStatementBuilder.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.queryplus; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.precedence.IdentifierOPToken; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.List; + +/** + * A {@link com.tangosol.coherence.dslquery.StatementBuilder} that builds the + * QueryPlus "TRACE" command. + * + * @author jk 2014.01.06 + * @since Coherence 12.2.1 + */ +public class TraceStatementBuilder + extends AbstractQueryPlusStatementBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a TraceStatementBuilder. + * + * @param tokenDelegate the delegate {@link OPToken} + */ + public TraceStatementBuilder(OPToken tokenDelegate) + { + f_tokenDelegate = tokenDelegate; + } + + // ----- StatementBuilder interface ------------------------------------- + + @Override + public TraceQueryPlusStatement realize(ExecutionContext ctx, NodeTerm term, + List listBindVars, ParameterResolver namedBindVars) + { + AtomicTerm atomicTerm = (AtomicTerm) term.termAt(1); + + if ("on".equals(atomicTerm.getValue())) + { + return new TraceQueryPlusStatement(true); + } + else if ("off".equals(atomicTerm.getValue())) + { + return new TraceQueryPlusStatement(false); + } + + throw new CohQLException("Invalid trace command - valid syntax is: " + getSyntax()); + } + + @Override + public String getSyntax() + { + return "TRACE (ON | OFF)"; + } + + @Override + public String getDescription() + { + return "Controls tracing mode. This shows information that can help with debugging"; + } + + @Override + public AbstractOPToken instantiateOpToken() + { + return new TraceCommandOPToken(); + } + + // ----- inner class: TraceCommandOPToken ------------------------------- + + /** + * A CohQL OPToken representing the QueryPlus "trace" command. + */ + public class TraceCommandOPToken + extends AbstractOPToken + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a TraceCommandOPToken. + */ + public TraceCommandOPToken() + { + super("trace", OPToken.IDENTIFIER_NODE, "traceCommand"); + } + + // ----- OpToken methods -------------------------------------------- + + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + String action = scanner.getCurrentAsString(); + + if ("on".equals(action) || "off".equals(action) || f_tokenDelegate == null) + { + scanner.advance(); + + return Terms.newTerm(getFunctor(), AtomicTerm.createString(action)); + } + + return f_tokenDelegate.nud(parser); + } + + @Override + public Term led(OPParser parser, Term termLeft) + { + if (f_tokenDelegate == null) + { + return super.led(parser, termLeft); + } + + return f_tokenDelegate.led(parser, termLeft); + } + } + + // ----- inner class: TraceQueryPlusStatement --------------------------- + + /** + * A command that turns on or off QueryPlus trace. + */ + public class TraceQueryPlusStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a command to turn on or off QueryPlus tracing. + * + * @param fTrace true to turn trace on, false to turn it off + */ + protected TraceQueryPlusStatement(boolean fTrace) + { + m_fTrace = fTrace; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.setTraceEnabled(m_fTrace); + + return StatementResult.NULL_RESULT; + } + + // ----- data members ----------------------------------------------- + + /** + * Flag indicating whether this command turns trace on or off. + */ + protected boolean m_fTrace; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link IdentifierOPToken} to delegate to if we cannot process the token. Typically this would be a previously + * registered OPToken for the same token string. + */ + protected final OPToken f_tokenDelegate; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/WheneverStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/WheneverStatementBuilder.java new file mode 100644 index 0000000000000..de09e6c2a3f99 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/WheneverStatementBuilder.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.queryplus; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import java.util.List; + +/** + * A class that builds the QueryPlus "WHENEVER" command. + * + * @author jk 2014.08.05 + * @since Coherence 12.2.1 + */ +public class WheneverStatementBuilder + extends AbstractQueryPlusStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public WheneverQueryPlusStatement realize(ExecutionContext ctx, NodeTerm term, + List listBindVars, ParameterResolver namedBindVars) + { + AtomicTerm action = (AtomicTerm) term.termAt(1); + String sValue = action.getValue(); + + if ("continue".equalsIgnoreCase(sValue)) + { + return new WheneverQueryPlusStatement(false); + } + else if ("exit".equalsIgnoreCase(sValue)) + { + return new WheneverQueryPlusStatement(true); + } + + throw new CohQLException("Invalid whenever command - valid syntax is: " + getSyntax()); + } + + @Override + public String getSyntax() + { + return "WHENEVER COHQLERROR THEN (CONTINUE | EXIT)"; + } + + @Override + public String getDescription() + { + return "Controls the action taken by QueryPlus when a statement fails to execute."; + } + + @Override + public AbstractOPToken instantiateOpToken() + { + return new WheneverCommandOPToken(); + } + + // ----- inner class: WheneverCommandOPToken ---------------------------- + + /** + * A CohQL OPToken representing the QueryPlus "whenever" command. + */ + public class WheneverCommandOPToken + extends AbstractOPToken + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a WheneverCommandOPToken. + */ + public WheneverCommandOPToken() + { + super("whenever", OPToken.IDENTIFIER_NODE, "wheneverCommand"); + } + + // ----- OpToken methods -------------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + + scanner.advanceWhenMatching("cohqlerror"); + scanner.advanceWhenMatching("then"); + + String sAction = scanner.getCurrentAsStringWithAdvance(); + + return Terms.newTerm(getFunctor(), AtomicTerm.createString(sAction)); + } + } + + // ----- inner class: WheneverQueryPlusStatement ------------------------ + + /** + * The command to set the QueryPlus error action. + */ + public class WheneverQueryPlusStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a WheneverQueryPlusStatement to set the error action. + * + * @param fStopOnError flag indicating that statement processing + * should stop if an error occurs + */ + protected WheneverQueryPlusStatement(boolean fStopOnError) + { + m_fStopOnError = fStopOnError; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.setStopOnError(m_fStopOnError); + + return StatementResult.NULL_RESULT; + } + + // ----- data members ----------------------------------------------- + + /** + * The QueryPlus error action. + */ + protected boolean m_fStopOnError; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/package.html new file mode 100644 index 0000000000000..9cb08215b896a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/queryplus/package.html @@ -0,0 +1,6 @@ + +This package contains commands that are specific to the {@link com.tangosol.coherence.dslquery.QueryPlus} +tool rather than general CohQL commands. + +@serial exclude + \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/AbstractStatement.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/AbstractStatement.java new file mode 100644 index 0000000000000..042e90d5a615c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/AbstractStatement.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.Statement; + +import com.tangosol.util.Base; + + +/** + * A base class for {@link Statement} implementations. + * + * @author jk 2013.12.10 + * @since Coherence 12.2.1 + */ +public abstract class AbstractStatement + extends Base + implements Statement + { + // ----- Statement interface -------------------------------------------- + + @Override + public void sanityCheck(ExecutionContext ctx) + { + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + // default is we do not want a confirmation + return null; + } + + /** + * Test to see whether the given String is a known cache name. + * + * @param sName the cache name + * @param ctx the execution context of the CohQL query + * + * @throws AssertionError if a cache with the given name + * does not exist. + */ + protected void assertCacheName(String sName, ExecutionContext ctx) + { + if (!ctx.getCacheFactory().isCacheActive(sName, null)) + { + throw new AssertionError(String.format("cache '%s' does not exist!", sName)); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/AbstractStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/AbstractStatementBuilder.java new file mode 100644 index 0000000000000..4a9b3777be377 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/AbstractStatementBuilder.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.FilterBuilder; +import com.tangosol.coherence.dslquery.Statement; +import com.tangosol.coherence.dslquery.StatementBuilder; + +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.Filter; + +import com.tangosol.util.filter.AlwaysFilter; + +import java.util.List; + +/** + * A base class for {@link com.tangosol.coherence.dslquery.StatementBuilder} implementations. + * + * @author jk 2013.12.09 + * @since Coherence 12.2.1 + */ +public abstract class AbstractStatementBuilder + implements StatementBuilder + { + // ----- helper methods ------------------------------------------------- + + /** + * Build a {@link Filter} for the given cache using the given where clause, + * alias and bind environments. + * + * @param termWhere the {@link NodeTerm} containing the where clause + * @param sCacheName the name of the cache that the filter will be built for + * @param sAlias the table/cache alias used in the where clause, may be null + * @param listBindVars bind variables to be used to replace any numeric bind + * variables in the where clause + * @param namedBindVars named bind variables to be used to replace any named bind + * variables in the where clause + * @param ctx the {@link ExecutionContext} to use + * + * @return a {@link Filter} created from the given where clause + */ + protected static Filter ensureFilter(NodeTerm termWhere, String sCacheName, String sAlias, List listBindVars, + ParameterResolver namedBindVars, ExecutionContext ctx) + { + if (termWhere == null) + { + return AlwaysFilter.INSTANCE; + } + + FilterBuilder bldrFilter = new FilterBuilder(listBindVars, namedBindVars, ctx.getCoherenceQueryLanguage()); + + bldrFilter.setAlias(sAlias); + + return bldrFilter.makeFilterForCache(sCacheName, termWhere, listBindVars, namedBindVars); + } + + /** + * Return a String that is the value of the given Term. + * + * @param t the Term that is atomic + * + * @return return the String found in the AtomicTerm + */ + protected static String atomicStringValueOf(Term t) + { + return (t != null && t.isAtom()) + ? ((AtomicTerm) t).getValue() + : null; + } + + /** + * Return the String that represents the cache name from the given AST + * node by looking for the "from" term AST. + * + * @param sn the syntax node + * + * @return return the String found in the AST node + */ + protected static String getCacheName(NodeTerm sn) + { + return atomicStringValueOf(sn.findAttribute("from")); + } + + /** + * Return the String that represents the cache name alias from the given AST + * node by looking for the "alias" term in the AST. + * + * @param sn the syntax node + * + * @return return the String found in the AST node + */ + protected static String getAlias(NodeTerm sn) + { + return atomicStringValueOf(sn.findAttribute("alias")); + } + + /** + * Return the String that represents the filename from the given AST + * node by looking for the "file" term in the AST. + * + * @param sn the syntax node + * + * @return return the String found in the AST node + */ + protected static String getFile(NodeTerm sn) + { + return atomicStringValueOf(sn.findAttribute("file")); + } + + /** + * Return the boolean that indicates whether distinctness in indicated + * in the given AST node. + * + * @param sn the syntax node + * + * @return return the boolean result of testing the node + */ + protected static boolean getIsDistinct(NodeTerm sn) + { + String dist = atomicStringValueOf(sn.findAttribute("isDistinct")); + + return dist != null && dist.equals("true"); + } + + /** + * Return the AST node that represents the where clause from the given AST + * node. + * + * @param sn the syntax node + * + * @return return the AST node found in the parent AST node + */ + protected static NodeTerm getWhere(NodeTerm sn) + { + Term t = sn.findChild("whereClause"); + + if (t == null) + { + return null; + } + + if (t.length() == 1) + { + return (NodeTerm) t.termAt(1); + } + + return null; + } + + /** + * Return the AST node that represents the fields to select from the + * given AST node. + * + * @param sn the syntax node + * + * @return return the AST node found in the parent AST node + */ + protected static NodeTerm getFields(NodeTerm sn) + { + return (NodeTerm) sn.findChild(OPToken.FIELD_LIST); + } + + /** + * Return the AST node that represents the key to insert from the + * given AST node. + * + * @param sn the syntax node + * + * @return return the AST node found in the parent AST node + */ + protected static Term getInsertKey(NodeTerm sn) + { + return sn.findAttribute("key"); + } + + /** + * Return the AST node that represents the value to insert from the + * given AST node. + * + * @param sn the syntax node + * + * @return return the AST node found in the parent AST node + */ + protected static Term getInsertValue(NodeTerm sn) + { + return sn.findAttribute("value"); + } + + /** + * Return the AST node that represents the group by fields from the + * given AST node. + * + * @param sn the syntax node + * + * @return return the AST node found in the parent AST node + */ + protected static NodeTerm getGroupBy(NodeTerm sn) + { + NodeTerm t = (NodeTerm) sn.findChild("groupBy"); + + if (t == null) + { + return null; + } + + if (t.length() == 0) + { + return null; + } + + return t; + } + + /** + * Return the AST node that represents the list of "Set statements" from the + * given AST node. + * + * @param sn the syntax node + * + * @return return the AST node found in the parent AST node + */ + protected static Term getSetList(NodeTerm sn) + { + return sn.findChild("setList"); + } + + /** + * Return the AST node that represents the extractor for an index from the + * given AST node. + * + * @param sn the syntax node + * + * @return return the AST node found in the parent AST node + */ + protected static Term getExtractor(NodeTerm sn) + { + return sn.findChild("extractor"); + } + + /** + * Test to see if the AST for the group-by is equal to the head of + * the list from the select clause AST. + * + * @param fieldList the list of fields in a select list + * @param groupByList the list of fields in a group by clause + * + * @return return the results of matching + */ + protected static boolean headsMatch(NodeTerm fieldList, NodeTerm groupByList) + { + return groupByList.headChildrenTermEqual(fieldList); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/BackupStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/BackupStatementBuilder.java new file mode 100644 index 0000000000000..0a6e62c5e848b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/BackupStatementBuilder.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.util.MapBackupHelper; + +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.NamedCache; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.RandomAccessFile; + +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link BackupStatement}. + * + * @author jk 2013.12.09 + * @since Coherence 12.2.1 + */ +public class BackupStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public BackupStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for backing up cache"); + } + + String sFile = getFile(term); + + if (sFile == null || sFile.isEmpty()) + { + throw new CohQLException("File name needed for backing up cache"); + } + + return new BackupStatement(sCacheName, sFile); + } + + @Override + public String getSyntax() + { + return "BACKUP CACHE 'cache-name' [TO] [FILE] 'filename'"; + } + + @Override + public String getDescription() + { + return "Backup the cache named 'cache-name' to the file named 'filename'.\n" + + "WARNING: This backup command should not be used on active data set, as it\n" + + "makes no provisions that ensure data consistency during the backup. Please see\n" + + "the documentation for more detailed information.\n" + + "Note: As of Coherence 12.2.1 this command is deprecated. Please use Persistence\n" + + "command 'CREATE SNAPSHOT' instead."; + + } + + // ----- inner class: BackupStatement ----------------------------------- + + /** + * Implementation of the CohQL "BACKUP" command. + */ + public static class BackupStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a BackupStatement that backs the specified cache + * up to the specified file. + * + * @param sCache the name of the cache to be backed up + * @param sFile the name of the file to use to back up the cache + */ + public BackupStatement(String sCache, String sFile) + { + f_sCache = sCache; + f_sFile = sFile; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + try (RandomAccessFile file = new RandomAccessFile(new File(f_sFile), "rw")) + { + ConfigurableCacheFactory ccf = ctx.getCacheFactory(); + NamedCache cache = ccf.ensureTypedCache(f_sCache, null, withoutTypeChecking()); + + MapBackupHelper.writeMap(file, cache); + } + catch (IOException e) + { + throw ensureRuntimeException(e, "Error in BACKUP"); + } + + return StatementResult.NULL_RESULT; + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCache, ctx); + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("ExternalizableHelper.writeMap(" + + "new RandomAccessFile(new File(\"%s\"),\"rw\")," + + "CacheFactory.getCache(\"%s\"))", f_sFile, f_sCache); + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the cache to be backed up. + */ + protected final String f_sCache; + + /** + * The file name to write the cache contents to. + */ + protected final String f_sFile; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link BackupStatementBuilder}. + */ + public static final BackupStatementBuilder INSTANCE = new BackupStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/CreateCacheStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/CreateCacheStatementBuilder.java new file mode 100644 index 0000000000000..16eb24e018981 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/CreateCacheStatementBuilder.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link CreateCacheStatement}. + * + * @author jk 2013.12.11 + * @since Coherence 12.2.1 + */ +public class CreateCacheStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public CreateCacheStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for create cache"); + } + + return new CreateCacheStatement(sCacheName); + } + + @Override + public String getSyntax() + { + return "(ENSURE | CREATE) CACHE 'cache-name'"; + } + + @Override + public String getDescription() + { + return "Make sure the NamedCache 'cache-name' exists."; + } + + // ----- inner class: CreateCacheStatement ------------------------------ + + /** + * Implementation of the CohQL "CREATE CACHE" command. + */ + public static class CreateCacheStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a CreateCacheStatement that will create a cache + * with the specified name. + * + * @param sCache the name of the cache to create + */ + public CreateCacheStatement(String sCache) + { + f_sCache = sCache; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.getCacheFactory().ensureTypedCache(f_sCache, null, withoutTypeChecking()); + + return StatementResult.NULL_RESULT; + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("CacheFactory.getCache(\"%s\"))", f_sCache); + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the cache to be created by this command. + */ + protected final String f_sCache; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of CreateCacheStatementBuilder. + */ + public static final CreateCacheStatementBuilder INSTANCE = new CreateCacheStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/CreateIndexStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/CreateIndexStatementBuilder.java new file mode 100644 index 0000000000000..fee04d1476e91 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/CreateIndexStatementBuilder.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dslquery.internal.SelectListMaker; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.ValueExtractor; + +import java.io.PrintWriter; +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link CreateIndexStatement}. + * + * @author jk 2013.12.11 + * @since Coherence 12.2.1 + */ +public class CreateIndexStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public CreateIndexStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for create index"); + } + + Term termExtractor = getExtractor(term); + + if (termExtractor == null || termExtractor.length() == 0) + { + throw new CohQLException("ValueExtractor(s) needed for create index"); + } + + SelectListMaker transformer = new SelectListMaker(listBindVars, namedBindVars, + ctx.getCoherenceQueryLanguage()); + + transformer.makeSelectsForCache(sCacheName, (NodeTerm) termExtractor); + + ValueExtractor extractor = transformer.getResultsAsValueExtractor(); + + if (extractor == null) + { + throw new CohQLException("ValueExtractor(s) needed for create index"); + } + + return new CreateIndexStatement(sCacheName, extractor); + } + + @Override + public String getSyntax() + { + return "(ENSURE | CREATE) INDEX [ON] 'cache-name' value-extractor-list"; + } + + @Override + public String getDescription() + { + return "Make sure the Index on 'cache-name' that is made from the value-extractor-list\nexists."; + } + + // ----- inner class: CreateIndexStatement ------------------------------ + + /** + * Implementation of the CohQL "CREATE INDEX" command. + */ + public static class CreateIndexStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a CreateIndexStatement that will create an index on the + * specified cache using the specified {@link ValueExtractor}. + * + * @param sCache the name of the cache to create the index on + * @param extractor the ValueExtractor to use to create the index + */ + public CreateIndexStatement(String sCache, ValueExtractor extractor) + { + f_sCache = sCache; + f_extractor = extractor; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.getCacheFactory().ensureTypedCache(f_sCache, null, withoutTypeChecking()) + .addIndex(f_extractor, true, null); + + return StatementResult.NULL_RESULT; + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("CacheFactory.getCache(\"%s\")).addIndex(%s, true, null)", + f_sCache, f_extractor); + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCache, ctx); + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the cache the index will be added to. + */ + protected final String f_sCache; + + /** + * The {@link ValueExtractor} to be used to create the index. + */ + protected final ValueExtractor f_extractor; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of a CreateIndexStatementBuilder. + */ + public static final CreateIndexStatementBuilder INSTANCE = new CreateIndexStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DefaultStatementResult.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DefaultStatementResult.java new file mode 100644 index 0000000000000..76c90b0d0ba80 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DefaultStatementResult.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.util.LiteSet; + +import java.io.PrintWriter; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A default implementation of a {@link StatementResult}. + * + * @author jk 2014.07.15 + * @since Coherence 12.2.1 + */ +public class DefaultStatementResult + implements StatementResult + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a DefaultStatementResult with the specified result + * value. + * + * @param oResult the result Object that this DefaultStatementResult contains + */ + public DefaultStatementResult(Object oResult) + { + this(oResult, true); + } + + /** + * Construct a DefaultStatementResult with the specified result + * value. + * + * @param oResult the result Object that this DefaultStatementResult contains + * @param fShowKeys if true and oResult is a {@link Map} then the keys of the Map + * will be printed by the {@link #print(java.io.PrintWriter, String)} method, + * if false, no keys will be printed + */ + public DefaultStatementResult(Object oResult, boolean fShowKeys) + { + f_oResult = oResult; + f_fShowKeys = fShowKeys; + } + + // ----- StatementResult interface -------------------------------------- + + @Override + public Object getResult() + { + return f_oResult; + } + + @Override + public void print(PrintWriter writer, String sTitle) + { + printResults(writer, sTitle, f_oResult, f_fShowKeys); + writer.flush(); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Print the specified result value to the specified {@link PrintWriter}. + * + * @param writer the PrintWriter to print the results to + * @param sTitle the optional title to print before the results + * @param oResult the result object to print + * @param fShowKeys a flag to determine whether to print keys if the result object is a map + */ + protected void printResults(PrintWriter writer, String sTitle, Object oResult, boolean fShowKeys) + { + if (oResult == null) + { + return; + } + + if (sTitle != null) + { + writer.println(sTitle); + } + + if (oResult instanceof Map) + { + printResultsMap(writer, (Map) oResult, fShowKeys); + } + else if (oResult instanceof LiteSet) + { + printResultsCollection(writer, (Collection) oResult, fShowKeys); + } + else if (oResult instanceof Collection) + { + printResultsCollection(writer, (Collection) oResult, false); + } + else + { + printStringOrObject(writer, oResult); + writer.println(); + } + } + + /** + * Print out the given Object on the given {@link PrintWriter}. + * + * @param writer a PrintWriter to print on + * @param oResult the object to print + * @param fPrintNewLine a flag controlling whether to print a new line + * @param fTopObject a flag to tell whether the object is outermost + */ + protected void printObject(PrintWriter writer, Object oResult, boolean fPrintNewLine, boolean fTopObject) + { + writer.flush(); + + if (oResult instanceof Object[]) + { + Object[] aoTuple = (Object[]) oResult; + + writer.print("["); + + boolean first = true; + + for (Object t : aoTuple) + { + if (!first) + { + writer.print(", "); + } + + first = false; + printStringOrObject(writer, t); + } + + writer.print("]"); + } + else if (oResult instanceof List) + { + if (!fTopObject) + { + writer.print("["); + } + + printCommaSeparatedCollection(writer, (Collection) oResult); + + if (!fTopObject) + { + writer.print("]"); + } + } + else if (oResult instanceof Map) + { + boolean first = true; + + writer.print("{"); + + for (Map.Entry me : (Set) ((Map) oResult).entrySet()) + { + if (!first) + { + writer.print(", "); + } + + first = false; + printStringOrObject(writer, me.getKey(), false); + writer.print(": "); + printStringOrObject(writer, me.getValue()); + } + + writer.print("}"); + } + else if (oResult instanceof Set) + { + if (!fTopObject) + { + writer.print("{"); + } + + printCommaSeparatedCollection(writer, (Collection) oResult); + + if (!fTopObject) + { + writer.print("}"); + } + } + else + { + writer.print(oResult); + } + + if (fPrintNewLine) + { + writer.println(); + } + } + + /** + * Print the contents of the given {@link Collection} to the specified + * {@link PrintWriter} as a comma separated list. + * + * @param writer the PrintWriter to print the Collection to + * @param col the Collection to print + */ + protected void printCommaSeparatedCollection(PrintWriter writer, Collection col) + { + boolean first = true; + + for (Object value : col) + { + if (!first) + { + writer.print(", "); + } + + first = false; + printStringOrObject(writer, value); + } + } + + /** + * If the given Object is a String print it within double quotes around otherwise + * pass the Object to the {@link #printObject(PrintWriter, Object, boolean, boolean)} method. + * + * @param writer a PrintWriter to print on + * @param o the object to print + */ + protected void printStringOrObject(PrintWriter writer, Object o) + { + printStringOrObject(writer, o, false); + } + + /** + * If the given Object is a String print it within double quotes around otherwise + * pass the Object to the {@link #printObject(PrintWriter, Object, boolean, boolean)} method. + * + * @param writer a PrintWriter to print on + * @param o the object to print + * @param fNewLine a flag controlling whether to print a new line + */ + protected void printStringOrObject(PrintWriter writer, Object o, boolean fNewLine) + { + if (o instanceof String) + { + writer.print("\"" + o + "\""); + + if (fNewLine) + { + writer.println(); + } + } + else + { + printObject(writer, o, fNewLine, false); + } + } + + /** + * Print the given {@link Collection} of Objects on the given {@link PrintWriter}. + * + * @param writer a PrintWriter to print on + * @param col the Collection to print + * @param fShowKeys true to show keys + */ + protected void printResultsCollection(PrintWriter writer, Collection col, boolean fShowKeys) + { + if (col == null) + { + return; + } + + for (Object o : col) + { + if (!fShowKeys && o instanceof Map.Entry) + { + printStringOrObject(writer, ((Map.Entry) o).getValue(), true); + } + else + { + printStringOrObject(writer, o, true); + } + } + } + + /** + * Print the contents of the specified {@link Map} to the specified + * {@link PrintWriter}. + * + * @param writer a PrintWriter to print on + * @param map the Map to print + * @param fShowKey a flag controlling whether to print the Maps keys + */ + protected void printResultsMap(PrintWriter writer, Map map, boolean fShowKey) + { + for (Map.Entry entry : (Set) map.entrySet()) + { + if (fShowKey) + { + printStringOrObject(writer, entry.getKey(), false); + writer.print(": "); + } + + printStringOrObject(writer, entry.getValue(), true); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The actual result of executing a CohQL {@link com.tangosol.coherence.dslquery.Statement}. + */ + protected final Object f_oResult; + + /** + * A flag to determine whether to print keys in the {@link #print(java.io.PrintWriter, String)} + * method if the value in {@link #f_oResult} is a {@link Map}. + */ + protected final boolean f_fShowKeys; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DeleteStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DeleteStatementBuilder.java new file mode 100644 index 0000000000000..1ea0222fd749d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DeleteStatementBuilder.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.Filter; + +import com.tangosol.util.filter.AlwaysFilter; + +import com.tangosol.util.processor.ConditionalRemove; + +import java.io.PrintWriter; + +import java.util.List; +import java.util.Map; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link DeleteStatement}. + * + * @author jk 2013.12.11 + * @since Coherence 12.2.1 + */ +public class DeleteStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public DeleteStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for delete query"); + } + + Filter filter = ensureFilter(getWhere(term), sCacheName, getAlias(term), + listBindVars, namedBindVars, ctx); + + return new DeleteStatement(sCacheName, filter); + } + + @Override + public String getSyntax() + { + return "DELETE FROM 'cache-name'[[AS] alias] [WHERE conditional-expression]"; + } + + @Override + public String getDescription() + { + return "Delete the entries from the cache 'cache-name' that match the conditional\n" + + "expression. If no conditional-expression is given all entries will be deleted!\n" + + "Use with Care!"; + } + + // ----- inner class: DeleteStatement ----------------------------------- + + /** + * Implementation of the CohQL "DELETE" query. + */ + public static class DeleteStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create an instance of {@link DeleteStatement} that will delete + * all entries from the specified cache that match the given + * {@link Filter}. + * + * @param sCacheName the name of the cache to remove entries from + * @param filter the Filter to use to determine the entries to + * be removed + */ + public DeleteStatement(String sCacheName, Filter filter) + { + f_sCache = sCacheName; + f_filter = filter; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + Map map = ctx.getCacheFactory().ensureTypedCache(f_sCache, null, withoutTypeChecking()) + .invokeAll(f_filter, new ConditionalRemove<>(AlwaysFilter.INSTANCE())); + + return new DefaultStatementResult(map.entrySet()); + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCache, ctx); + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("CacheFactory.getCache(\"%s\")." + + "invokeAll(%s, new ConditionalRemove(AlwaysFilter.INSTANCE))", + f_sCache, f_filter); + } + + // ----- data members --------------------------------------------------- + + /** + * The cache name containing the entries to be deleted + */ + protected final String f_sCache; + + /** + * The {@link Filter} to be used in the CohQL "delete" command. + */ + protected final Filter f_filter; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of DeleteStatementBuilder. + */ + public static final DeleteStatementBuilder INSTANCE = new DeleteStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DropCacheStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DropCacheStatementBuilder.java new file mode 100644 index 0000000000000..afc53c09fae45 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DropCacheStatementBuilder.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link DropCacheStatement}. + * + * @author jk 2013.12.11 + * @since Coherence 12.2.1 + */ +public class DropCacheStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public DropCacheStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for drop cache"); + } + + return new DropCacheStatement(sCacheName); + } + + @Override + public String getSyntax() + { + return "DROP CACHE 'cache-name'"; + } + + @Override + public String getDescription() + { + return "Remove the cache 'cache-name' from the cluster."; + } + + // ----- inner class: DropCacheStatement -------------------------------- + + /** + * Implementation of the CohQL "DROP CACHE" command. + */ + public static class DropCacheStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a DropCacheStatement. + * + * @param sCacheName the name of the cache to destroy + */ + public DropCacheStatement(String sCacheName) + { + f_sCacheName = sCacheName; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.getCacheFactory().ensureTypedCache(f_sCacheName, null, withoutTypeChecking()).destroy(); + + return StatementResult.NULL_RESULT; + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCacheName, ctx); + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("CacheFactory.getCache(\"%s\")).destroy()", f_sCacheName); + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the cache to be destroyed. + */ + protected final String f_sCacheName; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of DropCacheStatementBuilder. + */ + public static final DropCacheStatementBuilder INSTANCE = new DropCacheStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DropIndexStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DropIndexStatementBuilder.java new file mode 100644 index 0000000000000..e22bbf3b02034 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/DropIndexStatementBuilder.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dslquery.internal.SelectListMaker; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.ValueExtractor; + +import java.io.PrintWriter; + +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link DropIndexStatement}. + * + * @author jk 2013.12.11 + * @since Coherence 12.2.1 + */ +public class DropIndexStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public DropIndexStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for drop index"); + } + + Term termExtractor = getExtractor(term); + + if (termExtractor == null || termExtractor.length() == 0) + { + throw new CohQLException("ValueExtractor(s) needed for drop index"); + } + + SelectListMaker transformer = new SelectListMaker(listBindVars, namedBindVars, + ctx.getCoherenceQueryLanguage()); + + transformer.makeSelectsForCache(sCacheName, (NodeTerm) termExtractor); + + ValueExtractor extractor = transformer.getResultsAsValueExtractor(); + + if (extractor == null) + { + throw new CohQLException("ValueExtractor(s) needed for drop index"); + } + + return new DropIndexStatement(sCacheName, extractor); + } + + @Override + public String getSyntax() + { + return "DROP INDEX [ON] 'cache-name' value-extractor-list"; + } + + @Override + public String getDescription() + { + return "Remove the index made from value-extractor-list from cache 'cache-name'."; + } + + // ----- inner class: BackupStatement ----------------------------------- + + /** + * Implementation of the CohQL "create index" command. + */ + public static class DropIndexStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a DropIndexStatement that will drop the index + * created with the specified {@link com.tangosol.util.ValueExtractor} from the + * cache with the specified name. + * + * @param sCacheName the name of the cache to drop the index on + * @param extractor the ValueExtractor to use to identify the index to drop + */ + public DropIndexStatement(String sCacheName, ValueExtractor extractor) + { + f_sCacheName = sCacheName; + f_extractor = extractor; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.getCacheFactory().ensureTypedCache(f_sCacheName, null, withoutTypeChecking()) + .removeIndex(f_extractor); + + return StatementResult.NULL_RESULT; + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("CacheFactory.getCache(\"%s\")).removeIndex(%s)", + f_sCacheName, f_extractor); + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCacheName, ctx); + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the cache the index will be added to. + */ + protected final String f_sCacheName; + + /** + * The {@link ValueExtractor} to be used to create the index. + */ + protected final ValueExtractor f_extractor; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of DropIndexStatementBuilder. + */ + public static final DropIndexStatementBuilder INSTANCE = new DropIndexStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/FormattedMapStatementResult.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/FormattedMapStatementResult.java new file mode 100644 index 0000000000000..bcc42ed88928d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/FormattedMapStatementResult.java @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import java.io.PrintWriter; + +import java.util.Map; +import java.util.Set; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementResult} which assumes the result is + * a {@link Map} with value being a {@link Object} or {@link Object}[]. + * The caller may call setColumnHeaders to set the column header values and the + * formatting of the results will be based upon the largest value in each column. + * + * @author tam 2014.08.05 + * @since 12.2.1 + */ +public class FormattedMapStatementResult + extends DefaultStatementResult + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an instance with the given result which will should be a + * {@link Map}. If the result is not a {@link Map} then it is just output + * using super class. + * + * @param oResult the result + */ + public FormattedMapStatementResult(Object oResult) + { + super(oResult, true); + } + + // ----- StatementResult interface -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void print(PrintWriter writer, String sTitle) + { + if (f_oResult instanceof Map) + { + if (sTitle != null) + { + writer.println(sTitle); + } + + printResultsObjectMap(writer, (Map) f_oResult, f_fShowKeys); + } + else + { + printResults(writer, sTitle, f_oResult, f_fShowKeys); + } + + writer.flush(); + } + + /** + * Print the contents of the specified {@link Map} to the specified + * {@link PrintWriter} and format as required. The {@link Map} is assumed to + * have values of either {@link Object} or {@link Object}[]. + * + * @param writer a PrintWriter to print on + * @param map the Map to print + * @param fShowKey a flag controlling whether to print the Maps keys + */ + protected void printResultsObjectMap(PrintWriter writer, Map map, boolean fShowKey) + { + int nKeyMax = -1; + int anValueMax[] = null; + + // determine the max length of the key and values + for (Map.Entry entry : (Set) map.entrySet()) + { + Object oKey = entry.getKey(); + Object oValue = entry.getValue(); + + if (oKey.toString().length() > nKeyMax) + { + nKeyMax = oKey.toString().length(); + + // we have not yet created the array of max values for each of the values + // then do this now + if (anValueMax == null) + { + int nValueLen = ensureObjectArray(oValue).length; + + if (nValueLen == 0) + { + throw new IllegalArgumentException("Cannot use " + this.getClass() + " for Map with no value"); + } + + anValueMax = new int[nValueLen]; + + for (int i = 0; i < nValueLen; i++) + { + anValueMax[i] = 0; + } + } + } + + Object[] oValueResult = ensureObjectArray(oValue); + + // get max length of results + int i = 0; + + for (Object o : oValueResult) + { + int nLen = o.toString().length(); + + if (nLen > anValueMax[i]) + { + anValueMax[i] = nLen; + } + + i++; + } + } + + // always show keys + if (m_asColumnHeaders != null && anValueMax != null) + { + if (m_asColumnHeaders.length != anValueMax.length + 1) + { + throw new IllegalArgumentException("The number of column headers is " + m_asColumnHeaders.length + + " which does not match the number of results + key value which is " + (anValueMax.length + 1)); + } + + if (m_asColumnHeaders[0].length() > nKeyMax) + { + nKeyMax = m_asColumnHeaders[0].length(); + } + + // output the key column header + writer.print(rightPad(m_asColumnHeaders[0].toString(), nKeyMax) + " "); + + // output the value column headers + for (int i = 1; i < anValueMax.length + 1; i++) + { + // check to see if the column label is > max + if (m_asColumnHeaders[i].length() > anValueMax[i - 1]) + { + anValueMax[i - 1] = m_asColumnHeaders[i].length(); + } + + writer.print(rightPad(m_asColumnHeaders[i].toString(), anValueMax[i - 1]) + " "); + } + } + + if (map.size() > 0) + { + writer.println(); + writer.println(underline('-', nKeyMax, anValueMax)); + + for (Map.Entry entry : (Set) map.entrySet()) + { + Object oValue = entry.getValue(); + + if (fShowKey) + { + writer.print(rightPad(entry.getKey().toString(), nKeyMax) + " "); + } + + Object[] oValueResult = ensureObjectArray(oValue); + int i = 0; + + for (Object o : oValueResult) + { + writer.print(rightPad(o.toString(), anValueMax[i++]) + " "); + } + + writer.println(); + } + } + } + + // ----- FormattedMapStatementResult methods ------------------------------- + + /** + * Set the column headers to print. + * + * @param asColumnHeaders the column headers to print + */ + public void setColumnHeaders(String[] asColumnHeaders) + { + m_asColumnHeaders = asColumnHeaders; + } + + // ----- helpers -------------------------------------------------------- + + /** + * Ensure that a value is an Object array. If the passed value is + * an Object then convert to an Object[1], otherwise return the Object[]. + * + * @param oValue the value to convert to Object[] + * + * @return the converted Object[] + */ + private Object[] ensureObjectArray(Object oValue) + { + return oValue instanceof Object[] ? (Object[]) oValue : new Object[] {(Object) oValue}; + } + + /** + * Create an underline String + * + * @param cChar the character to use for underline + * @param nKeyLen the length of the key + * @param anWidths the array of value widths + * + * @return a String that can be used for underline + */ + private String underline(char cChar, int nKeyLen, int... anWidths) + { + StringBuilder sb = new StringBuilder(); + + sb.append(fillChar(cChar, nKeyLen) + " "); + + for (int i = 0; i < anWidths.length; i++) + { + sb.append(fillChar(cChar, anWidths[i]) + " "); + } + + return sb.toString(); + } + + /** + * Return a {@link String} of characters a certain length + * + * @param c the character to repeat + * @param nLen the length of the new string + * + * @return the completed string + */ + private String fillChar(char c, int nLen) + { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < nLen; i++) + { + sb.append(c); + } + + return sb.toString(); + } + + /** + * Right pad a string up to the max length. + * + * @param sValue the value to right pad + * @param nLen the length to right pad to + * + * @return the formatted string + */ + private String rightPad(String sValue, int nLen) + { + return String.format("%1$-" + nLen + "s", sValue); + } + + // ----- data members --------------------------------------------------- + + /** + * The column headers. + */ + private String[] m_asColumnHeaders = null; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/InsertStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/InsertStatementBuilder.java new file mode 100644 index 0000000000000..8a0e046cfbbf9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/InsertStatementBuilder.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dslquery.internal.UpdateSetListMaker; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.ClassHelper; + +import java.io.PrintWriter; + +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link InsertStatement}. + * + * @author jk 2013.12.17 + * @since Coherence 12.2.1 + */ +public class InsertStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public InsertStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + Term termKey = getInsertKey(term); + Term termValue = getInsertValue(term); + UpdateSetListMaker transformer = createUpdateSetListMaker(ctx, listBindVars, namedBindVars); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for insert command"); + } + + Object oValue = createInsertValue(termValue, transformer); + Object oKey = createInsertKey(termKey, transformer, oValue); + + return new InsertStatement(sCacheName, oKey, oValue); + } + + @Override + public String getSyntax() + { + return "INSERT INTO 'cache-name' [KEY (literal | new java-constructor | static method)]\n" + + " VALUE (literal | new java-constructor | static method)"; + } + + @Override + public String getDescription() + { + return "Insert into the cache named 'cache-name a new key value pair. If the KEY part\n" + + "is omitted then getKey() will be sent to the VALUE object."; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Create the key to use in the insert statement. + * + * @param termKey the AST representing the Key term + * @param transformer the {@link UpdateSetListMaker} that will create the key instance + * @param oValue the value being inserted that will be used to call its getKey method if no + * key term is present + * + * @return the value to use as the key for the insert + * + * @throws CohQLException if there are any errors creating the key instance + */ + protected Object createInsertKey(Term termKey, UpdateSetListMaker transformer, Object oValue) + { + Object oKey; + + if (termKey == null) + { + if (oValue == null) + { + throw new RuntimeException("No key specified for insert"); + } + + try + { + oKey = ClassHelper.invoke(oValue, "getKey", new Object[] + { + }); + } + catch (NoSuchMethodException e) + { + throw new RuntimeException("No key specified and missing or inaccessible method: " + + oValue.getClass().getName() + ".getKey()"); + } + catch (Exception e) + { + throw new CohQLException("Error creating key for insert", e); + } + } + else + { + try + { + oKey = transformer.makeObjectForKey((NodeTerm) termKey, oValue); + } + catch (Exception e) + { + throw new CohQLException("Error creating key (from value) for insert", e); + } + } + + return oKey; + } + + /** + * Create the instance of the value that will be inserted into the cache. + * + * @param termValue the AST term to use to create the value + * @param transformer the {@link UpdateSetListMaker} that can create values from AST terms + * + * @return an instance of a value to insert into the cache + * + * @throws CohQLException if any errors occur creating the value + */ + protected Object createInsertValue(Term termValue, UpdateSetListMaker transformer) + { + try + { + if (termValue == null) + { + termValue = Terms.newTerm("literal", AtomicTerm.createNull()); + } + + return transformer.makeObject((NodeTerm) termValue); + } + catch (Exception e) + { + throw new CohQLException("Error creating value object", e); + } + } + + /** + * Create an {@link UpdateSetListMaker}. + * + * @param ctx the {@link ExecutionContext} to use + * @param listBindVars the indexed bind variables to pass to the UpdateSetListMaker + * @param namedBindVars the named bind variables to pass to the UpdateSetListMaker + * + * @return an UpdateSetListMaker + */ + protected UpdateSetListMaker createUpdateSetListMaker(ExecutionContext ctx, List listBindVars, + ParameterResolver namedBindVars) + { + UpdateSetListMaker transformer = new UpdateSetListMaker(listBindVars, namedBindVars, + ctx.getCoherenceQueryLanguage()); + + transformer.setExtendedLanguage(ctx.isExtendedLanguageEnabled()); + + return transformer; + } + + // ----- inner class: InsertStatement ----------------------------------- + + /** + * Implementation of the CohQL "INSERT" command. + */ + public static class InsertStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a InsertStatement that will insert the specified + * key and value into the specified cache. + * + * @param sCacheName then name of the cache to insert the key and value into + * @param oKey the key of the entry to insert into the cache + * @param oValue the value to insert into the cache + */ + public InsertStatement(String sCacheName, Object oKey, Object oValue) + { + f_sCacheName = sCacheName; + f_oKey = oKey; + f_oValue = oValue; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + Object oResult = ctx.getCacheFactory().ensureTypedCache(f_sCacheName, null, withoutTypeChecking()) + .put(f_oKey, f_oValue); + + return new DefaultStatementResult(oResult); + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("CacheFactory.getCache(\"%s\").put(%s, %s)", + f_sCacheName, f_oKey, f_oValue); + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCacheName, ctx); + } + + // ----- data members ----------------------------------------------- + + /** + * The cache name to be used in the CohQL "insert" command. + */ + protected final String f_sCacheName; + + /** + * The key to use to put the value into the cache. + */ + protected final Object f_oKey; + + /** + * The value being inserted into the cache. + */ + protected final Object f_oValue; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of a InsertStatementBuilder. + */ + public static final InsertStatementBuilder INSTANCE = new InsertStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/QueryRecorderStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/QueryRecorderStatementBuilder.java new file mode 100644 index 0000000000000..93a12197924a7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/QueryRecorderStatementBuilder.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.Filter; + +import com.tangosol.util.aggregator.QueryRecorder; + +import java.io.PrintWriter; + +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link QueryRecorderStatement}. + * + * @author jk 2013.12.17 + * @since Coherence 12.2.1 + */ +public class QueryRecorderStatementBuilder + extends AbstractStatementBuilder + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a QueryRecorderStatementBuilder of the specified type. + * + * @param recordType the type of query recorder to build + */ + protected QueryRecorderStatementBuilder(QueryRecorder.RecordType recordType) + { + m_recordType = recordType; + } + + // ----- StatementBuilder interface ------------------------------------- + + @Override + public QueryRecorderStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + NodeTerm termStmt = (NodeTerm) term.termAt(1); + String sCacheName = getCacheName(termStmt); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for query plan"); + } + + String sAlias = getAlias(termStmt); + NodeTerm termWhere = getWhere(termStmt); + Filter filter = ensureFilter(termWhere, sCacheName, sAlias, listBindVars, namedBindVars, ctx); + + return new QueryRecorderStatement(sCacheName, filter, m_recordType); + } + + @Override + public String getSyntax() + { + if (m_recordType == QueryRecorder.RecordType.EXPLAIN) + { + return "SHOW PLAN 'CohQL command' | EXPLAIN PLAN for 'CohQL command'"; + } + + return "TRACE 'CohQL command'"; + } + + @Override + public String getDescription() + { + if (m_recordType == QueryRecorder.RecordType.EXPLAIN) + { + return "Shows what the CohQL command would do rather than executing it."; + } + + return "Shows what the CohQL command would do rather than executing it."; + } + + // ----- inner class: QueryRecorderStatement --------------------------- + + /** + * Implementation of the CohQL "QueryRecorder" command. + */ + public static class QueryRecorderStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a QueryRecorderStatement that produces a plan or trace + * of the specified filter query against the specified cache. + * + * @param sCacheName the cache to be queried + * @param filter the {@link Filter} to show the plan or trace for + * @param type the type of query recorder - explain plan or trace + */ + public QueryRecorderStatement(String sCacheName, Filter filter, + QueryRecorder.RecordType type) + { + f_sCacheName = sCacheName; + f_filter = filter; + f_aggregator = new QueryRecorder<>(type); + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + Object oResult = ctx.getCacheFactory().ensureTypedCache(f_sCacheName, null, withoutTypeChecking()) + .aggregate(f_filter, f_aggregator); + + return new DefaultStatementResult(oResult); + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("CacheFactory.getCache(\"%s\").aggregate(%s, %s)", + f_sCacheName, f_filter, f_aggregator); + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCacheName, ctx); + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the cache to query. + */ + protected final String f_sCacheName; + + /** + * The {@link Filter} to be explained or traced. + */ + protected final Filter f_filter; + + /** + * The type of query recorder to run. + */ + protected final QueryRecorder f_aggregator; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of a QueryRecorderStatementBuilder that builds EXPLAIN PLAN queries. + */ + public static final QueryRecorderStatementBuilder EXPLAIN_INSTANCE = + new QueryRecorderStatementBuilder(QueryRecorder.RecordType.EXPLAIN); + + /** + * An instance of a QueryRecorderStatementBuilder that builds TRACE queries. + */ + public static final QueryRecorderStatementBuilder TRACE_INSTANCE = + new QueryRecorderStatementBuilder(QueryRecorder.RecordType.TRACE); + + // ----- data members --------------------------------------------------- + + /** + * The type of query recorder that this builder builds. + */ + protected QueryRecorder.RecordType m_recordType; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/RestoreStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/RestoreStatementBuilder.java new file mode 100644 index 0000000000000..ce33b9f7dce6b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/RestoreStatementBuilder.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.internal.util.MapBackupHelper; + +import com.tangosol.net.NamedCache; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.RandomAccessFile; + +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link RestoreStatement}. + * + * @author jk 2013.12.09 + * @since Coherence 12.2.1 + */ +public class RestoreStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public RestoreStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed to restore cache"); + } + + String sFile = getFile(term); + + if (sFile == null || sFile.isEmpty()) + { + throw new CohQLException("File name needed to restore cache"); + } + + return new RestoreStatement(sCacheName, sFile); + } + + @Override + public String getSyntax() + { + return "RESTORE CACHE 'cache-name' [FROM] [FILE] 'filename'"; + } + + @Override + public String getDescription() + { + return "Restore the cache named 'cache-name' from the file named 'filename'.\n" + + "WARNING: This restore command should not be used on active data set, as it makes\n" + + "no provisions that ensure data consistency during the restore. Please see the\n" + + "documentation for more detailed information.\n" + + "Note: As of Coherence 12.2.1 this command is deprecated. Please use Persistence\n" + + "command 'RECOVER SNAPSHOT' instead."; + } + + // ----- inner class: RestoreStatement ---------------------------------- + + /** + * Implementation of the CohQL "RESTORE" command. + */ + public static class RestoreStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a RestoreStatement that restores a cache from a + * given file. + * + * @param sCacheName the name of the cache to restore + * @param sFile the file to restore the cache from + */ + public RestoreStatement(String sCacheName, String sFile) + { + f_sCacheName = sCacheName; + f_sFile = sFile; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + try (RandomAccessFile file = new RandomAccessFile(new File(f_sFile), "rw")) + { + NamedCache cache = ctx.getCacheFactory() + .ensureTypedCache(f_sCacheName, null, withoutTypeChecking()); + + MapBackupHelper.readMap(file, cache, 0, null); + } + catch (IOException e) + { + throw ensureRuntimeException(e, "Error in RESTORE"); + } + + return StatementResult.NULL_RESULT; + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("ExternalizableHelper.readMap(" + + "new RandomAccessFile(new File(\"%s\"),\"rw\")," + + "CacheFactory.getCache(\"%s\"), null)", + f_sFile, f_sCacheName); + } + + // ----- data members ----------------------------------------------- + + /** + * The cache name to be used in the CohQL "backup" command. + */ + protected final String f_sCacheName; + + /** + * The file name to be used in the CohQL "backup" command. + */ + protected final String f_sFile; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of a RestoreStatementBuilder. + */ + public static final RestoreStatementBuilder INSTANCE = new RestoreStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/SelectStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/SelectStatementBuilder.java new file mode 100644 index 0000000000000..282d375da6cd3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/SelectStatementBuilder.java @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.CoherenceQueryLanguage; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dslquery.internal.SelectListMaker; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.NamedCache; + +import com.tangosol.util.Filter; +import com.tangosol.util.InvocableMap; + +import java.io.PrintWriter; + +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link SelectStatement}. + * + * @author jk 2013.12.17 + * @since Coherence 12.2.1 + */ +public class SelectStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public SelectStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + SelectListMaker transformer = createSelectListMaker(listBindVars, namedBindVars, + ctx.getCoherenceQueryLanguage()); + + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for select query"); + } + + boolean isDistinct = getIsDistinct(term); + String alias = getAlias(term); + NodeTerm fields = getFields(term); + NodeTerm whereTerm = getWhere(term); + NodeTerm groupBy = getGroupBy(term); + + if (groupBy != null) + { + if (fields == null) + { + throw new CohQLException("must have fields for group by to make sense"); + } + else if (!headsMatch(fields, groupBy)) + { + throw new CohQLException("group by fields must match head of select list"); + } + } + + InvocableMap.EntryAggregator aggregator = createAggregator(sCacheName, fields, alias, isDistinct, transformer); + Filter filter = ensureFilter(whereTerm, sCacheName, alias, listBindVars, + namedBindVars, ctx); + boolean fReduction = !transformer.hasCalls() &&!isDistinct && aggregator != null; + + return new SelectStatement(sCacheName, filter, aggregator, fReduction); + } + + @Override + public String getSyntax() + { + return "SELECT (properties* aggregators* | * | alias) FROM 'cache-name' [[AS] alias]\n" + + " [WHERE conditional-expression] [GROUP [BY] properties+]"; + } + + @Override + public String getDescription() + { + return "Select an ordered list of properties from the cache named 'cache-name' filtered\n" + + "by the conditional-expression. If '*' is used then fetch the entire object.\n" + + "If no conditional-expression is given all elements are selected, so this is not\n" + + "suggested for large data sets!\n\n" + + "SELECT aggregators FROM 'cache-name' [[AS] alias] [WHERE conditional-expression]\n\n" + + "Select an ordered list of aggregators from the cache named 'cache-name' selected\n" + + "by the conditional-expression.\n" + + "The aggregators may be MAX, MIN, AVG, SUM, COUNT, LONG_MAX, LONG_MIN, LONG_SUM.\n" + + "If no conditional-expression is given all elements are selected.\n" + "\n" + + "SELECT (properties then aggregators) FROM 'cache-name' [[AS] alias]\n" + + " [WHERE conditional-expression ] GROUP [BY] properties\n\n" + + "Select an ordered list of properties aggregators from the cache named\n" + + "'cache-name' selected by the conditional-expression and grouped by the\n" + + "set of properties that precedes the aggregators. For example:\n" + + " SELECT supplier, SUM(amount), AVG(price) FROM 'orders' GROUP BY supplier\n" + + "As usual, if no conditional-expression is given all elements are selected."; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Create the {@link InvocableMap.EntryAggregator} that will aggregate the results of this + * select query. + * + * @param cacheName the cache being queried + * @param fields the fields being selected + * @param alias the alias of the cache name + * @param fDistinct a flag indicating whether this is a distinct query + * @param transformer the transformer to use to transform the field list to extractors + * + * @return an InvocableMap.EntryAggregator to use to aggregate the query results. + */ + protected InvocableMap.EntryAggregator createAggregator(String cacheName, NodeTerm fields, String alias, + boolean fDistinct, SelectListMaker transformer) + { + InvocableMap.EntryAggregator aggregator = null; + + if (!isSelectStarQuery(alias, fields)) + { + transformer.setAlias(alias); + + transformer.makeSelectsForCache(cacheName, fields); + + if (!transformer.hasCalls()) + { + if (fDistinct) + { + aggregator = transformer.getDistinctValues(); + } + else + { + aggregator = transformer.getResultsAsReduction(); + } + } + else + { + aggregator = transformer.getResultsAsEntryAggregator(); + } + } + + return aggregator; + } + + /** + * Create an instance of a {@link SelectListMaker}. + * + * @param listBindVars the indexed bind variables that the SelectListMaker should use + * @param namedBindVars the named bind variables that the SelectListMaker should use + * @param language the CohQL language instance that the SelectListMaker should use + * + * @return a SelectListMaker + */ + protected SelectListMaker createSelectListMaker(List listBindVars, ParameterResolver namedBindVars, + CoherenceQueryLanguage language) + { + return new SelectListMaker(listBindVars, namedBindVars, language); + } + + /** + * Return true if this query is of the form "SELECT * FROM cache-name". + * + * @param sAlias the alias for the cache name + * @param termFields the field list for the query + * + * @return true if this is a "SELECT * FROM cache-name" query + */ + protected boolean isSelectStarQuery(String sAlias, NodeTerm termFields) + { + return termFields.termEqual(Terms.newTerm("fieldList", AtomicTerm.createString("*"))) + || (sAlias != null && termFields.termEqual(Terms.newTerm("fieldList", AtomicTerm.createString(sAlias)))); + } + + // ----- inner class: SelectStatement ----------------------------------- + + /** + * Implementation of the CohQL "SELECT" command. + */ + public static class SelectStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a SelectStatement that will query the specified cache. + * + * @param sCache the cache to query + * @param filter the {@link Filter} to use to query tha cache + * @param aggregator the {@link InvocableMap.EntryAggregator} to run against the cache entries + * @param fReduction a flag indicating whether this query is a sub-set of entry fields + */ + public SelectStatement(String sCache, Filter filter, + InvocableMap.EntryAggregator aggregator, boolean fReduction) + { + f_sCache = sCache; + f_filter = filter; + f_aggregator = aggregator; + f_fReduction = fReduction; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + NamedCache cache = ctx.getCacheFactory().ensureTypedCache(f_sCache, null, withoutTypeChecking()); + Object oResult; + + if (f_aggregator == null) + { + oResult = cache.entrySet(f_filter); + } + else + { + oResult = cache.aggregate(f_filter, f_aggregator); + } + + return new DefaultStatementResult(oResult, !f_fReduction); + } + + @Override + public void showPlan(PrintWriter out) + { + if (f_aggregator == null) + { + out.printf("CacheFactory.getCache(\"%s\").entrySet(%s)", + f_sCache, f_filter); + } + else + { + out.printf("CacheFactory.getCache(\"%s\").aggregate(%s, %s)", + f_sCache, f_filter, f_aggregator); + } + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCache, ctx); + } + + // ----- accessor methods ------------------------------------------- + + /** + * Return the {@link Filter} to use to execute this query. + * + * @return the {@link Filter} to use to execute this query + */ + public Filter getFilter() + { + return f_filter; + } + + /** + * Return the {@link InvocableMap.EntryAggregator} to use to + * execute this query. + * + * @return the InvocableMap.EntryAggregator to use to execute + * this query + */ + public InvocableMap.EntryAggregator getAggregator() + { + return f_aggregator; + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the cache to query. + */ + protected final String f_sCache; + + /** + * The {@link Filter} to use in the query. + */ + protected final Filter f_filter; + + /** + * The {@link InvocableMap.EntryAggregator} to use in the query. + */ + protected final InvocableMap.EntryAggregator f_aggregator; + + /** + * Flag to denote whether this query is an aggregation to select specific + * fields from the values of a cache; e.g. select x, y, z from foo. + */ + protected final boolean f_fReduction; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of a SelectStatementBuilder. + */ + public static final SelectStatementBuilder INSTANCE = new SelectStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/SourceStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/SourceStatementBuilder.java new file mode 100644 index 0000000000000..96244978a60a3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/SourceStatementBuilder.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.PrintWriter; +import java.io.Reader; + +import java.util.List; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link SourceStatement}. + * + * @author jk 2013.12.09 + * @since Coherence 12.2.1 + */ +public class SourceStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public SourceStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sFile = getFile(term); + + if (sFile == null || sFile.isEmpty()) + { + throw new CohQLException("File name needed for sourcing"); + } + + return new SourceStatement(sFile); + } + + @Override + public String getSyntax() + { + return "SOURCE FROM [FILE] 'filename'\n@ 'filename'\n. filename"; + } + + @Override + public String getDescription() + { + return "Read and process a file of commands form the file named 'file-name. Each\n" + + "statement must end with a ';'. The character '@' may be used as an alias for\n" + + "SOURCE FROM [FILE] as in @ 'filename'. Source files may SOURCE other files.\n" + + "At the command line only you may also use '.' as an abbreviation for '@' but\n" + + "do not put quotes around the filename since '.' is processed specially before\n" + + "the line executed."; + } + + // ----- inner class: SourceStatement ----------------------------------- + /** + * Implementation of the CohQL "source" command. + */ + public static class SourceStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a SourceStatement that will execute commands from + * the specified file. + * + * @param sFileName the file of commands to execute + */ + public SourceStatement(String sFileName) + { + f_sFileName = sFileName; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + return source(f_sFileName, ctx); + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("source('%s')", f_sFileName); + } + + @Override + public boolean isManagingTimeout() + { + return true; + } + + // ----- helper methods --------------------------------------------- + + /** + * Attempt to parse, build, and execute from the given file of + * statements tracing on the given PrintWriter if the given flag + * indicates the need to trace. + * + * @param sFileName a String representing a filename to process + * @param ctx the {@link ExecutionContext} to use to execute + * statements + * + * @return the StatementResult resulting form the last statement to + * be processed in the set of statements from the file + */ + public StatementResult source(String sFileName, ExecutionContext ctx) + { + if (sFileName == null || sFileName.length() == 0) + { + return StatementResult.NULL_RESULT; + } + + Reader reader; + + try + { + reader = new BufferedReader(new FileReader(sFileName)); + } + catch (FileNotFoundException e) + { + String sError = "file not found " + sFileName; + + if (ctx.isStopOnError()) + { + throw new CohQLException(sError); + } + + + traceout(sError, ctx.getWriter(), ctx.isTraceEnabled()); + + return StatementResult.NULL_RESULT; + } + + boolean fSavedSilent = ctx.isSilent(); + boolean fSavedSanity = ctx.isSanityChecking(); + ctx.setSilentMode(true); + try + { + ctx.getStatementExecutor().execute(reader, ctx); + } + finally + { + ctx.setSilentMode(fSavedSilent); + ctx.setSanityCheckingEnabled(fSavedSanity); + } + + return StatementResult.NULL_RESULT; + } + + /** + * Write the given line on the give PrintWriter if the trace flag is true. + * + * @param sLine a String to be displayed + * @param writer a PrintWriter to write upon + * @param fTrace a flag indicating whether to trace + */ + protected void traceout(String sLine, PrintWriter writer, boolean fTrace) + { + if (fTrace && writer != null) + { + writer.println(sLine); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The file name to be used in the CohQL "source" command. + */ + protected final String f_sFileName; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of a SourceStatementBuilder. + */ + public static final SourceStatementBuilder INSTANCE = new SourceStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/TruncateCacheStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/TruncateCacheStatementBuilder.java new file mode 100644 index 0000000000000..8408eb6bc4205 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/TruncateCacheStatementBuilder.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link TruncateCacheStatement}. + * + * @author bbc 2015.09.01 + * @since Coherence 12.2.1.1 + */ +public class TruncateCacheStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public TruncateCacheStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for truncate cache"); + } + + return new TruncateCacheStatement(sCacheName); + } + + @Override + public String getSyntax() + { + return "TRUNCATE CACHE 'cache-name'"; + } + + @Override + public String getDescription() + { + return "Remove all entries from the cache 'cache-name'."; + } + + // ----- inner class: TruncateCacheStatement -------------------------------- + + /** + * Implementation of the CohQL "TRUNCATE CACHE" command. + */ + public static class TruncateCacheStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a TruncateCacheStatement. + * + * @param sCacheName the name of the cache to truncate + */ + public TruncateCacheStatement(String sCacheName) + { + f_sCacheName = sCacheName; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + ctx.getCacheFactory().ensureTypedCache(f_sCacheName, null, withoutTypeChecking()).truncate(); + + return StatementResult.NULL_RESULT; + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCacheName, ctx); + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("CacheFactory.getCache(\"%s\")).truncate()", f_sCacheName); + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the cache to be truncated. + */ + protected final String f_sCacheName; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of TruncateCacheStatementBuilder. + */ + public static final TruncateCacheStatementBuilder INSTANCE = new TruncateCacheStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/UpdateStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/UpdateStatementBuilder.java new file mode 100644 index 0000000000000..09ab29f1279d5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/UpdateStatementBuilder.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; + +import com.tangosol.coherence.dslquery.internal.UpdateSetListMaker; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.util.Filter; +import com.tangosol.util.InvocableMap; + +import java.io.PrintWriter; + +import java.util.List; +import java.util.Map; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link UpdateStatement}. + * + * @author jk 2013.12.17 + * @since Coherence 12.2.1 + */ +public class UpdateStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public UpdateStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sCacheName = getCacheName(term); + + if (sCacheName == null || sCacheName.isEmpty()) + { + throw new CohQLException("Cache name needed for update command"); + } + + String sAlias = getAlias(term); + Term termSetList = getSetList(term); + NodeTerm termWhere = getWhere(term); + Filter filter = ensureFilter(termWhere, sCacheName, sAlias, listBindVars, namedBindVars, ctx); + + UpdateSetListMaker transformer = createUpdateSetListMaker(ctx, listBindVars, namedBindVars); + + transformer.setAlias(sAlias); + + try + { + InvocableMap.EntryProcessor processor = transformer.makeSetList((NodeTerm) termSetList); + + return new UpdateStatement(sCacheName, filter, processor); + } + catch (Exception e) + { + throw new CohQLException("Error creating update processor", e); + } + } + + @Override + public String getSyntax() + { + return "UPDATE 'cache-name' [[AS] alias] SET update-statement {, update-statement}*\n" + + " [WHERE conditional-expression]"; + } + + @Override + public String getDescription() + { + return "Update the cache named 'cache-name that are selected by the given conditional\n" + + "expression. If no conditional-expression is given all entries will be updated!\n" + + "Use with Care! Assignment of both simple values and java constructors and\n" + + "static methods are supported. Simple addition and multiplication is supported as\n" + + "well. E.G. update 'employees' set salary = 1000, vacation = 200 where grade > 7"; + } + + // ----- helper methods ------------------------------------------------- + + /** + * Create an instance of an {@link UpdateSetListMaker}. + * + * @param context the {@link ExecutionContext} to use to configure the + * UpdateSetListMaker + * @param listBindVars the indexed bind variables that the SelectListMaker + * should use + * @param namedBindVars the named bind variables that the SelectListMaker + * should use + * + * @return an UpdateSetListMaker + */ + protected UpdateSetListMaker createUpdateSetListMaker(ExecutionContext context, List listBindVars, + ParameterResolver namedBindVars) + { + UpdateSetListMaker transformer = new UpdateSetListMaker(listBindVars, namedBindVars, + context.getCoherenceQueryLanguage()); + + transformer.setExtendedLanguage(context.isExtendedLanguageEnabled()); + + return transformer; + } + + // ----- inner class: UpdateStatement ----------------------------------- + + /** + * Implementation of the CohQL "UPDATE" command. + */ + public static class UpdateStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a UpdateStatement that will update the specified cache. + * + * @param sCache the name of the cache to update + * @param filter the {@link Filter} to select the cache entries + * to update + * @param processor the {@link InvocableMap.EntryProcessor} that + * will perform the update + */ + public UpdateStatement(String sCache, Filter filter, + InvocableMap.EntryProcessor processor) + { + f_sCache = sCache; + f_filter = filter; + f_processor = processor; + } + + // ----- Statement interface ---------------------------------------- + + @Override + public StatementResult execute(ExecutionContext ctx) + { + Map mapResult = ctx.getCacheFactory().ensureTypedCache(f_sCache, null, withoutTypeChecking()) + .invokeAll(f_filter, f_processor); + + return new DefaultStatementResult(mapResult); + } + + @Override + public void showPlan(PrintWriter out) + { + out.printf("CacheFactory.getCache(\"%s\").invokeAll(%s, %s)", + f_sCache, f_filter, f_processor); + } + + @Override + public void sanityCheck(ExecutionContext ctx) + { + assertCacheName(f_sCache, ctx); + } + + // ----- data members ----------------------------------------------- + + /** + * The name of the cache to be updated. + */ + protected final String f_sCache; + + /** + * The {@link Filter} that will be used to select entries to be + * updated. + */ + protected final Filter f_filter; + + /** + * The {@link InvocableMap.EntryProcessor} that will perform the + * "update" command. + */ + protected final InvocableMap.EntryProcessor f_processor; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of a UpdateStatementBuilder. + */ + public static final UpdateStatementBuilder INSTANCE = new UpdateStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/package.html new file mode 100644 index 0000000000000..ad2b7ed30e012 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/package.html @@ -0,0 +1,6 @@ + +This package contains {@link com.tangosol.coherence.dslquery.StatementBuilder} implementations and +{@link com.tangosol.coherence.dslquery.CohQLStatement} implementations. + +@serial exclude + \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/AbstractSnapshotStatement.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/AbstractSnapshotStatement.java new file mode 100644 index 0000000000000..d353bd87ab2f7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/AbstractSnapshotStatement.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatement; + +import com.tangosol.io.FileHelper; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +/** + * Abstract implementation of an {@link AbstractStatement} providing functionality + * useful for generic snapshot statements. + */ +public abstract class AbstractSnapshotStatement + extends AbstractStatement + { + // ----- constructors --------------------------------------------------- + + /** + * Create a AbstractSnapshotStatement that will prove base functionality for + * other snapshot commands. + * + * @param sSnapshotName the snapshot name to create + * @param sServiceName the service to snapshot + */ + public AbstractSnapshotStatement(String sSnapshotName, String sServiceName) + { + f_sSnapshotName = sSnapshotName; + f_sServiceName = sServiceName; + } + + // ----- AbstractStatement methods -------------------------------------- + + @Override + public void showPlan(PrintWriter out) + { + } + + // ----- helpers -------------------------------------------------------- + + /** + * Return a confirmation message. + * + * @param sAction the action to be performed + * + * @return a confirmation message + */ + protected String getConfirmationMessage(String sAction) + { + return "Are you sure you want to " + sAction + " a snapshot called '" + f_sSnapshotName + "' for " + + "service '" + f_sServiceName + "'? (y/n): "; + } + + /** + * Validate that the snapshot name conforms to standard. + * + * @param sSnapshotName the name of snapshot to validate + * + * @throws CohQLException if the name is not valid + */ + protected void validateSnapshotName(String sSnapshotName) + throws CohQLException + { + String sSafeSnapshotName = FileHelper.toFilename(sSnapshotName); + + if (!sSafeSnapshotName.equals(sSnapshotName)) + { + throw new CohQLException("The supplied snapshot name " + sSnapshotName + " is not a valid " + + "file name. Consider using " + sSafeSnapshotName); + } + } + + /** + * Validate that the service f_sServiceName exists. + * + * @param helper the {@link PersistenceToolsHelper} instance to use to validate + */ + protected void validateServiceExists(PersistenceToolsHelper helper) + { + if (!helper.serviceExists(f_sServiceName)) + { + throw new CohQLException("Service '" + f_sServiceName + "' does not exist"); + } + } + + /** + * Validate that a snapshot f_sSnapshotName exists for the given service + * f_sServiceName. + * + * @param helper the {@link PersistenceToolsHelper} instance to use to validate + */ + protected void validateSnapshotExistsForService(PersistenceToolsHelper helper) + { + if (!helper.snapshotExists(f_sServiceName, f_sSnapshotName)) + { + throw new CohQLException("Snapshot '" + f_sSnapshotName + "' does not exist for service '" + f_sServiceName + + "'"); + } + } + + /** + * Validate that an archived snapshot f_sSnapshotName exists for the given service + * f_sServiceName. + * + * @param helper the {@link PersistenceToolsHelper} instance to use to validate + */ + protected void validateArchivedSnapshotExistsForService(PersistenceToolsHelper helper) + { + if (!helper.archivedSnapshotExists(f_sServiceName, f_sSnapshotName)) + { + throw new CohQLException("Snapshot '" + f_sSnapshotName + "' does not exist for service '" + f_sServiceName + + "'"); + } + } + + /** + * Replace the following macro's for the snapshot name:
+ *

    + *
  • %y - Year
  • + *
  • %m - Month
  • + *
  • %d - Day of month
  • + *
  • %w - Day of week. mon,tues,wed, etc
  • + *
  • %M - Month name - Jan, Feb, etc
  • + *
  • %hh - Hour
  • + *
  • %mm - Minute
  • + *
+ * + * @param sSnapshotName the snapshot name to replace macros + * + * @return the formatted snapshot name + */ + protected static String replaceDateMacros(String sSnapshotName) + { + String sFinalName = new String(sSnapshotName); + Calendar calendar = Calendar.getInstance(); + + sFinalName = sFinalName.replaceAll("%mm", String.format("%02d", calendar.get(Calendar.MINUTE))). + replaceAll("%m", String.format("%02d", calendar.get(Calendar.MONTH))). + replaceAll("%y", String.format("%d", calendar.get(Calendar.YEAR))). + replaceAll("%d", String.format("%02d", calendar.get(Calendar.DAY_OF_MONTH))). + replaceAll("%hh", String.format("%02d", calendar.get(Calendar.HOUR_OF_DAY))). + replaceAll("%w", WEEKDAY_NAME.format(calendar.getTime())). + replaceAll("%M", MONTH_NAME.format(calendar.getTime())); + + return sFinalName; + } + + // ----- constants ------------------------------------------------------ + + /** + * Result to output on command success. + */ + protected static final String SUCCESS = "Success"; + + /** + * Format month name. + */ + public static final SimpleDateFormat MONTH_NAME = new SimpleDateFormat("MMMM"); + + /** + * Format weekday name. + */ + public static final SimpleDateFormat WEEKDAY_NAME = new SimpleDateFormat("E"); + + // ----- data members --------------------------------------------------- + + /** + * Snapshot name to utilize. + */ + protected final String f_sSnapshotName; + + /** + * Service name to carry out operations for. + */ + protected final String f_sServiceName; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ArchiveSnapshotStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ArchiveSnapshotStatementBuilder.java new file mode 100644 index 0000000000000..e04ecc654058b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ArchiveSnapshotStatementBuilder.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link ArchiveSnapshotStatement}. + * + * @author tam 2014.08.27 + * @since Coherence 12.2.1 + */ +public class ArchiveSnapshotStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public ArchiveSnapshotStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sServiceName = atomicStringValueOf(term.findAttribute("service")); + String sSnapshotName = atomicStringValueOf(term.findAttribute("snapshotname")); + + if (sSnapshotName == null || sSnapshotName.isEmpty()) + { + throw new CohQLException("Snapshot name required for archive snapshot"); + } + + if (sServiceName == null || sServiceName.isEmpty()) + { + throw new CohQLException("Service name required for archive snapshot"); + } + + return new ArchiveSnapshotStatement(sSnapshotName, sServiceName); + } + + @Override + public String getSyntax() + { + return "ARCHIVE SNAPSHOT 'snapshot-name' 'service'"; + } + + @Override + public String getDescription() + { + return "Archive a snapshot for an individual service to a centralized location using\n" + + "the archiver configured for the service."; + } + + /** + * Implementation of the CohQL "ARCHIVE SNAPSHOT" command. + */ + public static class ArchiveSnapshotStatement + extends AbstractSnapshotStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a new ArchiveSnapshotStatement for the snapshot and service name. + * + * @param sSnapshotName snapshot name to create statement for + * @param sServiceName service name to create statement for + */ + public ArchiveSnapshotStatement(String sSnapshotName, String sServiceName) + { + super(replaceDateMacros(sSnapshotName), sServiceName); + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + return getConfirmationMessage("archive"); + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + PrintWriter out = ctx.getWriter(); + + try + { + validateServiceExists(helper); + validateSnapshotExistsForService(helper); + validateSnapshotName(f_sSnapshotName); + + // ensure that the archived snapshot does not exist for the service + if (helper.archivedSnapshotExists(f_sServiceName, f_sSnapshotName)) + { + throw new CohQLException("Archived snapshot '" + f_sSnapshotName + "' already exists for" + + " service '" + f_sServiceName + "'"); + } + + helper.ensureReady(ctx, f_sServiceName); + + out.println("Archiving snapshot '" + f_sSnapshotName + "' for service '" + f_sServiceName + "'"); + out.flush(); + helper.invokeOperationWithWait(PersistenceToolsHelper.ARCHIVE_SNAPSHOT, f_sSnapshotName, f_sServiceName); + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in ARCHIVE SNAPSHOT"); + } + + return new DefaultStatementResult(SUCCESS, true); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link ArchiveSnapshotStatementBuilder}. + */ + public static final ArchiveSnapshotStatementBuilder INSTANCE = new ArchiveSnapshotStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/CreateSnapshotStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/CreateSnapshotStatementBuilder.java new file mode 100644 index 0000000000000..3e089905d8b8e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/CreateSnapshotStatementBuilder.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.oracle.coherence.common.base.Blocking; +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.net.CacheFactory; + +import java.io.PrintWriter; + +import java.util.List; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link CreateSnapshotStatement}. + * + * @author tam 2014.08.04 + * @since Coherence 12.2.1 + */ +public class CreateSnapshotStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public CreateSnapshotStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sServiceName = atomicStringValueOf(term.findAttribute("service")); + String sSnapshotName = atomicStringValueOf(term.findAttribute("snapshotname")); + + if (sSnapshotName == null || sSnapshotName.isEmpty()) + { + throw new CohQLException("Snapshot name required for create snapshot"); + } + + if (sServiceName == null || sServiceName.isEmpty()) + { + throw new CohQLException("Service name required for create snapshot"); + } + + return new CreateSnapshotStatement(sSnapshotName, sServiceName); + } + + @Override + public String getSyntax() + { + return "CREATE SNAPSHOT 'snapshot-name' 'service'"; + } + + @Override + public String getDescription() + { + return "Create a snapshot of cache contents for an individual service.\n" + + "If you do not specify a service, then all services configured for either\n" + + "on-demand or active mode will be included in snapshot. You can supply the\n" + + "following to timestamp the snapshot:\n" + + "%y - Year, %m - Month, %d - Day, %hh - Hour, %mm - Minute, %w - weekday,\n" + + "%M - Month name"; + } + + /** + * Implementation of the CohQL "CREATE SNAPSHOT" command. + */ + public static class CreateSnapshotStatement + extends AbstractSnapshotStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a new CreateSnapshotStatement for the snapshot and service name. + * + * @param sSnapshotName snapshot name to create statement for + * @param sServiceName service name to create statement for + */ + public CreateSnapshotStatement(String sSnapshotName, String sServiceName) + { + super(replaceDateMacros(sSnapshotName), sServiceName); + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + return getConfirmationMessage("create"); + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + PrintWriter out = ctx.getWriter(); + + try + { + validateSnapshotName(f_sSnapshotName); + validateServiceExists(helper); + + // Temporary Debugging only + String sErrorMsg = null; + for (int i = 25; i > 0 && helper.snapshotExists(f_sServiceName, f_sSnapshotName); --i) + { + if (sErrorMsg == null) + { + sErrorMsg = "A snapshot named '" + f_sSnapshotName + "' already exists for " + + "service '" + f_sServiceName + "'"; + } + CacheFactory.log(sErrorMsg + ": " + helper.listSnapshots(), CacheFactory.LOG_WARN); + Blocking.sleep(250L); + } + + if (sErrorMsg != null) + { + throw new CohQLException(sErrorMsg); + } + + helper.ensureReady(ctx, f_sServiceName); + out.println("Creating snapshot '" + f_sSnapshotName + "' for service '" + f_sServiceName + "'"); + out.flush(); + helper.invokeOperationWithWait(PersistenceToolsHelper.CREATE_SNAPSHOT, f_sSnapshotName, f_sServiceName); + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in CREATE SNAPSHOT"); + } + + return new DefaultStatementResult(SUCCESS, true); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link CreateSnapshotStatementBuilder}. + */ + public static final CreateSnapshotStatementBuilder INSTANCE = new CreateSnapshotStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ForceRecoveryStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ForceRecoveryStatementBuilder.java new file mode 100644 index 0000000000000..dcc4206c62486 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ForceRecoveryStatementBuilder.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatement; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; +import java.util.List; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link ForceRecoveryStatement}. + * + * @author tam 2014.09.01 + * @since Coherence 12.2.1.1 + */ +public class ForceRecoveryStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public ForceRecoveryStatement realize(ExecutionContext ctx, NodeTerm + term, List listBindVars, + ParameterResolver namedBindVars) + { + String sServiceName = atomicStringValueOf(term.findAttribute("service")); + + if (sServiceName == null || sServiceName.isEmpty()) + { + throw new CohQLException("Service name required for force recovery"); + } + + return new ForceRecoveryStatement(sServiceName == null || sServiceName.isEmpty() ? null : sServiceName); + } + + @Override + public String getSyntax() + { + return "FORCE RECOVERY 'service'"; + } + + @Override + public String getDescription() + { + return "Proceed with recovery despite the dynamic quorum policy objections.\n" + + "This may lead to the partial or full data loss at the corresponding\n" + + "cache service."; + } + + /** + * Implementation of the CohQL "FORCE RECOVERY" command. + */ + public static class ForceRecoveryStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a ForceRecoveryStatement that will force recovery. + * + * @param sServiceName the service name to resume on + */ + public ForceRecoveryStatement(String sServiceName) + { + f_sServiceName = sServiceName; + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + return "This operation may result in partial or full data loss. \n" + + "Are you sure you want to force recovery of this service '" + f_sServiceName + "'? (y/n): "; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + PrintWriter out = ctx.getWriter(); + + try + { + if (!helper.serviceExists(f_sServiceName)) + { + throw new CohQLException("Service '" + f_sServiceName + "' does not exist"); + } + + out.println("Forcing Recovery '" + f_sServiceName + "'"); + out.flush(); + helper.invokeOperation(PersistenceToolsHelper.FORCE_RECOVERY, f_sServiceName, new Object[0], new String[0]); + out.println("Recovery forced"); + out.flush(); + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in FORCE RECOVERY"); + } + + return new DefaultStatementResult(AbstractSnapshotStatement.SUCCESS, true); + } + + @Override + public void showPlan(PrintWriter out) + { + } + + // ----- data members ----------------------------------------------- + + /** + * The service name to resume. + */ + private final String f_sServiceName; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link ForceRecoveryStatementBuilder}. + */ + public static final ForceRecoveryStatementBuilder INSTANCE = new ForceRecoveryStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ListArchiverStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ListArchiverStatementBuilder.java new file mode 100644 index 0000000000000..be30dbca39f56 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ListArchiverStatementBuilder.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatement; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link ListArchiverStatement}. + * + * @author tam 2014.08.04 + * @since Coherence 12.2.1 + */ +public class ListArchiverStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public ListArchiverStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sServiceName = atomicStringValueOf(term.findAttribute("service")); + + if (sServiceName == null || sServiceName.isEmpty()) + { + throw new CohQLException("Service name required for LIST ARCHIVER"); + } + + return new ListArchiverStatement(sServiceName == null || sServiceName.isEmpty() ? null : sServiceName); + } + + @Override + public String getSyntax() + { + return "LIST ARCHIVER 'service'"; + } + + @Override + public String getDescription() + { + return "List the archiver configured for a given service."; + } + + /** + * Implementation of the CohQL "LIST ARCHIVER" command. + */ + public static class ListArchiverStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a ListArchiverStatement that will list the archiver for a service. + * + * @param sServiceName the service name to resume + */ + public ListArchiverStatement(String sServiceName) + { + f_sServiceName = sServiceName; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + Object oResult; + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + + try + { + if (!helper.serviceExists(f_sServiceName)) + { + throw new CohQLException("Service '" + f_sServiceName + "' does not exist"); + } + + oResult = helper.getArchiver(f_sServiceName); + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in LIST ARCHIVER"); + } + + return new DefaultStatementResult(oResult, true); + } + + @Override + public void showPlan(PrintWriter out) + { + } + + // ----- data members ----------------------------------------------- + + /** + * The service name to list the archiver for. + */ + private final String f_sServiceName; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link ListArchiverStatementBuilder}. + */ + public static final ListArchiverStatementBuilder INSTANCE = new ListArchiverStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ListServicesStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ListServicesStatementBuilder.java new file mode 100644 index 0000000000000..60d525871fec5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ListServicesStatementBuilder.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatement; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dslquery.statement.FormattedMapStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; +import java.util.Map; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link ListServicesStatement}. + * + * @author tam 2014.08.01 + * @since Coherence 12.2.1 + */ +public class ListServicesStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public ListServicesStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + return new ListServicesStatement("true".equals(atomicStringValueOf(term.findAttribute("environment")))); + } + + @Override + public String getSyntax() + { + return "LIST SERVICES [ENVIRONMENT]"; + } + + @Override + public String getDescription() + { + return "List services and their persistence mode, quorum status and current\n" + + "operation status. If you specify ENVIRONMENT then the persistence environment\n" + + "details are displayed."; + } + + /** + * Implementation of the CohQL "LIST SERVICES" command. + */ + public static class ListServicesStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a ListServicesStatement that will list services and their + * persistence mode. + * + * @param fEnvironment true if the environment option was used + */ + public ListServicesStatement(boolean fEnvironment) + { + f_fEnvironment = fEnvironment; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + Map mapServices; + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + DefaultStatementResult result; + + try + { + if (f_fEnvironment) + { + result = new DefaultStatementResult(helper.listServicesEnvironment()); + } + else + { + mapServices = helper.listServices(); + result = new FormattedMapStatementResult(mapServices); + + ((FormattedMapStatementResult) result).setColumnHeaders(new String[] {"Service Name", "Mode", + "Quorum Policy", "Current Operation"}); + } + } + catch (Exception e) + { + throw new CohQLException("Error in LIST SERVICES: " + e.getMessage()); + } + + return result; + } + + @Override + public void showPlan(PrintWriter out) + { + } + + // ----- data members ----------------------------------------------- + + /** + * Indicates if environment option was specified. + */ + private final boolean f_fEnvironment; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link ListServicesStatementBuilder}. + */ + public static final ListServicesStatementBuilder INSTANCE = new ListServicesStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ListSnapshotsStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ListSnapshotsStatementBuilder.java new file mode 100644 index 0000000000000..8f4c24e08a5d7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ListSnapshotsStatementBuilder.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatement; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; +import java.util.Map; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link ListSnapshotsStatement}. + * + * @author tam 2014.08.01 + * @since Coherence 12.2.1 + */ +public class ListSnapshotsStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public ListSnapshotsStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sService = atomicStringValueOf(term.findAttribute("service")); + boolean fArchived = "true".equals(atomicStringValueOf(term.findAttribute("archived"))); + + return new ListSnapshotsStatement(sService, fArchived); + + } + + // ----- StatementBuilder interface ------------------------------------- + + @Override + public String getSyntax() + { + return "LIST [ARCHIVED] SNAPSHOTS ['service']"; + } + + @Override + public String getDescription() + { + return "List snapshots for an individual service or all services. If ARCHIVED keyword\n" + + "is included then the archived snapshots for the service is listed. If there is\n" + + "no archiver defined for the service, then an exception will be raised."; + + } + + /** + * Implementation of the CohQL "LIST SNAPSHOTS" command. + */ + public static class ListSnapshotsStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a ListSnapshotsStatement that will list snapshots for services. + * + * @param sService the service to list snapshots for + * @param fArchived if true we want to list archived snapshots + */ + public ListSnapshotsStatement(String sService, boolean fArchived) + { + f_sService = sService; + f_fArchived = fArchived; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + Object oResult = null; + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + + try + { + if (f_sService == null) + { + oResult = f_fArchived ? helper.listArchivedSnapshots() : helper.listSnapshots(); + } + else + { + // retrieve the list of services + Map mapServices = helper.listServices(); + + if (!mapServices.containsKey(f_sService)) + { + throw new CohQLException("Service '" + f_sService + "' does not exist"); + } + + oResult = f_fArchived ? helper.listArchivedSnapshots(f_sService) : helper.listSnapshots(f_sService); + } + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in LIST SNAPSHOTS"); + } + + return new DefaultStatementResult(oResult, true); + } + + @Override + public void showPlan(PrintWriter out) + { + } + + // ----- data members ----------------------------------------------- + + /** + * Service name to list services for. + */ + private final String f_sService; + + /** + * Indicates if we with to list archived snapshots. + */ + private final boolean f_fArchived; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link ListSnapshotsStatementBuilder}. + */ + public static final ListSnapshotsStatementBuilder INSTANCE = new ListSnapshotsStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/RecoverSnapshotStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/RecoverSnapshotStatementBuilder.java new file mode 100644 index 0000000000000..0883dd9eaba12 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/RecoverSnapshotStatementBuilder.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementBuilder; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +/** + * An implementation of a {@link StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link RecoverSnapshotStatement}. + * + * @author tam 2014.08.04 + * @since Coherence 12.2.1 + */ +public class RecoverSnapshotStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public RecoverSnapshotStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sServiceName = atomicStringValueOf(term.findAttribute("service")); + String sSnapshotName = atomicStringValueOf(term.findAttribute("snapshotname")); + + if (sSnapshotName == null || sSnapshotName.isEmpty()) + { + throw new CohQLException("Snapshot name required for recover snapshot"); + } + + if (sServiceName == null || sServiceName.isEmpty()) + { + throw new CohQLException("Service name required for recover snapshot"); + } + + return new RecoverSnapshotStatement(sSnapshotName, + sServiceName == null || sServiceName.isEmpty() ? null : sServiceName); + } + + @Override + public String getSyntax() + { + return "RECOVER SNAPSHOT 'snapshot-name' 'service'"; + } + + @Override + public String getDescription() + { + return "Recover a snapshot for an individual service."; + } + + /** + * Implementation of the CohQL "RECOVER SNAPSHOT" command. + */ + public static class RecoverSnapshotStatement + extends AbstractSnapshotStatement + { + // ----- constructors ----------------------------------------------- + + public RecoverSnapshotStatement(String sSnapshotName, String sServiceName) + { + super(replaceDateMacros(sSnapshotName), sServiceName); + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + return getConfirmationMessage("recover"); + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + PrintWriter out = ctx.getWriter(); + + try + { + validateServiceExists(helper); + validateSnapshotExistsForService(helper); + helper.ensureReady(ctx, f_sServiceName); + + out.println("Recovering snapshot '" + f_sSnapshotName + "' for service '" + f_sServiceName + "'"); + out.flush(); + helper.invokeOperationWithWait(PersistenceToolsHelper.RECOVER_SNAPSHOT, f_sSnapshotName, f_sServiceName); + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in RECOVER SNAPSHOT"); + } + + return new DefaultStatementResult(SUCCESS, true); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link RecoverSnapshotStatementBuilder}. + */ + public static final RecoverSnapshotStatementBuilder INSTANCE = new RecoverSnapshotStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/RemoveSnapshotStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/RemoveSnapshotStatementBuilder.java new file mode 100644 index 0000000000000..bda652b9c517a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/RemoveSnapshotStatementBuilder.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link RemoveSnapshotStatement}. + * + * @author tam 2014.08.01 + * @since Coherence 12.2.1 + */ +public class RemoveSnapshotStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public RemoveSnapshotStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sServiceName = atomicStringValueOf(term.findAttribute("service")); + String sSnapshotName = atomicStringValueOf(term.findAttribute("snapshotname")); + boolean fArchived = "true".equals(atomicStringValueOf(term.findAttribute("archived"))); + + if (sSnapshotName == null || sSnapshotName.isEmpty()) + { + throw new CohQLException("Snapshot name required for remove snapshot"); + } + + if (sServiceName == null || sServiceName.isEmpty()) + { + throw new CohQLException("Service name required for remove snapshot"); + } + + return new RemoveSnapshotStatement(sSnapshotName, sServiceName, fArchived); + } + + @Override + public String getSyntax() + { + return "REMOVE [ARCHIVED] SNAPSHOT 'snapshot-name' 'service'"; + } + + @Override + public String getDescription() + { + return "Remove a snapshot for and individual service. If the ARCHIVED keyword is used\n" + + "then an archived snapshot of the name will be removed."; + } + + /** + * Implementation of the CohQL "REMOVE [ARCHIVED] SNAPSHOT" command. + */ + public static class RemoveSnapshotStatement + extends AbstractSnapshotStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a new RemoveSnapshotStatement for the snapshot and service name. + * + * @param sSnapshotName snapshot name to create statement for + * @param sServiceName service name to create statement for + * @param fArchive indicates if the snapshot is archived + */ + public RemoveSnapshotStatement(String sSnapshotName, String sServiceName, boolean fArchive) + { + super(replaceDateMacros(sSnapshotName), sServiceName); + f_fArchived = fArchive; + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + return "Are you sure you want to remove" + (f_fArchived ? "archived " : "") + " snapshot called '" + + f_sSnapshotName + "' for service '" + f_sServiceName + "'? (y/n): "; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + PrintWriter out = ctx.getWriter(); + + try + { + if (f_fArchived) + { + validateServiceExists(helper); + validateArchivedSnapshotExistsForService(helper); + helper.ensureReady(ctx, f_sServiceName); + + out.println("Removing archived snapshot '" + f_sSnapshotName + "' for service '" + f_sServiceName + + "'"); + out.flush(); + helper.invokeOperationWithWait(PersistenceToolsHelper.REMOVE_ARCHIVED_SNAPSHOT, f_sSnapshotName, f_sServiceName); + } + else + { + validateServiceExists(helper); + validateSnapshotExistsForService(helper); + helper.ensureReady(ctx, f_sServiceName); + + out.println("Removing snapshot '" + f_sSnapshotName + "' for service '" + f_sServiceName + "'"); + out.flush(); + helper.invokeOperationWithWait(PersistenceToolsHelper.REMOVE_SNAPSHOT, f_sSnapshotName, f_sServiceName); + } + + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in REMOVE SNAPSHOT"); + } + + return new DefaultStatementResult(SUCCESS, true); + } + + // ----- data members ------------------------------------------- + + /** + * Indicates if the snapshot to be removed is an archived snapshot. + */ + final boolean f_fArchived; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link RemoveSnapshotStatementBuilder}. + */ + public static final RemoveSnapshotStatementBuilder INSTANCE = new RemoveSnapshotStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ResumeServiceStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ResumeServiceStatementBuilder.java new file mode 100644 index 0000000000000..f77cb003b0962 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ResumeServiceStatementBuilder.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatement; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link ResumeServiceStatement}. + * + * @author tam 2014.08.05 + * @since Coherence 12.2.1 + */ +public class ResumeServiceStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public ResumeServiceStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sServiceName = atomicStringValueOf(term.findAttribute("service")); + + if (sServiceName == null || sServiceName.isEmpty()) + { + throw new CohQLException("Service name required for resume service"); + } + + return new ResumeServiceStatement(sServiceName == null || sServiceName.isEmpty() ? null : sServiceName); + } + + @Override + public String getSyntax() + { + return "RESUME SERVICE 'service'"; + } + + @Override + public String getDescription() + { + return "Resume a previously suspended service. \n" + "Note: If the service is not suspended, this is a No-op."; + } + + /** + * Implementation of the CohQL "RESUME SERVICE" command. + */ + public static class ResumeServiceStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a ResumeServiceStatement that will resume a suspended service. + * + * @param sServiceName the service name to resume + */ + public ResumeServiceStatement(String sServiceName) + { + f_sServiceName = sServiceName; + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + return "Are you sure you want to resume the service '" + f_sServiceName + "'? (y/n): "; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + PrintWriter out = ctx.getWriter(); + + + try + { + if (!helper.serviceExists(f_sServiceName)) + { + throw new CohQLException("Service '" + f_sServiceName + "' does not exist"); + } + + out.println("Resuming service '" + f_sServiceName + "'"); + out.flush(); + helper.resumeService(f_sServiceName); + out.println("Service resumed"); + out.flush(); + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in RESUME SERVICE"); + } + + return new DefaultStatementResult(AbstractSnapshotStatement.SUCCESS, true); + } + + @Override + public void showPlan(PrintWriter out) + { + } + + // ----- data members ----------------------------------------------- + + /** + * The service name to resume. + */ + private final String f_sServiceName; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link ResumeServiceStatementBuilder}. + */ + public static final ResumeServiceStatementBuilder INSTANCE = new ResumeServiceStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/RetrieveSnapshotStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/RetrieveSnapshotStatementBuilder.java new file mode 100644 index 0000000000000..9b1592866d77c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/RetrieveSnapshotStatementBuilder.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link RetrieveSnapshotStatement}. + * + * @author tam 2014.08.27 + * @since Coherence 12.2.1 + */ +public class RetrieveSnapshotStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public RetrieveSnapshotStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sServiceName = atomicStringValueOf(term.findAttribute("service")); + String sSnapshotName = atomicStringValueOf(term.findAttribute("snapshotname")); + boolean fVerbose = "true".equals(atomicStringValueOf(term.findAttribute("overwrite"))); + + if (sSnapshotName == null || sSnapshotName.isEmpty()) + { + throw new CohQLException("Snapshot name required for retrieve snapshot"); + } + + if (sServiceName == null || sServiceName.isEmpty()) + { + throw new CohQLException("Service name required for retrieve snapshot"); + } + + return new RetrieveSnapshotStatement(sSnapshotName, sServiceName, fVerbose); + } + + @Override + public String getSyntax() + { + return "RETRIEVE ARCHIVED SNAPSHOT 'snapshot-name' 'service' [OVERWIRTE]"; + } + + @Override + public String getDescription() + { + return "Retrieve an archived snapshot for an individual service from a centralized\n" + + "location using the archiver configured for the service. If a local snapshot\n" + + "exists with the same name an error is thrown unless OVERWRITE option is used"; + } + + /** + * Implementation of the CohQL "RETRIEVE SNAPSHOT" command. + */ + public static class RetrieveSnapshotStatement + extends AbstractSnapshotStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a new RetrieveSnapshotStatement for the snapshot and service name. + * + * @param sSnapshotName snapshot name to create statement for + * @param sServiceName service name to create statement for + * @param fOverwrite indicates if the snapshot should be overwritten + */ + public RetrieveSnapshotStatement(String sSnapshotName, String sServiceName, boolean fOverwrite) + { + super(replaceDateMacros(sSnapshotName), sServiceName); + f_fOverwrite = fOverwrite; + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + return getConfirmationMessage("retrieve"); + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + PrintWriter out = ctx.getWriter(); + + try + { + validateServiceExists(helper); + validateArchivedSnapshotExistsForService(helper); + helper.ensureReady(ctx, f_sServiceName); + + if (helper.snapshotExists(f_sServiceName, f_sSnapshotName)) + { + if (f_fOverwrite) + { + out.println("Removing existing local snapshot '" + f_sSnapshotName + "' for service '" + + f_sServiceName + "' because OVERWRITE option was specified."); + out.flush(); + helper.invokeOperationWithWait(PersistenceToolsHelper.REMOVE_SNAPSHOT, f_sSnapshotName, f_sServiceName); + out.println("Local snapshot removed"); + out.flush(); + } + else + { + throw new CohQLException("A snapshot named '" + f_sSnapshotName + "' already exists for " + + "service '" + f_sServiceName + "'.\n" + + "Please remove it if you want to retrieve the snapshot or use the OVERWRITE option."); + } + } + + out.println("Retrieving snapshot '" + f_sSnapshotName + "' for service '" + f_sServiceName + "'"); + out.flush(); + helper.invokeOperationWithWait(PersistenceToolsHelper.RETRIEVE_ARCHIVED_SNAPSHOT, f_sSnapshotName, + f_sServiceName); + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in RETRIEVE SNAPSHOT"); + } + + return new DefaultStatementResult(SUCCESS, true); + } + + // ----- data members ------------------------------------------- + + /** + * Indicates if we should overwrite a local snapshot if it exists. + */ + private final boolean f_fOverwrite; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link RemoveSnapshotStatementBuilder}. + */ + public static final RetrieveSnapshotStatementBuilder INSTANCE = new RetrieveSnapshotStatementBuilder(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/SuspendServiceStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/SuspendServiceStatementBuilder.java new file mode 100644 index 0000000000000..456cad7727a4b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/SuspendServiceStatementBuilder.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatement; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.DefaultStatementResult; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import java.io.PrintWriter; + +import java.util.List; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link SuspendServiceStatement}. + * + * @author tam 2014.09.01 + * @since Coherence 12.2.1 + */ +public class SuspendServiceStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public SuspendServiceStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sServiceName = atomicStringValueOf(term.findAttribute("service")); + + if (sServiceName == null || sServiceName.isEmpty()) + { + throw new CohQLException("Service name required for suspend service"); + } + + return new SuspendServiceStatement(sServiceName == null || sServiceName.isEmpty() ? null : sServiceName); + } + + @Override + public String getSyntax() + { + return "SUSPEND SERVICE 'service'"; + } + + @Override + public String getDescription() + { + return "Suspend a service in preparation for Persistence operations."; + } + + /** + * Implementation of the CohQL "SUSPEND SERVICE" command. + */ + public static class SuspendServiceStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a SuspendServiceStatement that will suspend a service. + * + * @param sServiceName the service name to suspend + */ + public SuspendServiceStatement(String sServiceName) + { + f_sServiceName = sServiceName; + } + + @Override + public String getExecutionConfirmation(ExecutionContext ctx) + { + return "Are you sure you want to suspend the service '" + f_sServiceName + "'? (y/n): "; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + PersistenceToolsHelper helper = PersistenceToolsHelper.ensurePersistenceToolsHelper(ctx); + PrintWriter out = ctx.getWriter(); + + try + { + if (!helper.serviceExists(f_sServiceName)) + { + throw new CohQLException("Service '" + f_sServiceName + "' does not exist"); + } + + out.println("Suspending service '" + f_sServiceName + "'"); + out.flush(); + helper.suspendService(f_sServiceName); + out.println("Service suspend"); + out.flush(); + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in SUSPEND SERVICE"); + } + + return new DefaultStatementResult(AbstractSnapshotStatement.SUCCESS, true); + } + + @Override + public void showPlan(PrintWriter out) + { + } + + // ----- data members ----------------------------------------------- + + /** + * The service name to resume. + */ + private final String f_sServiceName; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link SuspendServiceStatementBuilder}. + */ + public static final SuspendServiceStatementBuilder INSTANCE = new SuspendServiceStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ValidateSnapshotStatementBuilder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ValidateSnapshotStatementBuilder.java new file mode 100644 index 0000000000000..c7bf33eb60f58 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/statement/persistence/ValidateSnapshotStatementBuilder.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.statement.persistence; + +import com.oracle.coherence.persistence.OfflinePersistenceInfo; +import com.oracle.coherence.persistence.PersistenceStatistics; +import com.oracle.coherence.persistence.PersistenceTools; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.ExecutionContext; +import com.tangosol.coherence.dslquery.StatementResult; +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.statement.AbstractStatement; +import com.tangosol.coherence.dslquery.statement.AbstractStatementBuilder; +import com.tangosol.coherence.dslquery.statement.FormattedMapStatementResult; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; + +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.net.ConfigurableCacheFactory; +import com.tangosol.net.ExtensibleConfigurableCacheFactory; +import com.tangosol.persistence.CachePersistenceHelper; + +import java.io.File; +import java.io.PrintWriter; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * An implementation of a {@link com.tangosol.coherence.dslquery.StatementBuilder} + * that parses a CohQL term tree to produce an instance of a {@link ValidateSnapshotStatement}. + * + * @author tam 2014.08.06 + * @since Coherence 12.2.1 + */ +public class ValidateSnapshotStatementBuilder + extends AbstractStatementBuilder + { + // ----- StatementBuilder interface ------------------------------------- + + @Override + public ValidateSnapshotStatement realize(ExecutionContext ctx, NodeTerm term, List listBindVars, + ParameterResolver namedBindVars) + { + String sSnapshotDir = atomicStringValueOf(term.findAttribute("snapshotdirectory")); + String sSnapshotName = atomicStringValueOf(term.findAttribute("snapshotname")); + String sServiceName = atomicStringValueOf(term.findAttribute("servicename")); + + boolean fVerbose = "true".equals(atomicStringValueOf(term.findAttribute("verbose"))); + boolean fArchived = "archived".equals(atomicStringValueOf(term.findAttribute("type"))); + + return new ValidateSnapshotStatement(sSnapshotDir, fVerbose, fArchived, + sSnapshotName, sServiceName); + } + + @Override + public String getSyntax() + { + return "VALIDATE SNAPSHOT 'snapshot-directory' [VERBOSE]\n" + + "VALIDATE SNAPSHOT 'snapshot-name' 'service-name' [VERBOSE]\n" + + "VALIDATE ARCHIVED SNAPSHOT 'snapshot-name' 'service-name' [VERBOSE]"; + } + + @Override + public String getDescription() + { + return "Validate a snapshot (or archived snapshot) to ensure contents are valid.\n" + + "For snapshots, either a directory location can be specified or a snapshot\n" + + "name and service name. When a directory name is not supplied, the\n" + + "snapshot directory information is retrieved via the operational configuration.\n" + + "Verbose mode provides more detailed information regarding the contents of the\n" + + "snapshot at the cost of increased execution time and resouce usage.\n" + + "Note: the relevant operational and cache configuration pertaining\n" + + "to the cluster and services to be valdiated must be available to be loaded."; + } + + /** + * Implementation of the CohQL "VALIDATE [ARCHIVED] SNAPSHOT" command. + */ + public static class ValidateSnapshotStatement + extends AbstractStatement + { + // ----- constructors ----------------------------------------------- + + /** + * Create a ValidateSnapshotStatement the will validate a snapshot on disk + * to ensure the files are consistent. + * + * @param sSnapshotDir the snapshot directory to validate + * @param fVerbose indicates if verbose output should be displayed + * @param fArchived indicates if an archived snapshot should be validated + * @param sSnapshotName the snapshot name to validate + * @param sServiceName the service name to validate + */ + public ValidateSnapshotStatement(String sSnapshotDir, boolean fVerbose, boolean fArchived, + String sSnapshotName,String sServiceName) + { + m_sSnapshotDir = sSnapshotDir; + f_fVerbose = fVerbose; + f_Archived = fArchived; + f_sSnapshotName = AbstractSnapshotStatement.replaceDateMacros(sSnapshotName); + f_sServiceName = sServiceName; + } + + @Override + public StatementResult execute(ExecutionContext ctx) + { + Object oResult = null; + PrintWriter out = ctx.getWriter(); + + ConfigurableCacheFactory ccf = ctx.getCacheFactory(); + + try + { + PersistenceTools tools = null; + + if (f_Archived) + { + out.println("Validating archived snapshot " + f_sSnapshotName); + out.flush(); + if (ccf instanceof ExtensibleConfigurableCacheFactory) + { + tools = CachePersistenceHelper.getArchiverPersistenceTools( + (ExtensibleConfigurableCacheFactory) ccf,f_sSnapshotName, f_sServiceName); + } + else + { + throw new UnsupportedOperationException("ConfigurableCacheFactory is not an instance of ExtensibleConfigurableCacheFactory"); + } + } + else + { + File fileSnapshot = null; + + if (m_sSnapshotDir == null || m_sSnapshotDir.isEmpty()) + { + // user has specified snapshot name and service name + if (f_sSnapshotName == null || f_sSnapshotName.isEmpty() || + f_sServiceName == null || f_sServiceName.isEmpty() ) + { + throw new CohQLException("You must specify either a directory or snapshot and service names."); + } + + fileSnapshot = PersistenceToolsHelper.getSnapshotDirectory(ccf, f_sSnapshotName, f_sServiceName); + m_sSnapshotDir = fileSnapshot.getAbsolutePath(); + } + else + { + // user has specified just directory + fileSnapshot = new File(m_sSnapshotDir); + } + + if (!fileSnapshot.exists() || !fileSnapshot.isDirectory() || !fileSnapshot.canExecute()) + { + throw new CohQLException("The directory '" + m_sSnapshotDir + + "' does not exist or is not readable"); + } + + out.println("Validating snapshot directory '" + m_sSnapshotDir + "'"); + out.flush(); + + tools = CachePersistenceHelper.getSnapshotPersistenceTools(fileSnapshot); + } + + tools.validate(); + + if (f_fVerbose) + { + PersistenceStatistics stats = tools.getStatistics(); + OfflinePersistenceInfo info = tools.getPersistenceInfo(); + + Map mapResults = new LinkedHashMap<>(); + + mapResults.put("Partition Count", String.valueOf(info.getPartitionCount())); + if (f_Archived) + { + mapResults.put("Archived Snapshot", "Name=" + f_sSnapshotName + + ", Service=" + f_sServiceName); + mapResults.put("Original Storage Format", info.getStorageFormat()); + } + else + { + mapResults.put("Directory", m_sSnapshotDir); + mapResults.put("Storage Format", info.getStorageFormat()); + } + + mapResults.put("Storage Version", String.valueOf(info.getStorageVersion())); + mapResults.put("Implementation Version", String.valueOf(info.getImplVersion())); + mapResults.put("Number of Partitions Present", String.valueOf(info.getGUIDs().length)); + mapResults.put("Is Complete?", String.valueOf(info.isComplete())); + mapResults.put("Is Archived Snapshot?", String.valueOf(info.isArchived())); + mapResults.put("Persistence Version", String.valueOf(info.getPersistenceVersion())); + mapResults.put("Statistics", ""); + + if (stats != null) + { + for (String sCacheName : stats) + { + StringBuilder sb = new StringBuilder(); + + sb.append("Size=" + stats.getCacheSize(sCacheName)); + sb.append(", Bytes=" + stats.getCacheBytes(sCacheName)); + sb.append(", Indexes=" + stats.getIndexCount(sCacheName)); + sb.append(", Triggers=" + stats.getTriggerCount(sCacheName)); + sb.append(", Listeners=" + stats.getListenerCount(sCacheName)); + sb.append(", Locks=" + stats.getLockCount(sCacheName)); + mapResults.put(sCacheName, sb.toString()); + } + } + + oResult = mapResults; + } + else + { + oResult = AbstractSnapshotStatement.SUCCESS; + } + + } + catch (Exception e) + { + throw PersistenceToolsHelper.ensureCohQLException(e, "Error in VALIDATE SNAPSHOT:"); + } + + FormattedMapStatementResult result = new FormattedMapStatementResult(oResult); + + if (oResult instanceof Map) + { + result.setColumnHeaders(new String[] {"Attribute", "Value"}); + } + + return result; + } + + @Override + public void showPlan(PrintWriter out) + { + } + + // ----- data members ------------------------------------------- + + /** + * The directory to validate. + */ + private String m_sSnapshotDir; + + /** + * Indicates if verbose mode is on. + */ + private final boolean f_fVerbose; + + /** + * True if an archived snapshot should be validated. + */ + private final boolean f_Archived; + + /** + * The snapshot name to validate for archived snapshots; + */ + private final String f_sSnapshotName; + + /** + * The service name to validate for archived snapshots. + */ + private final String f_sServiceName; + } + + // ----- constants ------------------------------------------------------ + + /** + * An instance of {@link ValidateSnapshotStatementBuilder}. + */ + public static final ValidateSnapshotStatementBuilder INSTANCE = new ValidateSnapshotStatementBuilder(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLBackupOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLBackupOPToken.java new file mode 100644 index 0000000000000..033ea2c23355e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLBackupOPToken.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLBackupOPToken is used for parsing and specifying the AST + * used for backing up a cache. + *

+ * Syntax: + *

+ * BACKUP CACHE 'cache-name' [TO] [FILE] 'filename' + * + * @author djl 2009.09.10 + */ +public class SQLBackupOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLBackupOPToken with the given parameters. + * + * @param id string identifier for this token + */ + public SQLBackupOPToken(String id) + { + super(id, OPToken.IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + Term termTable; + Term termFile; + OPScanner scanner = parser.getScanner(); + + if (scanner.advanceWhenMatching("cache")) + { + termTable = Terms.newTerm("from", AtomicTerm.createString(scanner.getCurrentAsStringWithAdvance())); + scanner.advanceWhenMatching("to"); + scanner.advanceWhenMatching("file"); + termFile = Terms.newTerm("file", AtomicTerm.createString(scanner.getCurrentAsStringWithAdvance())); + + return Terms.newTerm(FUNCTOR, termTable, termFile); + } + else + { + return super.nud(parser); + } + + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlBackupCacheNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLCreateCacheOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLCreateCacheOPToken.java new file mode 100644 index 0000000000000..e3e9dbdb3fcb4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLCreateCacheOPToken.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLCreateOPToken is used for parsing and specifying the AST + * used for a create cache. + *

+ * Syntax: + *

+ * (ENSURE | CREATE) CACHE 'cache-name' + * + * @author jk 2014.02.12 + */ +public class SQLCreateCacheOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLCreateCacheOPToken with the given parameters. + */ + public SQLCreateCacheOPToken() + { + super("cache"); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + Term termService = Terms.newTerm("service"); + Term termLoader = Terms.newTerm("loader"); + Term termTable = Terms.newTerm("from", AtomicTerm.createString(scanner.getCurrentAsStringWithAdvance())); + + if (scanner.advanceWhenMatching("service")) + { + termService = Terms.newTerm("service", parser.expression(0)); + + if (scanner.advanceWhenMatching("loader")) + { + termLoader = Terms.newTerm("loader", parser.expression(0)); + } + } + + return Terms.newTerm(FUNCTOR, termTable, termService, termLoader); + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlCreateCacheNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLCreateIndexOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLCreateIndexOPToken.java new file mode 100644 index 0000000000000..e6996726463af --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLCreateIndexOPToken.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLCreateOPToken is used for parsing and specifying the AST + * used for a create index query. + *

+ * Syntax: + *

+ * (ENSURE | CREATE) INDEX [ON] 'cache-name' value-extractor-list + * + * @author jk 2014.02.12 + * @since Coherence 12.2.1 + */ +public class SQLCreateIndexOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLCreateIndexOPToken with the given parameters. + */ + public SQLCreateIndexOPToken() + { + super("index"); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner s = parser.getScanner(); + + s.advanceWhenMatching("on"); + + Term table = Terms.newTerm("from", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + Term extractors = Terms.newTerm("extractor", parser.nodeList()); + + return Terms.newTerm(FUNCTOR, table, extractors); + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlCreateIndexNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLDeleteOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLDeleteOPToken.java new file mode 100644 index 0000000000000..b080b74388693 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLDeleteOPToken.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLDeleteOPToken is used for parsing and specifying the AST + * used for a delete query. + *

+ * Syntax: + *

+ * DELETE FROM 'cache-name' [[AS] alias] [WHERE conditional-expression] + * + * @author djl 2009.09.10 + */ +public class SQLDeleteOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLDeleteOPToken with the given parameters. + * + * @param id string identifier for this token + */ + public SQLDeleteOPToken(String id) + { + super(id, OPToken.IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + Term whereClause; + OPScanner s = parser.getScanner(); + + if (s.advanceWhenMatching("from")) + { + Term table = Terms.newTerm("from", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + Term alias = checkAlias(parser, "where"); + + if (s.advanceWhenMatching("where")) + { + whereClause = Terms.newTerm("whereClause", parser.expression(0)); + } + else + { + whereClause = Terms.newTerm("whereClause"); + } + + return Terms.newTerm(FUNCTOR, table, alias, whereClause); + } + else + { + return super.nud(parser); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST. + */ + public static final String FUNCTOR = "sqlDeleteNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLDropCacheOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLDropCacheOPToken.java new file mode 100644 index 0000000000000..a0716841250f9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLDropCacheOPToken.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLDropCacheOPToken is used for parsing and specifying the AST + * used for drop cache + *

+ * Syntax: + *

+ * DROP CACHE 'cache-name' + * + * @author jk 2014.02.12 + */ +public class SQLDropCacheOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLDropCacheOPToken with the given parameters. + */ + public SQLDropCacheOPToken() + { + super("cache"); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + Term table = Terms.newTerm("from", AtomicTerm.createString(scanner.getCurrentAsStringWithAdvance())); + + return Terms.newTerm(FUNCTOR, table); + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlDropCacheNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLDropIndexOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLDropIndexOPToken.java new file mode 100644 index 0000000000000..577cd1d6e2f80 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLDropIndexOPToken.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLDropIndexOPToken is used for parsing and specifying the AST + * used for drop index. + *

+ * Syntax: + *

+ * DROP INDEX [ON] 'cache-name' value-extractor-list + * + * @author jk 2014.02.12 + * @since Coherence 12.2.1 + */ +public class SQLDropIndexOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLDropIndexOPToken. + */ + public SQLDropIndexOPToken() + { + super("index"); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + + scanner.advanceWhenMatching("on"); + + Term table = Terms.newTerm("from", AtomicTerm.createString(scanner.getCurrentAsStringWithAdvance())); + Term extractors = Terms.newTerm("extractor", parser.nodeList()); + + return Terms.newTerm(FUNCTOR, table, extractors); + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST. + */ + public static final String FUNCTOR = "sqlDropIndexNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLExplainOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLExplainOPToken.java new file mode 100644 index 0000000000000..105ac49690d95 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLExplainOPToken.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +/** + * SQLExplainOPToken is used for parsing and specifying the AST + * used for an explain plan statement. + *

+ * Syntax: + *

+ * EXPLAIN PLAN FOR select stmt | update stmt | delete stmt + * + * @author tb 2011.06.05 + */ +public class SQLExplainOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLExplainOPToken with the given parameters. + * + * @param id string identifier for this token + */ + public SQLExplainOPToken(String id) + { + super(id, OPToken.IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner s = parser.getScanner(); + String sCurrent = s.getCurrentAsString(); + + if (sCurrent == null) + { + return super.nud(parser); + } + else if (sCurrent.length() == 1) + { + char cCurrent = sCurrent.charAt(0); + + if (cCurrent == '.' || cCurrent == ',' || cCurrent == '(' || cCurrent == ';') + { + return super.nud(parser); + } + } + + Term stmt; + + advanceToStmt(s); + + if (s.advanceWhenMatching("select")) + { + SQLSelectOPToken token = SQL_SELECT_OP_TOKEN; + + stmt = token.nud(parser); + } + else if (s.advanceWhenMatching("update")) + { + SQLUpdateOPToken token = SQL_UPDATE_OP_TOKEN; + + stmt = token.nud(parser); + } + else if (s.advanceWhenMatching("delete")) + { + SQLDeleteOPToken token = SQL_DELETE_OP_TOKEN; + + stmt = token.nud(parser); + } + else + { + return super.nud(parser); + } + + return new NodeTerm(getFunctor(), new Term[] {stmt}); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Advance the scanner past any tokens preceding the statement. + * + * @param s the scanner + */ + protected void advanceToStmt(OPScanner s) + { + s.advance("plan"); + s.advance("for"); + } + + /** + * Get the functor for the new term. + * + * @return the functor + */ + protected String getFunctor() + { + return FUNCTOR; + } + + // ----- constants ------------------------------------------------------ + + /** + * The select token. + */ + private static final SQLSelectOPToken SQL_SELECT_OP_TOKEN = new SQLSelectOPToken("select"); + + /** + * The update token. + */ + private static final SQLUpdateOPToken SQL_UPDATE_OP_TOKEN = new SQLUpdateOPToken("update"); + + /** + * The delete token. + */ + private static final SQLDeleteOPToken SQL_DELETE_OP_TOKEN = new SQLDeleteOPToken("delete"); + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlExplainNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLInsertOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLInsertOPToken.java new file mode 100644 index 0000000000000..3e24295bb6f21 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLInsertOPToken.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLInsertOPToken is used for parsing and specifying the AST + * used for a insert statment. + *

+ * Syntax: + *

+ * INSERT INTO 'cache-name' + * [KEY (literal | new java-constructor | static method)] + * VALUE (literal | new java-constructor | static method) + * + * @author djl 2009.09.10 + */ +public class SQLInsertOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLInsertOPToken with the given parameters. + * + * @param id string identifier for this token + * + */ + public SQLInsertOPToken(String id) + { + super(id, OPToken.IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + Term key; + Term value; + Term table; + OPScanner s = parser.getScanner(); + + if (s.advanceWhenMatching("into")) + { + table = Terms.newTerm("from", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + + if (s.advanceWhenMatching("key")) + { + key = Terms.newTerm("key", parser.expression(0)); + } + else + { + key = Terms.newTerm("key"); + } + + if (s.advanceWhenMatching("value")) + { + value = Terms.newTerm("value", parser.expression(0)); + } + else + { + value = Terms.newTerm("value"); + } + + return Terms.newTerm(FUNCTOR, table, key, value); + } + else + { + return super.nud(parser); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlInsertNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLOPToken.java new file mode 100644 index 0000000000000..fa3eab9a51cf3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLOPToken.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.base.BaseToken; + +import com.tangosol.coherence.dsltools.precedence.IdentifierOPToken; +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +/** + * SQLOPToken provides useful convenience methods for subclasses. + * + * @author djl 2010.05.04 + */ +public class SQLOPToken + extends IdentifierOPToken + + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLOPToken with the given parameters. + * + * @param id string identifier for this token + */ + public SQLOPToken(String id) + { + this(id, IDENTIFIER_NODE); + } + + /** + * Construct a new SQLOPToken with the given parameters. + * + * @param id string identifier for this token + * @param sNudASTName the ast name to use for constructing an ast + */ + public SQLOPToken(String id, String sNudASTName) + { + super(id, sNudASTName); + } + + // ----- Utility API ---------------------------------------------------- + + /** + * Check to see if there is an alias and create a Term to hold the alias + * identifier if one exists + * + * @param p The current Parser + * @param expectedNextKeyword The next keyword to expect + * @return the alias Term + */ + protected Term checkAlias(OPParser p, String expectedNextKeyword) + { + OPScanner s = p.getScanner(); + NodeTerm alias = new NodeTerm("alias"); + String s1 = s.getCurrentAsString(); + String s2 = s.peekNextAsString(); + + if (s1 != null) + { + if (s1.equalsIgnoreCase("as")) + { + if (s2 == null || s2.equalsIgnoreCase(expectedNextKeyword)) + { + throw new OPException("Unfullfilled expectation, alias not found"); + } + + alias.withChild(AtomicTerm.createString(s2)); + s.advance(); + s.advance(); + } + else if (s2 == null || s2.equalsIgnoreCase(expectedNextKeyword)) + { + alias.withChild(AtomicTerm.createString(s1)); + s.advance(); + } + } + + return alias; + } + + /** + * Check to see if there is an alias and create a Term to hold the alias + * identifier if one exists + * + * @param p The current Parser + * @param expectedNextKeywords The next keyword to expect + * @return the alias Term + */ + protected Term checkAlias(OPParser p, String... expectedNextKeywords) + { + OPScanner s = p.getScanner(); + NodeTerm alias = new NodeTerm("alias"); + String s1 = s.getCurrentAsString(); + BaseToken t2 = s.peekNext(); + String s2 = s.peekNextAsString(); + + if (s1 != null) + { + if (s1.equalsIgnoreCase("as")) + { + if (s2 == null || containsIgnoreCase(s2, expectedNextKeywords)) + { + throw new OPException("Unfullfilled expectation, alias not found"); + } + + alias.withChild(AtomicTerm.createString(s2)); + s.advance(); + s.advance(); + } + else if (s2 == null || containsIgnoreCase(s2, expectedNextKeywords)) + + { + alias.withChild(AtomicTerm.createString(s1)); + s.advance(); + } + else if (t2 == null && (s2 == null || containsIgnoreCase(s2, expectedNextKeywords))) + { + alias.withChild(AtomicTerm.createString(s1)); + s.advance(); + } + } + + return alias; + } + + private boolean containsIgnoreCase(String s, String[] list) + { + for (String test : list) + { + if (test.equalsIgnoreCase(s)) + { + return true; + } + } + + return false; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLPeekOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLPeekOPToken.java new file mode 100644 index 0000000000000..76f8cb0d50609 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLPeekOPToken.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.PeekOPToken; + +/** + * An CohQL implementation of a {@link com.tangosol.coherence.dsltools.precedence.PeekOPToken}. + * + * @author jk 2014.02.12 + * @since Coherence 12.2.1 + */ +public class SQLPeekOPToken + extends PeekOPToken + { + + /** + * Construct a SQLPeekOPToken. + * + * @param sId the id (initial keyword) for this token + * @param tokens the list of tokens based on the next keyword + */ + public SQLPeekOPToken(String sId, OPToken... tokens) + { + super(sId, IDENTIFIER_NODE, tokens); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLRestoreOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLRestoreOPToken.java new file mode 100644 index 0000000000000..1cd622d3fdf4a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLRestoreOPToken.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLRestoreOPToken is used for parsing and specifying the AST + * used for restoring a cache. + *

+ * Syntax: + *

+ * RESTORE CACHE 'cache-name' [FROM] [ FILE ] 'filename' + * + * @author djl 2009.09.10 + */ +public class SQLRestoreOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLRestoreOPToken with the given parameters. + * + * @param id string identifier for this token + * + */ + public SQLRestoreOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner s = parser.getScanner(); + + if (s.advanceWhenMatching("cache")) + { + String cacheName = s.getCurrentAsStringWithAdvance(); + + s.advanceWhenMatching("from"); + s.advanceWhenMatching("file"); + + String fileName = s.getCurrentAsStringWithAdvance(); + + return Terms.newTerm(FUNCTOR, Terms.newTerm("from", AtomicTerm.createString(cacheName)), + Terms.newTerm("file", AtomicTerm.createString(fileName))); + } + else + { + throw new OPException("Expected CACHE but found " + s.getCurrentAsString()); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlRestoreCacheNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLSelectOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLSelectOPToken.java new file mode 100644 index 0000000000000..dab01086691cf --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLSelectOPToken.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.NodeTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * SQLSelectOPToken is used for parsing and specifying the AST + * used for a select statement. + *

+ * Syntax: + *

+ * SELECT (* | alias | (properties* aggregators*) + * FROM 'cache-name' [[AS] alias] + * [WHERE conditional-expression] + * [GROUP [BY] properties+] + * + * @author djl 2009.09.10 + */ +public class SQLSelectOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLSelectOPToken with the given parameters. + * + * @param id string identifier for this token + */ + public SQLSelectOPToken(String id) + { + super(id); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner s = parser.getScanner(); + OPToken currentToken = s.getCurrent(); + String sCurrent = currentToken.getValue(); + + if (sCurrent == null) + { + return super.nud(parser); + } + else if (sCurrent.length() == 1) + { + char cCurrent = sCurrent.charAt(0); + + if (cCurrent == '.' || cCurrent == ',' || cCurrent == ';') + { + return super.nud(parser); + } + } + + Term fieldList; + Term groupBy = null; + Term whereClause = null; + Term table = null; + Term subSelectTerm = null; + Term alias = null; + Term isDistinct = Terms.newTerm("isDistinct", AtomicTerm.createString("false")); + + if (s.advanceWhenMatching("*")) + { + fieldList = Terms.newTerm("fieldList", AtomicTerm.createString("*")); + } + else + { + if (s.advanceWhenMatching("distinct")) + { + isDistinct = Terms.newTerm("isDistinct", AtomicTerm.createString("true")); + } + + fieldList = Terms.newTerm("fieldList", parser.nodeList("from")); + } + + Map subSelectMap = new LinkedHashMap<>(); + + if (s.advanceWhenMatching("from")) + { + parseSubSelects(parser, s, subSelectMap); + + Term cacheName = parser.expression(OPToken.PRECEDENCE_ASSIGNMENT); + + if ("identifier".equalsIgnoreCase(cacheName.getFunctor()) + || "literal".equalsIgnoreCase(cacheName.getFunctor())) + { + cacheName = AtomicTerm.createString(((AtomicTerm) cacheName.termAt(1)).getValue()); + } + + table = Terms.newTerm("from", cacheName); + alias = checkAlias(parser, ",", "where", "group"); + + s.advanceWhenMatching(","); + parseSubSelects(parser, s, subSelectMap); + + if (subSelectMap.isEmpty()) + { + subSelectTerm = Terms.newTerm("subQueries"); + } + else + { + Term[] subSelects = new Term[subSelectMap.size()]; + int subCount = 0; + + for (Map.Entry entry : subSelectMap.entrySet()) + { + subSelects[subCount++] = Terms.newTerm("subQuery", Terms.newTerm("alias", entry.getKey()), + entry.getValue()); + } + + subSelectTerm = Terms.newTerm("subQueries", subSelects); + } + + if (s.advanceWhenMatching("where")) + { + whereClause = Terms.newTerm("whereClause", parser.expression(0)); + } + else + { + whereClause = Terms.newTerm("whereClause"); + } + + if (s.advanceWhenMatching("group")) + { + s.advanceWhenMatching("by"); + groupBy = Terms.newTerm("groupBy", parser.nodeList()); + } + else + { + groupBy = Terms.newTerm("groupBy"); + } + } + + return new NodeTerm(FUNCTOR, new Term[] + { + isDistinct, fieldList, table, alias, subSelectTerm, whereClause, groupBy + }); + } + + private void parseSubSelects(OPParser p, OPScanner s, Map subSelectMap) + { + while (s.getCurrent().getId().equals("(")) + { + Term subSelect = p.expression(OPToken.PRECEDENCE_PARENTHESES); + String subAlias = s.getCurrentAsStringWithAdvance(); + + subSelectMap.put(AtomicTerm.createString(subAlias), subSelect); + s.advanceWhenMatching(","); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlSelectNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLSourceOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLSourceOPToken.java new file mode 100644 index 0000000000000..8cdb4a60c6f8b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLSourceOPToken.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLSourceOPToken is used for parsing and specifying the AST + * used for sourcing (including) a file. + *

+ * Syntax: + *

+ * SOURCE FROM [FILE] 'filename' + * + * @author djl 2009.09.10 + */ +public class SQLSourceOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLSourceOPToken with the given parameters. + * + * @param id string identifier for this token + * + */ + public SQLSourceOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + Term file; + OPScanner s = parser.getScanner(); + boolean flag = s.advanceWhenMatching("from"); + + if (getId().equals("@") || flag) + { + s.advanceWhenMatching("file"); + file = Terms.newTerm("file", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + + return Terms.newTerm(FUNCTOR, file); + } + else + { + return super.nud(parser); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlSourceNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLTraceOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLTraceOPToken.java new file mode 100644 index 0000000000000..49d6cd73127f4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLTraceOPToken.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +/** + * SQLTraceOPToken is used for parsing and specifying the AST + * used for a trace statement. + *

+ * Syntax: + *

+ * TRACE select stmt | update stmt | delete stmt + * + * @author tb 2011.06.05 + */ +public class SQLTraceOPToken + extends SQLExplainOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLTraceOPToken with the given parameters. + * + * @param id string identifier for this token + */ + public SQLTraceOPToken(String id) + { + super(id); + } + + // ----- helper methods ------------------------------------------------- + + protected void advanceToStmt(OPScanner s) + { + } + + protected String getFunctor() + { + return FUNCTOR; + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlTraceNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLTruncateCacheOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLTruncateCacheOPToken.java new file mode 100644 index 0000000000000..df728cddb1255 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLTruncateCacheOPToken.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLTruncateCacheOPToken is used for parsing and specifying the AST + * used for truncate cache. + *

+ * Syntax: + *

+ * TRUNCATE CACHE 'cache-name' + * + * @author bbc 2015.09.01 + */ +public class SQLTruncateCacheOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLTruncateCacheOPToken with the given parameters. + * + * @param id the id + */ + public SQLTruncateCacheOPToken(String id) + { + super(id, OPToken.IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + OPScanner scanner = parser.getScanner(); + if (scanner.advanceWhenMatching("cache")) + { + Term table = Terms.newTerm("from", AtomicTerm.createString(scanner.getCurrentAsStringWithAdvance())); + + return Terms.newTerm(FUNCTOR, table); + } + else + { + return super.nud(parser); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlTruncateCacheNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLUpdateOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLUpdateOPToken.java new file mode 100644 index 0000000000000..c0d475b596eea --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/SQLUpdateOPToken.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLUpdateOPToken is used for parsing and specifying the AST + * used for an update statement. + *

+ * Syntax: + *

+ * UPDATE 'cache-name' [[AS] alias] + * SET update-statement {, update-statement} + * [WHERE conditional-expression] + * + * @author djl 2009.09.10 + */ +public class SQLUpdateOPToken + extends SQLOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SQLUpdateOPToken with the given parameters. + * + * @param id string identifier for this token + */ + public SQLUpdateOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser parser) + { + Term setlist; + Term where; + Term alias; + OPScanner s = parser.getScanner(); + String s1 = s.getCurrentAsString(); + String[] s2and3 = s.peekNext2AsStrings(); + + if (s2and3 == null + ||!(s2and3[0].equalsIgnoreCase("set") || s2and3[0].equalsIgnoreCase("as") + || s2and3[1].equalsIgnoreCase("set"))) + { + return super.nud(parser); + } + + Term table = Terms.newTerm("from", AtomicTerm.createString(s1)); + + s.next(); // eat the cache name + + if (s2and3[0].equalsIgnoreCase("as")) + { + alias = Terms.newTerm("alias", AtomicTerm.createString(s2and3[1])); + s.next(); + s.next(); + } + else if (s2and3[1].equalsIgnoreCase("set")) + { + alias = Terms.newTerm("alias", AtomicTerm.createString(s2and3[0])); + s.next(); + } + else + { + // no alias and thank goodness for a double peek! + alias = Terms.newTerm("alias"); + } + + s.advanceWhenMatching("set"); + setlist = Terms.newTerm("setList", parser.nodeList("where", true)); + + if (s.advanceWhenMatching("where")) + { + where = Terms.newTerm("whereClause", parser.expression(0)); + } + else + { + where = Terms.newTerm("whereClause"); + } + + return Terms.newTerm(FUNCTOR, table, setlist, alias, where); + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlUpdateNode"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/package.html b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/package.html new file mode 100644 index 0000000000000..8c017dd95300b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/package.html @@ -0,0 +1,6 @@ + +This package contains implementations of {@link com.tangosol.coherence.dsltools.precedence.OPToken} specifically +for parsing CohQL statements. + +@serial exclude + \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/AbstractSQLSnapshotOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/AbstractSQLSnapshotOPToken.java new file mode 100644 index 0000000000000..92c0eaf385cfd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/AbstractSQLSnapshotOPToken.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * An abstract implementation of a snapshot operation which can + * be extended to support different persistence operations. + * + * @author tam 2014.01.09 + * @since 12.2.1 + */ +public abstract class AbstractSQLSnapshotOPToken + extends SQLOPToken + { + // ---- constructors ----------------------------------------------------- + + /** + * Construct a new AbstractSQLSnapshotOPToken. + * + * @param id the string representing the command + */ + public AbstractSQLSnapshotOPToken(String id) + { + super(id); + } + + /** + * Construct a new AbstractSQLSnapshotOPToken. + * + * @param id the string representing the command + * @param sNudASTName the ast name to use for constructing an ast + */ + public AbstractSQLSnapshotOPToken(String id, String sNudASTName) + { + super(id, sNudASTName); + } + + // ----- helpers -------------------------------------------------- + + /** + * Process the commands for the given operation and return a + * valid {@link Term} for the command. + * + * @param s {@link OPScanner} to read commands from + * @param sOperation the operation being called + * @param sFunctor the current functor + * + * @return the processed {@link Term} + */ + protected Term process(OPScanner s, String sOperation, String sFunctor) + { + if (s.isEndOfStatement()) + { + throw new CohQLException("Snapshot name required for " + sOperation + " snapshot"); + } + + Term termSnapshot = Terms.newTerm("snapshotname", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + + if (s.isEndOfStatement()) + { + throw new OPException("Service name required for " + sOperation + " snapshot"); + } + + Term termService = Terms.newTerm("service", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + + return Terms.newTerm(sFunctor, termSnapshot, termService); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLArchiveSnapshotOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLArchiveSnapshotOPToken.java new file mode 100644 index 0000000000000..33b837013b8a7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLArchiveSnapshotOPToken.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.Term; + +/** + * SQLArchiveSnapshotOPToken is used for parsing and specifying the AST + * used for archiving snapshots. + *

+ * Syntax: + *

+ * ARCHIVE SNAPSHOT 'snapshot-name' 'service' + * + * @author tam 2014.08.04 + * @since 12.2.1 + */ +public class SQLArchiveSnapshotOPToken + extends AbstractSQLSnapshotOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLArchiveSnapshotOPToken. + * + * @param id the string representing the command + */ + public SQLArchiveSnapshotOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + + if (s.advanceWhenMatching("snapshot")) + { + return process(s, "archive", FUNCTOR); + } + else + { + throw new OPException("Expected SNAPSHOT but found " + s.getCurrentAsString()); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlArchiveSnapshot"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLCreateSnapshotOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLCreateSnapshotOPToken.java new file mode 100644 index 0000000000000..f458568a62b77 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLCreateSnapshotOPToken.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.termtrees.Term; + +/** + * SQLCreateSnapshotOPToken is used for parsing and specifying the AST + * used for creating snapshots. + *

+ * Syntax: + *

+ * CREATE SNAPSHOT 'snapshot-name' 'service' + * + * @author tam 2014.08.04 + * @since 12.2.1 + */ +public class SQLCreateSnapshotOPToken + extends AbstractSQLSnapshotOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLCreateSnapshotOPToken. + * + */ + public SQLCreateSnapshotOPToken() + { + super("snapshot"); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + return process(p.getScanner(), "create", FUNCTOR); + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlCreateSnapshot"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLForceRecoveryOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLForceRecoveryOPToken.java new file mode 100644 index 0000000000000..7c254612e0ae1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLForceRecoveryOPToken.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLForceRecoveryOPToken is used for parsing and specifying the AST + * used for forcing recovery. + *

+ * Syntax: + *

+ * FORCE RECOVERY 'SERVICE-NAME' + * + * @author tam 2016.03.15 + * @since 12.2.1.1 + */ +public class SQLForceRecoveryOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLForceRecoveryOPToken. + * + * @param id the string representing the command + */ + public SQLForceRecoveryOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + + if (s.advanceWhenMatching("recovery")) + { + Term termService = Terms.newTerm("service", AtomicTerm.createString( + s.getCurrentAsStringWithAdvance())); + + return Terms.newTerm(FUNCTOR, termService); + } + else + { + throw new OPException("Expected RECOVERY but found " + s.getCurrentAsString()); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlForceRecovery"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListArchivedSnapshotsOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListArchivedSnapshotsOPToken.java new file mode 100644 index 0000000000000..e97fba55bc8d3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListArchivedSnapshotsOPToken.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLListArchivedSnapshotsOPToken is used for parsing and specifying the AST + * used for listing archived snapshots. + *

+ * Syntax: + *

+ * LIST ARCHIVED SNAPSHOTS 'service' + * + * @author tam 2014.02.25 + * @since 12.2.1 + */ +public class SQLListArchivedSnapshotsOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLListArchivedSnapshotsOPToken. + */ + public SQLListArchivedSnapshotsOPToken() + { + super("archived"); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + + if (s.advanceWhenMatching("snapshots")) + { + Term termService = s.isEndOfStatement() + ? AtomicTerm.createNull() + : Terms.newTerm("service", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + Term termArchive = Terms.newTerm("archived", AtomicTerm.createString("true")); + + return Terms.newTerm(FUNCTOR, termService, termArchive); + } + else + { + return super.nud(p); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST. + * Note: this intentionaly calls list snapshots. + */ + public static final String FUNCTOR = "sqlListSnapshots"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListArchiverOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListArchiverOPToken.java new file mode 100644 index 0000000000000..fda6f6c5a0256 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListArchiverOPToken.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLListArchiverOPToken is used for parsing and specifying the AST + * used for showing the archiver implementation for a service. + *

+ * Syntax: + *

+ * LIST ARCHIVER 'service' + * + * @author tam 2014.10.20 + * @since 12.2.1 + */ +public class SQLListArchiverOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLListArchiverOPToken. + * + */ + public SQLListArchiverOPToken() + { + super("archiver"); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + + Term termService = s.isEndOfStatement() + ? AtomicTerm.createNull() + : Terms.newTerm("service", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + + return Terms.newTerm(FUNCTOR, termService); + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlListArchiver"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListServicesOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListServicesOPToken.java new file mode 100644 index 0000000000000..8ed42c6d3a605 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListServicesOPToken.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLListServicesOPToken is used for parsing and specifying the AST + * used for listing services and their persistence mode. + *

+ * Syntax: + *

+ * LIST SERVICES [MANAGER] + * + * @author tam 2014.02.25 + * @since 12.2.1 + */ +public class SQLListServicesOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLListServicesOPToken. + * + */ + public SQLListServicesOPToken() + { + super("services"); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + Term termVerbose = Terms.newTerm("environment", + AtomicTerm.createString(s.advanceWhenMatching("environment") ? "true" : "false")); + + return Terms.newTerm(FUNCTOR, termVerbose); + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlListServices"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListSnapshotsOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListSnapshotsOPToken.java new file mode 100644 index 0000000000000..f6591636ed008 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLListSnapshotsOPToken.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLConnectOPToken is used for parsing and specifying the AST + * used for listing snapshots. + *

+ * Syntax: + *

+ * LIST [ARCHIVED] SNAPSHOTS ['service'] + * + * @author tam 2014.02.25 + * @since 12.2.1 + */ +public class SQLListSnapshotsOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLListServicesOPToken. + * + */ + public SQLListSnapshotsOPToken() + { + super("snapshots"); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + + Term termService = s.isEndOfStatement() + ? AtomicTerm.createNull() + : Terms.newTerm("service", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + Term termArchive = Terms.newTerm("archived", AtomicTerm.createString("false")); + + return Terms.newTerm(FUNCTOR, termService, termArchive); + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlListSnapshots"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLRecoverSnapshotOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLRecoverSnapshotOPToken.java new file mode 100644 index 0000000000000..4d2fcd9f2de74 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLRecoverSnapshotOPToken.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.Term; + +/** + * SQLRecoverSnapshotOPToken is used for parsing and specifying the AST + * used for creating snapshots. + *

+ * Syntax: + *

+ * RECOVER SNAPSHOT 'snapshot-name' 'service' + * + * @author tam 2014.08.04 + * @since 12.2.1 + */ +public class SQLRecoverSnapshotOPToken + extends AbstractSQLSnapshotOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLRecoverSnapshotOPToken. + * + * @param id the string representing the command + */ + public SQLRecoverSnapshotOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + + if (s.advanceWhenMatching("snapshot")) + { + return process(s, "recover", FUNCTOR); + } + else + { + throw new OPException("Expected SNAPSHOT but found " + s.getCurrentAsString()); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlRecoverSnapshot"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLRemoveSnapshotOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLRemoveSnapshotOPToken.java new file mode 100644 index 0000000000000..472d4e95ef2ef --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLRemoveSnapshotOPToken.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.CohQLException; +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLRemoveSnapshotOPToken is used for parsing and specifying the AST + * used for creating snapshots. + *

+ * Syntax: + *

+ * REMOVE [ARCHIVED] SNAPSHOT 'snapshot-name' 'service' + * + * @author tam 2014.08.04 + */ +public class SQLRemoveSnapshotOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLListServicesOPToken. + * + * @param id the string representing the command + */ + public SQLRemoveSnapshotOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + boolean fArchived = false; + + if (s.advanceWhenMatching("archived")) + { + fArchived = true; + } + + if (s.advanceWhenMatching("snapshot")) + { + if (s.isEndOfStatement()) + { + throw new CohQLException("Snapshot name required for REMOVE SNAPSHOT"); + } + + Term termSnapshot = Terms.newTerm("snapshotname", + AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + + if (s.isEndOfStatement()) + { + throw new OPException("Service name required for REMOVE SNAPSHOT"); + } + + Term termService = Terms.newTerm("service", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + Term termArchive = Terms.newTerm("archived", AtomicTerm.createString(fArchived ? "true" : "false")); + + return Terms.newTerm(FUNCTOR, termSnapshot, termService, termArchive); + } + else + { + throw new OPException("Expected SNAPSHOT but found " + s.getCurrentAsString()); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlRemoveSnapshot"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLResumeServiceOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLResumeServiceOPToken.java new file mode 100644 index 0000000000000..f8356f29083ae --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLResumeServiceOPToken.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLResumeServiceOPToken is used for parsing and specifying the AST + * used for resuming services. + *

+ * Syntax: + *

+ * RESUME SERVICE 'service' + * + * @author tam 2014.08.05 + * @since 12.2.1 + */ +public class SQLResumeServiceOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLResumeServiceOPToken. + * + * @param id the string representing the command + */ + public SQLResumeServiceOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + + if (s.advanceWhenMatching("service")) + { + Term termService = Terms.newTerm("service", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + + return Terms.newTerm(FUNCTOR, termService); + } + else + { + throw new OPException("Expected SERVICE but found " + s.getCurrentAsString()); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlResumeService"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLRetrieveSnapshotOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLRetrieveSnapshotOPToken.java new file mode 100644 index 0000000000000..4649b1e9f29ac --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLRetrieveSnapshotOPToken.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLRetrieveSnapshotOPToken is used for parsing and specifying the AST + * used for retrieving snapshots. + *

+ * Syntax: + *

+ * RETRIEVE ARCHIVED SNAPSHOT 'snapshot-name' 'service' [OVERWRITE] + * + * @author tam 2014.08.04 + * @since 12.2.1 + */ +public class SQLRetrieveSnapshotOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLRetrieveSnapshotOPToken. + * + * @param id the string representing the command + */ + public SQLRetrieveSnapshotOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + final String sCommand = "RETRIEVE ARCHIVED SNAPSHOT"; + + if (s.isEndOfStatement()) + { + throw new OPException("Please include ARCHIVED keyword for " + sCommand); + } + + if (s.advanceWhenMatching("archived")) + { + if (s.advanceWhenMatching("snapshot")) + { + Term termSnapshotName = PersistenceToolsHelper.getNextTerm(s, "snapshotname", "Snapshot Name", + sCommand); + Term termServiceName = PersistenceToolsHelper.getNextTerm(s, "service", "Service Name", sCommand); + Term termOverwrite = Terms.newTerm("overwrite", + AtomicTerm.createString(s.advanceWhenMatching("overwrite") + ? "true" : "false")); + + return Terms.newTerm(FUNCTOR, termSnapshotName, termServiceName, termOverwrite); + } + else + { + throw new OPException("Expected SNAPSHOT but found " + s.getCurrentAsString()); + } + } + else + { + return super.nud(p); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlRetrieveSnapshot"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLSuspendServiceOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLSuspendServiceOPToken.java new file mode 100644 index 0000000000000..fa5d1fd075b89 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLSuspendServiceOPToken.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLSuspendServiceOPToken is used for parsing and specifying the AST + * used for suspending services. + *

+ * Syntax: + *

+ * SUSPEND SERVICE 'SERVICE-NAME' + * + * @author tam 2014.09.01 + * @since 12.2.1 + */ +public class SQLSuspendServiceOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLListServicesOPToken. + * + * @param id the string representing the command + */ + public SQLSuspendServiceOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + + if (s.advanceWhenMatching("service")) + { + Term termService = Terms.newTerm("service", AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + + return Terms.newTerm(FUNCTOR, termService); + } + else + { + throw new OPException("Expected SERVICE but found " + s.getCurrentAsString()); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlSuspendService"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLValidateSnapshotOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLValidateSnapshotOPToken.java new file mode 100644 index 0000000000000..8e52f5e5803ff --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dslquery/token/persistence/SQLValidateSnapshotOPToken.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dslquery.token.persistence; + +import com.tangosol.coherence.dslquery.internal.PersistenceToolsHelper; +import com.tangosol.coherence.dslquery.token.SQLOPToken; +import com.tangosol.coherence.dsltools.precedence.OPException; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.precedence.OPScanner; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + +/** + * SQLValidateSnapshotOPToken is used for parsing and specifying the AST + * used for creating snapshots. + *

+ * Syntax: + *

+ * VALIDATE [ARCHIVED] SNAPSHOT '/path/to/snapshot' [VERBOSE] + *
or
+ * VALIDATE SNAPSHOT 'snapshot-name' 'service-name' [VERBOSE] + *
+ * or + *
+ * VALIDATE ARCHIVED SNAPSHOT 'snapshot-name' 'service-name' [VERBOSE] + * + * @author tam 2014.08.06 + * @since 12.2.1 + */ +public class SQLValidateSnapshotOPToken + extends SQLOPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new SQLValidateSnapshotOPToken. + * + * @param id the string representing the command + */ + public SQLValidateSnapshotOPToken(String id) + { + super(id, IDENTIFIER_NODE); + } + + // ----- Operator Precedence API ---------------------------------------- + + @Override + public Term nud(OPParser p) + { + OPScanner s = p.getScanner(); + boolean fArchive = s.advanceWhenMatching("archived"); + Term termVerbose; + final String sCommand = "VALIDATE ARCHIVED SNAPSHOT"; + + if (s.advanceWhenMatching("snapshot")) + { + Term termType = Terms.newTerm("type", AtomicTerm.createString(fArchive ? "archived" : "snapshot")); + + if (fArchive) + { + // should have snapshot-name, cluster-name, service-name & archiver-name + Term termSnapshotName = PersistenceToolsHelper.getNextTerm(s, "snapshotname", "Snapshot Name", sCommand); + Term termServiceName = PersistenceToolsHelper.getNextTerm(s, "servicename", "Service Name", sCommand); + + termVerbose = Terms.newTerm("verbose", + AtomicTerm.createString(s.advanceWhenMatching("verbose") + ? "true" : "false")); + + return Terms.newTerm(FUNCTOR, new Term[] + { + termSnapshotName, termServiceName, termVerbose, termType + }); + } + else + { + // two options here: + // 1. user specifies snapshot-name and service name or + // 2. user specifies directory + // determine this by the number of tokens left + + String sNext = s.peekNextAsString(); + if (sNext == null || (sNext != null && "VERBOSE".equals(sNext.toUpperCase()))) + { + // must be directory only + Term termSnapshotDir = Terms.newTerm("snapshotdirectory", + AtomicTerm.createString(s.getCurrentAsStringWithAdvance())); + + termVerbose = Terms.newTerm("verbose", + AtomicTerm.createString(s.advanceWhenMatching("verbose") ? "true" : "false")); + + return Terms.newTerm(FUNCTOR, termSnapshotDir, termVerbose, termType); + } + else + { + // should have snapshot-name, service-name + Term termSnapshotName = PersistenceToolsHelper.getNextTerm(s, "snapshotname", "Snapshot Name", sCommand); + Term termServiceName = PersistenceToolsHelper.getNextTerm(s, "servicename", "Service Name", sCommand); + + termVerbose = Terms.newTerm("verbose", + AtomicTerm.createString(s.advanceWhenMatching("verbose") ? "true" : "false")); + + return Terms.newTerm(FUNCTOR, termSnapshotName, termServiceName, termVerbose, termType); + } + } + } + else + { + throw new OPException("Expected SNAPSHOT but found " + s.getCurrentAsString()); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The functor name used to represent this node in an AST + */ + public static final String FUNCTOR = "sqlValidateSnapshot"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseToken.java new file mode 100644 index 0000000000000..3d3b67ea84ab4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseToken.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* BaseToken is the abstract base class for all tokens processed by the low +* level BaseTokenScanner. The resulting hierarchy represents very simple +* syntactic elements. +* +* @author djl 2009.03.14 +*/ +public abstract class BaseToken + { + // ----- BaseToken interface -------------------------------------------- + + /** + * Answer whether this token is a leaf token. + * + * @return the answer to the question "is this token a leaf?" + */ + public abstract boolean isLeaf(); + + /** + * Answer whether this token is a compound token. + * + * @return the answer to the question "is this token compound?" + */ + public abstract boolean isCompound(); + + /** + * Answer whether this token represents a literal. + * + * @return the answer to the question "is this token a literal?" + */ + public boolean isLiteral() + { + return false; + } + + /** + * Answer whether this token represents a know operator. + * + * @return the answer to the question "is this token an operator?" + */ + public boolean isOperator() + { + return false; + } + + /** + * Answer whether this token represents an identifier. + * + * @return the answer to the question "is this token an identifier?" + */ + public boolean isIdentifier() + { + return false; + } + + /** + * Answer whether this token represents a nested collection of tokens. + * + * @return the answer to the question "is this token a nesting of + * tokens?" + */ + public boolean isNest() + { + return false; + } + + /** + * Answer whether this token represents a punctuation character. + * + * @return the answer to the question "is this token a punctuation + * character?" + */ + public boolean isPunctuation() + { + return false; + } + + /** + * Answer whether this token matches the given string. By default case + * matters. + * + * @param s the String to match agains + * + * @return the answer to the question "does this token match a given + * string? + */ + public boolean match(String s) + { + return match(s, false); + } + + /** + * Answer whether this token matches the given string. + * + * @param s the String to match agains + * @param fIgnoreCaseFlag the flag that controls if case matters + * + * @return the answer to the question "does this token match a given + * string? + */ + public abstract boolean match(String s, boolean fIgnoreCaseFlag); + + /** + * Return the simple class name for this instance. + * + * @return the class name without the package path + */ + public String getSimpleName() + { + String s = getClass().getName(); + int i = s.lastIndexOf('.'); + + return s.substring(i + 1); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseTokenScanner.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseTokenScanner.java new file mode 100644 index 0000000000000..0eb8620a04aba --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseTokenScanner.java @@ -0,0 +1,737 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +import java.util.ArrayList; +import java.util.List; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + + +/** +* BaseTokenScanner gives clients a streaming api that returns a next +* BaseToken by processing either a java.lang.String or a java.io.Reader. +* Clients may process the the underlying Reader or String all at one time by +* using scan() which will a BaseToken that is typically return a +* SequenceBaseToken. This all at once conversion from Chars to Tokens is +* standard for very low level tokenizers. BaseTokenScanner also processes +* nested tokens (things between (..), {..}, etc.) into a sequence of token +* represented by a composit token. Nested tokenizing relieves a client +* parser of bracketing concerns. This nest processing comes from the Dylan +* tokenizer. +* +* @author djl 2009.03.02 +*/ +public class BaseTokenScanner + { + // ----- constructors --------------------------------------------------- + + + /** + * Construct a new BaseTokenScanner with the given String. + * + * @param s the string to be tokenized + */ + public BaseTokenScanner(String s) + { + this(new StringReader(s)); + } + + /** + * Construct a new BaseTokenScanner with the given Reader. + * + * @param reader the Reader that is the source of chars to be tokenized + */ + public BaseTokenScanner(Reader reader) + { + this.m_reader = reader; + } + + + // ----- Public BaseTokenScanner API ------------------------------------ + + + /** + * Tokenize the entire expression at once. + * + * @return a BaseToken, typically a SequenceBaseToken + */ + public BaseToken scan() + { + List listTokens = new ArrayList(); + + try + { + reset(); + while (!isEnd()) + { + BaseToken token = next(); + if (token != null) + { + listTokens.add(token); + } + } + return new SequenceBaseToken((BaseToken[]) + listTokens.toArray(new BaseToken[listTokens.size()])); + } + finally + { + try + { + m_reader.close(); + } + catch (IOException e) + { + } + } + } + + /** + * Reset the receiver so that tokenizing may begin again. + */ + public void reset() + { + m_fIsEnd = true; + m_iPos = 0; + m_chCurrent = (char) 0; + + try + { + int ch = m_reader.read(); + if (ch != -1) + { + m_chCurrent = (char) ch; + m_fIsEnd = false; + } + } + catch (IOException e) + { + } + } + + /** + * Answer the next token from the underlying reader. + * + * @return the next token + */ + public BaseToken next() + { + try + { + resetTokenString(); + skipWhiteSpace(); + if (isEnd()) + { + return null; + } + notePos(); + char ch = getCurrentChar(); + if (isPunctuation(ch)) + { + takeCurrentCharAndAdvance(); + return new PunctuationBaseToken(tokenString()); + } + else if (isNest(ch)) + { + advance(); + return scanNest(ch); + } + else + { + BaseToken oToken = scanLiteral(); + if (oToken != null) + { + return oToken; + } + oToken = scanIdentifier(); + if (oToken != null) + { + return oToken; + } + + if (scanOperator()) + { + return new OperatorBaseToken(tokenString()); + } + else + { + throw new BaseTokenScannerException( + "Unclassifiable char '"+ + ch + "'"+ " : " + Character.getNumericValue(ch) + + " at line " + m_lineNumber + " offset "+ m_coffset); + } + } + } + catch (IndexOutOfBoundsException ex) + { + throw new BaseTokenScannerException( + "Indexing problem " + ex.getMessage()); + } + } + + /** + * Skip over any whitespace characters. + */ + public void skipWhiteSpace() + { + while (!isEnd() && Character.isWhitespace(getCurrentChar())) + { + advance(); + } + } + + /** + * Test whether the receiver has reached the end of the underlying Reader + * + * @return the boolean result + */ + public boolean isEnd() + { + return m_fIsEnd; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Set the String used to determine punctuation characters. + * + * @param s the String of characters to use as punctuation + */ + public void setPunctuation(String s) + { + m_sPunctuation = s; + } + + /** + * Set the Strings used as nesting characters. + * + * @param sNests the chars that start nesting + * @param sUnnests the chars that end nesting + */ + public void setNesting(String sNests, String sUnnests) + { + m_sNests = sNests; + m_sUnnests = sUnnests; + } + + + // ----- Internal Worker Methods ---------------------------------------- + + /** + * Test if the given char is a punctuation character. + * + * @param ch the char to test + * + * @return the boolean result of punctuation testing + */ + protected boolean isPunctuation(char ch) + { + return m_sPunctuation.indexOf(ch) >= 0; + } + + /** + * Test if the given char is a character that starts nesting/ + * + * @param ch the char to test + * + * @return the boolean result of nest testing + */ + protected boolean isNest(char ch) + { + return m_sNests.indexOf(ch) >= 0; + } + + + /** + * Tokenize the characters between the beginning nest chararcer + * and the character that ends the nest. + * + * @param ch the character that begins nesting + * + * @return a NestedBaseTokens that holds the nested tokens + * + * @throws BaseTokenScannerException if we reach the end of stream + * before the matching end of nest character is reached + */ + protected BaseToken scanNest(char ch) + { + ArrayList otoks = new ArrayList(); + char unnestChar = m_sUnnests.charAt(m_sNests.indexOf(ch)); + skipWhiteSpace(); + while (getCurrentChar() != unnestChar) + { + if (isEnd()) + { + throw new BaseTokenScannerException( + "Unexpected end of stream while looking for a "+ + unnestChar); + } + BaseToken oToken = next(); + if (oToken != null) + { + otoks.add(oToken); + } + skipWhiteSpace(); + } + advance(); + BaseToken[] aoTok = new BaseToken[otoks.size()]; + for (int i = 0, c = aoTok.length; i < c; ++i) + { + aoTok[i] = (BaseToken) otoks.get(i); + } + return new NestedBaseTokens(ch, unnestChar, aoTok); + } + + /** + * Attemt to tokenize a literal. + * + * @return a LiteralBaseToken if one is found otherwise return null + */ + protected BaseToken scanLiteral() + { + String st; + char ch = getCurrentChar(); + + if (ch == '"' || ch == '\'') + { + advance(); + notePos(); + while (getCurrentChar() != ch) + { + if (isEnd()) + { + throw new BaseTokenScannerException( + "Unexpected end of input while processing string literal"+ + " at line " + m_lineNumber + " offset "+ m_coffset); + } + else + { + takeCurrentCharAndAdvance(); + } + } + st = tokenString(); + if (!isEnd()) + { + advance(); + } + return LiteralBaseToken.createString(st); + } + else if (Character.isDigit(ch)) + { + notePos(); + if (!isEnd()) + { + takeCurrentCharAndAdvance(); + } + + while (!isEnd() && Character.isDigit(getCurrentChar())) + { + takeCurrentCharAndAdvance(); + } + if (isEnd()) + { + return LiteralBaseToken.createInteger(tokenString()); + } + if (getCurrentChar() == '.') + { + return literalFloat(); + } + else + { + st = tokenString(); + if (!isEnd()) + { + char cc = getCurrentChar(); + if (cc == 'l' || cc == 'L') + { + advance(); + return LiteralBaseToken.createLong(st); + } + else if (cc == 's' || cc == 'S') + { + advance(); + return LiteralBaseToken.createShort(st); + } + else if (cc == 'd' || cc == 'D') + { + advance(); + return LiteralBaseToken.createDouble(st+".0"); + } + else if (cc == 'f' || cc == 'F') + { + advance(); + return LiteralBaseToken.createFloat(st+".0"); + } + } + return LiteralBaseToken.createInteger(st); + } + } + return null; + } + + /** + * A floating point literal has been detected so create. + * + * @return the literal representation of a floating point number + */ + protected LiteralBaseToken literalFloat() + { + String st; + char ch; + takeCurrentCharAndAdvance(); + if (!isEnd() && Character.isDigit(getCurrentChar())) + { + takeCurrentCharAndAdvance(); + } + else + { + throw floatingPointFormatError(); + } + while (!isEnd() && Character.isDigit(getCurrentChar())) + { + takeCurrentCharAndAdvance(); + } + ch = getCurrentChar(); + if ( ch == 'E' || ch == 'e') + { + takeCurrentCharAndAdvance(); + if (!isEnd() && getCurrentChar() == '-') + { + takeCurrentCharAndAdvance(); + if (!isEnd() && Character.isDigit(getCurrentChar())) + { + takeCurrentCharAndAdvance(); + } + else + { + throw floatingPointFormatError(); + } + } + else if (!isEnd() && Character.isDigit(getCurrentChar())) + { + takeCurrentCharAndAdvance(); + } + else + { + throw floatingPointFormatError(); + } + while (!isEnd() && Character.isDigit(getCurrentChar())) + { + takeCurrentCharAndAdvance(); + } + } + st = tokenString(); + if (!isEnd()) + { + char cc = getCurrentChar(); + if (cc == 'd' || cc == 'D') + { + advance(); + return LiteralBaseToken.createDouble(st); + } + else if (cc == 'f' || cc == 'F') + { + advance(); + return LiteralBaseToken.createFloat(st); + } + } + return LiteralBaseToken.createDouble(st); + } + + /** + * Attemt to tokenize an Identifier. + * + * @return an IdentifierBaseToken if one is found otherwise return null + */ + protected BaseToken scanIdentifier() + { + if (Character.isJavaIdentifierStart(getCurrentChar())) + { + takeCurrentCharAndAdvance(); + while (!isEnd() && Character.isJavaIdentifierPart(getCurrentChar())) + { + takeCurrentCharAndAdvance(); + } + return new IdentifierBaseToken(tokenString()); + + } + else + { + return null; + } + } + + /** + * A problem has been detected in a floating point number. Signal + * an error. + * + * @return the RuntimeException for float format errors + */ + protected RuntimeException floatingPointFormatError() + { + return new BaseTokenScannerException( + "Invalid floating point format "+ tokenString() + + " at line " + m_lineNumber + + " offset " + m_coffset); + } + + /** + * Attemt to tokenize an Operator. + * + * @return an OperatorBaseToken if one is found otherwise return null + */ + protected boolean scanOperator() + { + // ToDo This could be much less with some ascii tests + char ch = getCurrentChar(); + switch (ch) + { + case '@': + case '?': + case ';': + case '+': + case '-': + takeCurrentCharAndAdvance(); + return true; + + case '*': + takeCurrentCharAndAdvance(); + + if (ch == getCurrentChar()) + { + takeCurrentCharAndAdvance(); + } + return true; + + case '/': + takeCurrentCharAndAdvance(); + return true; + + case '^': + case '&': + case '|': + takeCurrentCharAndAdvance(); + if (ch == getCurrentChar()) + { + takeCurrentCharAndAdvance(); + } + return true; + + case '<': + takeCurrentCharAndAdvance(); + if ('=' == getCurrentChar() || '>' == getCurrentChar()) + { + takeCurrentCharAndAdvance(); + } + return true; + + + case ':': + takeCurrentCharAndAdvance(); + if ('=' == getCurrentChar()) + { + takeCurrentCharAndAdvance(); + } + return true; + + case '>': + takeCurrentCharAndAdvance(); + if ('=' == getCurrentChar()) + { + takeCurrentCharAndAdvance(); + } + return true; + + case '=': + takeCurrentCharAndAdvance(); + if (ch == getCurrentChar()) + { + takeCurrentCharAndAdvance(); + } + return true; + + case '~': + takeCurrentCharAndAdvance(); + if (getCurrentChar() == '=') + { + + } + return true; + + case '!': + takeCurrentCharAndAdvance(); + if (getCurrentChar() == '=') + { + takeCurrentCharAndAdvance(); + } + return true; + default: + return false; + } + } + + + /** + * Answer the current char of the underlying Reader + * + * @return the current char + */ + protected char getCurrentChar() + { + return m_chCurrent; + } + + /** + * Advance to the next character. + */ + protected void advance() + { + try + { + int nCh = m_reader.read(); + if (nCh != -1) + { + ++m_iPos; + m_chCurrent = (char) nCh; + m_fIsEnd = false; + m_coffset++; + if (m_chCurrent == '\n') + { + m_lineNumber++; + m_coffset = 1; + } + } + else + { + m_chCurrent = (char) 0; + m_fIsEnd = true; + } + } + catch (IOException e) + { + m_fIsEnd = true; + } + } + + /** + * Advance to the next character and return it. + * + * @return the next character + */ + protected char nextChar() + { + advance(); + return getCurrentChar(); + } + + /** + * Note the offset within the underlying Reader + */ + protected void notePos() + { + m_iStartPos = m_iPos; + } + + /** + * Convert the buffered characters into a String representing a token. + * + * @return the String representing the buffered token + */ + protected String tokenString() + { + return m_tokenBuffer.toString(); + } + + /** + * Reset the buffer used for holding the current token + */ + protected void resetTokenString() + { + m_tokenBuffer.setLength(0); + } + + /** + * Add the current character to buffer that builds the current token. + */ + protected void takeCurrentChar() + { + m_tokenBuffer.append(getCurrentChar()); + } + + /** + * Add the current character to buffer that builds the current token and + * advance. + */ + protected void takeCurrentCharAndAdvance() + { + m_tokenBuffer.append(getCurrentChar()); + advance(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The current position in the underlying Reader. + */ + protected int m_iPos = 0; + + /** + * The saved start position for a token. + */ + protected int m_iStartPos = 0; + + /** + * The characters used for punctuation. + */ + protected String m_sPunctuation = ".;,"; + + /** + * The characters used to begin nesting. + */ + protected String m_sNests = "([{"; + + /** + * The characters used to end nesting. + */ + protected String m_sUnnests = ")]}"; + + /** + * The temporary buffer used to build the String representing a token. + */ + protected StringBuffer m_tokenBuffer = new StringBuffer(); + + /** + * The current working char. + */ + protected char m_chCurrent; + + /** + * The underlying Reader holding the characters being tokenized. + */ + protected Reader m_reader; + + /** + * The flag set when end of file is detected. + */ + protected boolean m_fIsEnd = true; + + /** + * Offset of the current character. + */ + protected int m_coffset = 1; + + /** + * The current line number. + */ + protected int m_lineNumber = 1; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseTokenScannerException.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseTokenScannerException.java new file mode 100644 index 0000000000000..1047504f0ef42 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseTokenScannerException.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* BaseTokenScannerExpression is the RuntimeException thrown by the +* BaseTokenScanner when expectations are not met. +* +* @author djl 2009.03.14 +*/ +public class BaseTokenScannerException + extends RuntimeException + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new BaseTokenException with the given string. + * + * @param sMessage the message String for the exception + */ + public BaseTokenScannerException(String sMessage) + { + super(sMessage); + } + + /** + * Construct a new BaseTokenException. + */ + public BaseTokenScannerException() + { + super(); + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseTokenStream.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseTokenStream.java new file mode 100644 index 0000000000000..32489e10fd072 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/BaseTokenStream.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + +/** +* Since BaseTokens can nest, BaseTokenStream creates for clients a utility +* interface that allows streaming (atEnd(), next()) over a CompoundBaseToken. +* +* @author djl 2009.03.14 +*/ +public class BaseTokenStream + { + // ----- constructors --------------------------------------------------- + + /** + * Constructor a new BaseTokenStream on the CompoundBaseToken. + * + * @param token the CompoundBaseToken to stream over + */ + public BaseTokenStream(CompoundBaseToken token) + { + m_aTokens = token.getTokens(); + m_iPos = 0; + + m_currentToken = isEnd() ? null : m_aTokens[0]; + } + + + // ----- Utility Streaming ---------------------------------------------- + + /** + * Answer a boolean indication as to whether the stream is at the end + * + * @return true if streaming is at an end otherwise false + */ + public boolean isEnd() + { + return m_iPos >= m_aTokens.length; + } + + /** + * Answer a current BaseToken base on the position of streaming + * + * @return the BaseToken at the current stream position + */ + public BaseToken getCurrentToken() + { + return m_currentToken; + } + + /** + * Answer the next token in the stream or null if at end. + * Advance the stream position. + * + * @return the next BaseToken in the stream + */ + public BaseToken next() + { + BaseToken[] aTokens = m_aTokens; + + return m_currentToken = + m_iPos < aTokens.length ? aTokens[m_iPos++] : null; + } + + /** + * Answer the next token in the stream or null if at end. + * Do not advance the stream position + * + * @return the next BaseToken in the stream + */ + public BaseToken peek() + { + BaseToken[] aTokens = m_aTokens; + return m_iPos < aTokens.length ? aTokens[m_iPos] : null; + } + + /** + * Answer the next two tokens in the stream or null(s) if at end. + * Do not advance the stream position + * + * @return the next BaseToken in the stream + */ + public BaseToken[] peek2() + { + BaseToken[] aTokens = m_aTokens; + BaseToken[] aResult = new BaseToken[2]; + aResult[0] = m_iPos < aTokens.length ? aTokens[m_iPos ] : null; + aResult[1] = m_iPos+1 < aTokens.length ? aTokens[m_iPos+1] : null; + return aResult; + } + + // ----- accessors ------------------------------------------------------ + + /** + * Remember a BaseTokenStream that can be the target of streaming + * + * @param ts the remembered token stream + */ + public void setLink(BaseTokenStream ts) + { + m_link = ts; + } + + /** + * Answer a BaseTokenStream that can become the target of streaming + * + * @return the BaseTokenStream that can become the target of streaming + */ + public BaseTokenStream getLink() + { + return m_link; + } + + + // ----- data members --------------------------------------------------- + + /** + * The array of tokens that the receiver is streaming over + */ + private BaseToken[] m_aTokens; + + /** + * The current position in the array of tokens + */ + private int m_iPos = 0; + + /** + * A BaseTokenStream that the receiver has "nested" from + */ + private BaseTokenStream m_link = null; + + /** + * The current BaseToken to return + */ + private BaseToken m_currentToken = null; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/CompoundBaseToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/CompoundBaseToken.java new file mode 100644 index 0000000000000..cdeb81b67b536 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/CompoundBaseToken.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* CompoundBaseToken is the abstract base class for all tokens processed by the +* low level BaseTokenScanner that are made up of two or more BaseTokens. +* +* @author djl 2009.03.14 +*/ +public abstract class CompoundBaseToken + extends BaseToken + { + // ----- Compound BaseToken interface ----------------------------------- + + /** + * Return an array of BaseTokens making up the receiver. + * + * @return the an array of BaseTokens making up the receiver. + */ + public abstract BaseToken[] getTokens(); + + /** + * Return the size of the collection of BaseTokens making up the receiver. + * + * @return the size of the BaseTocken collection making up the receiver + */ + public int size() + { + return getTokens().length; + } + + /** + * Return the BaseToken at the given index + * + * @param index the zero base index for retreiving a BaseToken + * + * @return the BaseToken at the given index + */ + public BaseToken get(int index) + { + return getTokens()[index]; + } + + + // ----- BaseToken interface -------------------------------------------- + + /** + * {@inheritDoc} + */ + public boolean isLeaf() + { + return false; + } + + /** + * {@inheritDoc} + */ + public boolean isCompound() + { + return true; + } + + /** + * {@inheritDoc} + */ + public boolean match(String s, boolean fIgnoreCaseFlag) + { + return false; + } + + // ----- Object methods ------------------------------------------------- + + /** + * Return a human-readable description for this token. + * + * @return a String description of the token + */ + public String toString() + { + String s = getSimpleName() + "{"; + BaseToken[] aoTokens = getTokens(); + + for (int i = 0, c = aoTokens.length; i < c; ++i) + { + s += aoTokens[i]; + if (i != c - 1) + { + s += ", "; + } + } + return s + "}"; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/IdentifierBaseToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/IdentifierBaseToken.java new file mode 100644 index 0000000000000..bce8d9f44f523 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/IdentifierBaseToken.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* IdentifierBaseToken is a token that represents an identifier +* +* @author djl 2009.03.14 +*/ +public class IdentifierBaseToken + extends LeafBaseToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new IdentifierBaseToken with the given value. + * + * @param s the string that represents an identifier + */ + public IdentifierBaseToken(String s) + { + m_sValue = s; + } + + + // ----- Leaf BaseToken interface --------------------------------------- + + /** + * {@inheritDoc} + */ + public String getValue() + { + return m_sValue; + } + + + // ----- BaseToken interface -------------------------------------------- + + /** + * {@inheritDoc} + */ + public boolean isIdentifier() + { + return true; + } + + + // ----- data members --------------------------------------------------- + + /** + * The string that represents the identifier. + */ + private String m_sValue; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/LeafBaseToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/LeafBaseToken.java new file mode 100644 index 0000000000000..9446d1636de0b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/LeafBaseToken.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* LeafBaseToken is the abstract base class for all tokes processed by the +* low level BaseTokenScanner that are considered leaves. +* +* @author djl 2009.03.14 +*/ +public abstract class LeafBaseToken + extends BaseToken + { + // ----- Leaf BaseToken interface -------------------------------------------- + + /** + * Return the string representation of this LeafBaseToken. + * + * @return the string that represents the reciever + */ + public abstract String getValue(); + + + // ----- BaseToken interface -------------------------------------------- + + /** + * {@inheritDoc} + */ + public boolean isLeaf() + { + return true; + } + + /** + * {@inheritDoc} + */ + public boolean isCompound() + { + return false; + } + + /** + * {@inheritDoc} + */ + public boolean match(String s, boolean fIgnoreCase) + { + String sValue = getValue(); + return fIgnoreCase ? s.equalsIgnoreCase(sValue) : s.equals(sValue); + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Return a human-readable description for this token. + * + * @return a String description of the token + */ + public String toString() + { + return getSimpleName() + "{" + getValue() + "}"; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/LiteralBaseToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/LiteralBaseToken.java new file mode 100644 index 0000000000000..5ba7288a164c3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/LiteralBaseToken.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* LiteralBaseToken is the BaseToken that represents literals such as String, +* Integer, Long, Float, and Double. +* +* @author djl 2009.03.14 +*/ +public class LiteralBaseToken + extends LeafBaseToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new LiteralBaseToken with the given parameters. + * + * @param nType the type code for the token + * @param s the string representation for the token + */ + public LiteralBaseToken(int nType, String s) + { + m_nType = nType; + m_sValue = s; + } + + + // ----- LeafBaseToken interface ---------------------------------------- + + /** + * {@inheritDoc} + */ + public String getValue() + { + return m_sValue; + } + + + // ----- BaseToken interface -------------------------------------------- + + /** + * {@inheritDoc} + */ + public boolean isLiteral() + { + return true; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Obtain the type code of this token. + * + * @return the type code + */ + public int getType() + { + return m_nType; + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Return a human-readable description for this token. + * + * @return a String description of the token + */ + public String toString() + { + String s = "Literal{"; + int nType = m_nType; + + if (nType == STRINGLITERAL) + { + s = s + "\""; + } + s = s + getValue(); + if (nType == DOUBLELITERAL) + { + s = s + "d"; + } + else if (nType == LONGLITERAL) + { + s = s + "l"; + } + else if (nType == SHORTLITERAL) + { + s = s + "s"; + } + else if (nType == STRINGLITERAL) + { + s = s + "\""; + } + return s + "}"; + } + + + // ----- static literal creation healper's ----------------------------- + + /** + * Create new LiteralBaseToken representing a String with given value + * + * @param s the text of the literal + * + * @return a LiteralBaseToken for a String + */ + public static LiteralBaseToken createString(String s) + { + return new LiteralBaseToken(STRINGLITERAL, s); + } + + /** + * Create new LiteralBaseToken representing a Integer with given value + * + * @param s the text of the literal + * + * @return a LiteralBaseToken for a Integer + */ + public static LiteralBaseToken createShort(String s) + { + return new LiteralBaseToken(SHORTLITERAL, s); + } + + /** + * Create new LiteralBaseToken representing a Integer with given value + * + * @param s the text of the literal + * + * @return a LiteralBaseToken for a Integer + */ + public static LiteralBaseToken createInteger(String s) + { + return new LiteralBaseToken(INTEGERLITERAL, s); + } + + /** + * Create new LiteralBaseToken representing a Long with given value + * + * @param s the text of the literal + * + * @return a LiteralBaseToken for a Long + */ + public static LiteralBaseToken createLong(String s) + { + return new LiteralBaseToken(LONGLITERAL, s); + } + + /** + * Create new LiteralBaseToken representing a float with given value + * + * @param s the text of the literal + * + * @return a LiteralBaseToken for a Float + */ + public static LiteralBaseToken createFloat(String s) + { + return new LiteralBaseToken(FLOATLITERAL, s); + } + + /** + * Create new LiteralBaseToken representing a Double with given value + * + * @param s the text of the literal + * + * @return a LiteralBaseToken for a Double + */ + public static LiteralBaseToken createDouble(String s) + { + return new LiteralBaseToken(DOUBLELITERAL, s); + } + + /** + * Create new LiteralBaseToken representing a Boolean with given value + * + * @param s the text of the literal + * + * @return a LiteralBaseToken for a Double + */ + public static LiteralBaseToken createBoolean(String s) + { + return new LiteralBaseToken(BOOLEANLITERAL, s); + } + + /** + * Create new LiteralBaseToken representing a null with given value + * + * @param s the text of the literal + * + * @return a LiteralBaseToken for a Double + */ + public static LiteralBaseToken createNull(String s) + { + return new LiteralBaseToken(NULLLITERAL, s); + } + + + // ----- data members --------------------------------------------------- + + /** + * The type code for this literal token + */ + private int m_nType = -1; + + /** + * The string representation of this literal token + */ + private String m_sValue = ""; + + + // ----- constants ------------------------------------------------------ + + /** + * The numberic code for a string literal + */ + public static final int STRINGLITERAL = 1; + + /** + * The numberic code for a integer literal + */ + public static final int INTEGERLITERAL = 2; + + /** + * The numberic code for a long literal + */ + public static final int LONGLITERAL = 3; + + /** + * The numberic code for a float literal + */ + public static final int FLOATLITERAL = 4; + + /** + * The numberic code for a double literal + */ + public static final int DOUBLELITERAL = 5; + + /** + * The numberic code for a boolean literal + */ + public static final int BOOLEANLITERAL = 6; + + /** + * The numberic code for a boolean literal + */ + public static final int NULLLITERAL = 7; + + /** + * The numberic code for a short literal + */ + public static final int SHORTLITERAL = 8; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/NestedBaseTokens.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/NestedBaseTokens.java new file mode 100644 index 0000000000000..03cf07333025c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/NestedBaseTokens.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* NestedBaseTokens is a token that holds a sequence of tokens as well as the +* two bracketing characters. This nesting of tokens makes many algorithms +* easier in the higher levels of a parser. +* +* @author djl 2009.03.14 +*/ +public class NestedBaseTokens + extends CompoundBaseToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new NestedBaseTokens with the given parameters. + * + * @param chStart the character that starts the nesting + * @param chEnd the character that ends the nesting + * @param aTokens the right argument for the node + */ + public NestedBaseTokens(char chStart, char chEnd, + BaseToken[] aTokens) + { + m_aTokens = aTokens; + m_nestStart = chStart; + m_nestEnd = chEnd; + } + + + // ----- CompoundBaseToken interface ------------------------------------ + + /** + * {@inheritDoc} + */ + public BaseToken[] getTokens() + { + return m_aTokens; + } + + + // ----- BaseToken interface -------------------------------------------- + + /** + * {@inheritDoc} + */ + public boolean isNest() + { + return true; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Obtain the character that begins the nesting. + * + * @return the character that begings the nesting + */ + public char getNestStart() + { + return m_nestStart; + } + + /** + * Obtain the character that ends the nesting. + * + * @return the character that ends the nesting + */ + public char getNestEnd() + { + return m_nestEnd; + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Return a human-readable description for this token. + * + * @return a String description of the token + */ + public String toString() + { + String s = "Nest" + m_nestStart; + BaseToken[] aTokens = getTokens(); + + for (int i = 0, c = aTokens.length; i < c; ++i) + { + s = s + aTokens[i]; + if (i != c - 1) + { + s = s + ", "; + } + } + return s + m_nestEnd; + } + + + // ----- data members --------------------------------------------------- + + /** + * The array of tokens that are nested + */ + private BaseToken[] m_aTokens; + + /** + * The character that starts the nesting + */ + private char m_nestStart; + + /** + * The character that ends the nesting. + */ + private char m_nestEnd; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/OperatorBaseToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/OperatorBaseToken.java new file mode 100644 index 0000000000000..506cb7b8925f0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/OperatorBaseToken.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* OperatorBaseToken is a token that represents a known operator. +* +* @author djl 2009.03.14 +*/ +public class OperatorBaseToken + extends LeafBaseToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new OperatorBaseToken with the given operator value. + * + * @param s the string that represents an operator + */ + public OperatorBaseToken(String s) + { + m_sValue = s; + } + + + // ----- Leaf BaseToken interface --------------------------------------- + + /** + * {@inheritDoc} + */ + public String getValue() + { + return m_sValue; + } + + + // ----- BaseToken interface -------------------------------------------- + + /** + * {@inheritDoc} + */ + public boolean isOperator() + { + return true; + } + + + // ----- data members --------------------------------------------------- + + /** + * The string that represents the operator. + */ + private String m_sValue; + } + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/PunctuationBaseToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/PunctuationBaseToken.java new file mode 100644 index 0000000000000..b57bbda293820 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/PunctuationBaseToken.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* PunctuationBaseToken is a token that represents a known punctuation. +* +* @author djl 2009.03.14 +*/ +public class PunctuationBaseToken + extends LeafBaseToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new PunctuationBaseToken with the given punctuation value. + * + * @param s the string that represents a punctuation + */ + public PunctuationBaseToken(String s) + { + this.m_sValue = s; + } + + + // ----- Leaf BaseToken interface --------------------------------------- + + /** + * {@inheritDoc} + */ + public String getValue() + { + return m_sValue; + } + + + // ----- BaseToken interface -------------------------------------------- + + public boolean isPunctuation() + { + return true; + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Return a human-readable description for this token. + * + * @return a String description of the token + */ + public String toString() + { + return "Punctuation{\"" + getValue() + "\"}"; + } + + + // ----- data members --------------------------------------------------- + + /** + * The string that represents the punctuation + */ + private String m_sValue; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/SequenceBaseToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/SequenceBaseToken.java new file mode 100644 index 0000000000000..3a158dd116210 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/base/SequenceBaseToken.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.base; + + +/** +* SequenceBaseToken is a token that holds a sequence of tokens. This nesting +* of tokens makes many algorithms easier in the higher levels of a parser. +* +* @author djl 2009.03.14 +*/ +public class SequenceBaseToken + extends CompoundBaseToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new SequenceBaseToken with the array of tokens. + * + * @param aTokens an array of BaseTokens + */ + public SequenceBaseToken(BaseToken[] aTokens) + { + m_aTokens = aTokens; + } + + + // ----- Compound BaseToken interface ----------------------------------- + + /** + * {@inheritDoc} + */ + public BaseToken[] getTokens() + { + return m_aTokens; + } + + + // ----- data members --------------------------------------------------- + + /** + * The array of tokens for this sequence of tokens + */ + private BaseToken[] m_aTokens; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/BetweenOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/BetweenOPToken.java new file mode 100644 index 0000000000000..5f310753d5006 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/BetweenOPToken.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + + +/** +* BetweenOPToken is used to parse a SQl like between statment. +* Example "x between 5 and 10. +* +* @author djl 2009.03.14 +*/ +public class BetweenOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new BetweenOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public BetweenOPToken(String sId, int nBp) + { + super(sId, nBp); + } + + /** + * Construct a new BetweenOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sAstName the name for this tokens AST + */ + public BetweenOPToken(String sId, int nBp, String sAstName) + { + super(sId, nBp, sAstName); + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + Term[] at = new Term[2]; + + at[0] = p.expression(getBindingPower() + 1); + p.m_scanner.advance("and"); // eat the and + at[1] = p.expression(getBindingPower() + 1); + return newAST(getLedASTName(), + getId(), + leftNode, + Terms.newTerm("listNode",at)); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/ContainsOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/ContainsOPToken.java new file mode 100644 index 0000000000000..4caf03ae87982 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/ContainsOPToken.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; + + +/** +* ContainsOPToken is used to implement a contains operation that checks for +* membership in a list. Example "x contains 5". The parsing can support +* the additional keywors "any" or "all" as in "x contains any (4,8,9)". +* +* @author djl 2009.03.14 +*/ +public class ContainsOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new ContainsOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public ContainsOPToken(String sId, int nBp) + { + super(sId, nBp); + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + OPToken tok = p.getScanner().getCurrent(); + String sMyName = getId(); + + if (tok instanceof IdentifierOPToken) + { + String sTok = tok.getValue(); + if (sTok.equalsIgnoreCase("all") || sTok.equalsIgnoreCase("any")) + { + sMyName = sMyName + "_" + sTok; + p.getScanner().next(); + } + } + return newAST(getLedASTName(), + sMyName, + leftNode, + p.expression(getBindingPower() - 1)); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/EndOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/EndOPToken.java new file mode 100644 index 0000000000000..17c0dcc828a94 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/EndOPToken.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + +/** + * An OPToken representing the end of a token stream. + * + * @author jk 2014.08.07 + */ +public class EndOPToken + extends PunctuationOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Create a new EndOPToken + */ + protected EndOPToken() + { + super("*end*"); + } + + // ----- constants ------------------------------------------------------ + + public static EndOPToken INSTANCE = new EndOPToken(); + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/EndOfStatementOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/EndOfStatementOPToken.java new file mode 100644 index 0000000000000..92c361b60137a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/EndOfStatementOPToken.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + +/** + * An OPToken representing the end of a CohQL statement. + * + * @author jk 2014.08.07 + */ +public class EndOfStatementOPToken + extends PunctuationOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Create a new EndOfStatementOPToken + */ + protected EndOfStatementOPToken() + { + super(";"); + } + + // ----- constants ------------------------------------------------------ + + public static EndOfStatementOPToken INSTANCE = new EndOfStatementOPToken(); + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/IdentifierOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/IdentifierOPToken.java new file mode 100644 index 0000000000000..0e07e2c326af8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/IdentifierOPToken.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + + +/** +* IdentifierOPToken is used to implement identifiers. +* +* @author djl 2009.03.14 +*/ +public class IdentifierOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new IdentifierOPToken with the given parameters. + * + * @param s string value of the identifier + */ + public IdentifierOPToken(String s) + { + super(s); + setBindingPower(OPToken.PRECEDENCE_IDENTIFIER); + } + + /** + * Construct a new IdentifierOPToken with the given parameters. + * + * @param sIdentifier string value of the identifier + * @param sNudASTName the ast name to use for constructing an ast + */ + public IdentifierOPToken(String sIdentifier, String sNudASTName) + { + this(sIdentifier); + m_sNudASTName = sNudASTName; + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term nud(OPParser parser) + { + String sNudASTName = getNudASTName(); + + if (sNudASTName == null) + { + return AtomicTerm.createSymbol(getValue()); + } + return Terms.newTerm( + sNudASTName, + AtomicTerm.createSymbol(getValue())); + } + + /** + * {@inheritDoc} + */ + public Term led(OPParser parser, Term leftNode) + { + throw new OPException("Unexpected Identifier " + getId() + + " in operator position"); + } + } + + + + + + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/InfixOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/InfixOPToken.java new file mode 100644 index 0000000000000..6a29512aa70a7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/InfixOPToken.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; + + +/** +* InfixOPToken is used to implement infix operators. If enabled it will also +* do the right thing for unary operators such as + and - which are typically +* overloaded. +* +* @author djl 2009.03.14 +*/ +public class InfixOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new InfixOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public InfixOPToken(String sId, int nBp) + { + super(sId, nBp); + } + + /** + * Construct a new InfixOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sASTName the name for this tokens AST + */ + public InfixOPToken(String sId, int nBp, String sASTName) + { + super(sId, nBp, sASTName); + } + + /** + * Construct a new InfixOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBP the binding power for this token + * @param sLedASTName the name for this tokens AST + * @param sNudASTName the name for this tokens AST + */ + public InfixOPToken(String sId, int nBP, String sLedASTName, + String sNudASTName) + { + super(sId, nBP, sLedASTName, sNudASTName); + } + + + // ----- Operator Precedence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + return newAST( + getLedASTName(), + getId(), + leftNode, + p.expression(getBindingPower())); + } + + /** + * {@inheritDoc} + */ + public Term nud(OPParser p) + { + if (!m_fPrefixAllowed) + { + throw new OPException("Infix operator " + getId() + + " used in prefix position"); + } + return newAST( + getNudASTName(), + getId(), + p.expression(getBindingPower())); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the flag that control whether the operator can be used as a prefix. + * + * @return the string representation + */ + public boolean isPrefixAllowed() + { + return m_fPrefixAllowed; + } + + /** + * Set the flag that control whether the operator may be used as a prefix. + * + * @param fIsPrefix the string representation for the token + */ + public void setPrefixAllowed(boolean fIsPrefix) + { + m_fPrefixAllowed = fIsPrefix; + } + + + // ----- data members --------------------------------------------------- + + /** + * Flag that control whether the operator can be used as a prefix. + */ + protected boolean m_fPrefixAllowed = false; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/InfixRightOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/InfixRightOPToken.java new file mode 100644 index 0000000000000..5700c7f88c1e6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/InfixRightOPToken.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; + + +/** +* InfixRightOPToken is used to implement infix operators that like to bind + * to the right which is typical of exponentiation rules. +* +* @author djl 2009.03.14 +*/ +public class InfixRightOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new InfixRightOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBP the binding power for this token + */ + public InfixRightOPToken(String sId, int nBP) + { + super(sId, nBP); + } + + /** + * Construct a new InfixRightOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sLedASTName the name for this tokens AST + */ + public InfixRightOPToken(String sId, int nBp, String sLedASTName) + { + super(sId, nBp, sLedASTName); + } + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + return newAST(getLedASTName(), + getId(), + leftNode, p.expression(getBindingPower() -1)); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/KeywordOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/KeywordOPToken.java new file mode 100644 index 0000000000000..46367d3825925 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/KeywordOPToken.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + + +/** +* KeywordOPToken acts like a PunctuationOPToken when used in a led role +* and a Identifier when used as a nud. +* +* @author djl 2009.03.14 +*/ +public class KeywordOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new KeywordOPToken with the given parameters. + * + * @param s string value of the identifier + */ + public KeywordOPToken(String s) + { + super(s); + m_sNudASTName = "identifier"; + setBindingPower(OPToken.PRECEDENCE_KEYWORD); + } + + // ----- Operator Precedence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term nud(OPParser p) + { + String sNudASTName = getNudASTName(); + + if (sNudASTName == null) + { + return AtomicTerm.createSymbol(getValue()); + } + return Terms.newTerm( + sNudASTName, + AtomicTerm.createSymbol(getValue())); + } + + /** + * {@inheritDoc} + */ + public Term led(OPParser parser, Term leftNode) + { + return null; + } + + } + + + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/LikeOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/LikeOPToken.java new file mode 100644 index 0000000000000..30079a8707243 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/LikeOPToken.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + + +/** +* LikeOPToken is used to parse a SQL like statement. +* Example "key()like 'key\_%' escape '\' +* +* @author bbc 2011.05.23 +*/ +public class LikeOPToken + extends InfixOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new LikeOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public LikeOPToken(String sId, int nBp) + { + super(sId, nBp); + } + + /** + * Construct a new LikeOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sAstName the name for this tokens AST + */ + public LikeOPToken(String sId, int nBp, String sAstName) + { + super(sId, nBp, sAstName); + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + Term[] at = new Term[2]; + + at[0] = p.expression(getBindingPower() + 1); + if (p.m_scanner.advanceWhenMatching("escape")) + { + at[1] = p.expression(getBindingPower() + 1); + return newAST(getLedASTName(), + getId(), leftNode, + Terms.newTerm("listNode", at)); + } + else + { + return newAST(getLedASTName(), + getId(), leftNode, at[0]); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/ListOpToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/ListOpToken.java new file mode 100644 index 0000000000000..7ccc9bcbbe7a0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/ListOpToken.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + + +/** +* ListOPToken is used to process expressions between bracketing characters +* such as are between "[" and "]" which should result in a list (e.g. +* [1,3,4,5]). This class does nothing to limit nesting hence +* nested lists are possible. +* +* @author djl 2009.03.14 +*/ +public class ListOpToken + extends NestingOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new InfixOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public ListOpToken(String sId, int nBp) + { + super(sId, nBp); + } + + /** + * Construct a new InfixOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sNudASTName the name for this tokens AST + */ + public ListOpToken(String sId, int nBp, String sNudASTName) + { + super(sId, nBp, null, sNudASTName); + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term nud(OPParser p) + { + String sNudASTName = getNudASTName(); + Term[] aLst = p.readNestedCommaSeparatedList(getNest()); + String sFunctor = sNudASTName == null ? getId() : sNudASTName; + + return Terms.newTerm(sFunctor, aLst); + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/LiteralOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/LiteralOPToken.java new file mode 100644 index 0000000000000..44e8307e2a93c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/LiteralOPToken.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.base.LiteralBaseToken; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; + + +/** +* LiteralOpToken is used to implement literals. +* +* @author djl 2009.03.14 +*/ +public class LiteralOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new LiteralOPToken with the given parameters. + * + * @param bt the LiteralBaseToken that holds the literal info + */ + public LiteralOPToken(LiteralBaseToken bt) + { + this(bt.getValue(), bt.getType()); + } + + /** + * Construct a new LiteralOPToken with the given parameters. + * + * @param s string representation of the literal + * @param nTypeCode the type code for this literal token + * @param sNudASTName the name to use for building an AST + */ + public LiteralOPToken(String s, int nTypeCode, String sNudASTName) + { + this(s, nTypeCode); + m_sNudASTName = sNudASTName; + } + + /** + * Construct a new LiteralOPToken with the given parameters. + * + * @param s string representation of the literal + * @param nTypeCode the type code for this literal token + */ + public LiteralOPToken(String s, int nTypeCode) + { + super(s); + m_nType = nTypeCode; + setBindingPower(OPToken.PRECEDENCE_IDENTIFIER); + } + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term nud(OPParser p) + { + String sNudASTName = getNudASTName(); + int nType = m_nType; + + if (sNudASTName == null) + { + return new AtomicTerm(getValue(), nType); + } + return Terms.newTerm(sNudASTName, + new AtomicTerm(getValue(), nType)); + } + + /** + * {@inheritDoc} + */ + public Term led(OPParser parser, Term leftNode) + { + throw new OPException("Unexpected Literal " + getId() + + " in operator position"); + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Return a human-readable description for this token. + * + * @return a String description of the token + */ + public String toString() + { + return "LiteralOPToken(" + getValue() + ")"; + } + + + // ----- data members --------------------------------------------------- + + /** + * The type code for this token + */ + private int m_nType = 0; + } + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/NestingOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/NestingOPToken.java new file mode 100644 index 0000000000000..34050443d4ef9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/NestingOPToken.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.base.NestedBaseTokens; + + +/** +* NestingOPToken is an abstract classused to implement parsing situation +* where some nesting is implied. Typical uses are for processing between +* bracked symbols like "(" and ")". This class supports processing a nested +* collection of BaseTokens. +* +* @author djl 2009.03.14 +*/ +public class NestingOPToken + extends OPToken + implements Cloneable + + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new NestingOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public NestingOPToken(String sId, int nBp) + { + super(sId, nBp); + } + /** + * Construct a new NestingOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sLedASTName the name for this tokens AST + * @param sNudASTName the name for this tokens AST + */ + public NestingOPToken(String sId, int nBp, String sLedASTName, + String sNudASTName) + { + super(sId, nBp, sLedASTName, sNudASTName); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Obtain the NestedBaseTokens that this token will process. + * + * @return the NestedBaseTokens that this token will process + */ + public NestedBaseTokens getNest() + { + return m_nest; + } + + /** + * Set the NestedBaseTokens to process. + * + * @param nest the NestedBaseTokens object to process + * @return the receiver + */ + public NestingOPToken setNest(NestedBaseTokens nest) + { + m_nest = nest; + return this; + } + + + // ----- Object methods ------------------------------------------------- + + public Object clone() throws CloneNotSupportedException + { + return super.clone(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The nested collection of base tokens used for processing + */ + NestedBaseTokens m_nest = null; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/NotOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/NotOPToken.java new file mode 100644 index 0000000000000..4c647478d315a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/NotOPToken.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; + + +/** +* NotOPToken is used to implement not operators. It will also work for +* sql like infix not. +* +* @author djl 2009.03.14 +*/ +public class NotOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new NotOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public NotOPToken(String sId, int nBp) + { + super(sId, nBp); + } + + /** + * Construct a new NotOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sLedASTName the name for this tokens AST + * @param sNudASTName the name for this tokens AST + */ + public NotOPToken(String sId, int nBp, String sLedASTName, + String sNudASTName) + { + super(sId, nBp, sLedASTName, sNudASTName); + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + // Not is really a prefix operator but + // It is possible for ! to be after a left value and before the + // operator like "x not in (1,2,3)" + // so we deal with it in a funny led rather that corrupt our + // simple parser loop + OPToken tok = p.getScanner().getCurrent(); + p.getScanner().next(); + Term t = tok.led(p, leftNode); + return newAST(getLedASTName(), getId(), t); + } + + /** + * {@inheritDoc} + */ + public Term nud(OPParser p) + { + Term t = p.expression(getBindingPower()); + return newAST(getNudASTName(), getId(), t); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPException.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPException.java new file mode 100644 index 0000000000000..7f11fc74ae25a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPException.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +/** +* OPExpression is the RuntimeException thrown by the OPParser and OPScanner +* when problems are detected. +* +* @author djl 2009.03.14 +*/ +public class OPException extends RuntimeException + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new OPException with the given message string. + * + * @param sMessage the message String for the exception + */ + public OPException(String sMessage) + { + super(sMessage); + } + + /** + * Construct a new OPException. + */ + public OPException() + { + super(); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPParser.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPParser.java new file mode 100644 index 0000000000000..c4441e63acba7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPParser.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.base.NestedBaseTokens; +import com.tangosol.coherence.dsltools.base.BaseTokenStream; + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; + +import java.io.Reader; +import java.io.StringReader; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + + +/** +* OPParser is the parser class for the Top Down Operator Presidence Parsing +* framework. Top-down Operator Precedence Parsing is a technique invented by +* Vaughan Pratt in the 1970's. +* +* The fundamental idea is to associate semantics with tokens embodied in +* objects rather than using grammar rules embodied in procedures. The +* token's job is to transform pieces of syntax into Abstract Syntax Trees (AST). +* Each token has a "binding power" that determines operator precedence and +* two functions, "nud" (null denotation) and "led" (left denotation). +* The "nud" function is typically used to process tokens that have a role of +* "literal" or "variable". The "led" function is used for tokens that are +* typically in the role of an operator such as "+" or "*". The parsing +* process is driven by a very simple algorithm embodied in a method named +* "expression" which takes a right binding power as argument. The expression +* method starts by processing the first token (usually a literal or a +* variable) by invoking its "nud" method. The expression method then loops +* fetching the next token and building an AST by dispatching to the current +* token's "led" methods so long as the passed right binding power is less +* than the next token's left binding power. These "led" methods may eat as +* many tokens as is warranted by their semantics and they may make recursive +* calls back to the parsers expression method using their binding power as +* the right binding power argument. +* +* For example, to parse the expression "1 + 2", the parser's expression +* method would call "nud" on the token for the literal "1". The "nud" of a +* literal simply returns an atomic term. Then the parser checks if the right +* binding power is less than the left binding power of the next token and +* if true, it calls "led" on that next token. In this case the right binding +* power is 0 and the binding power of the "+" operator token is 50. So the +* parser calls "led" on the "+" operator token passing itself and the left +* AST node term ("1"). +* +* The "led" implementation of the "+" operator recursively calls the parser's +* expression method with a right binding power of 50. The expression calls +* "nud" on the next token, the literal token "2" which returns an atomic term +* for "2". The loop in expression is avoided because the next token is a +* special token used to represent the end of stream. It has a left binding +* power of -1 which is lower than any possible right binding power. +* Therefore, the parser returns the atomic term "2". The caller +* ("+" operator "led") returns an AST term for the "+" operator that has +* left and right terms. In this case the left term is the atomic "1" and +* the right is the atomic "2". +* +* Had we been parsing a slightly more complicated example such as "1 + 2 * 3" +* when the "led" for "+" recursively called expression with its binding power +* of 50 as the right binding power then the three tokens "2", "*", and "3" +* will be consumed because "*" has a left binding power of 60 which lets +* expression's loop calls the "led" of "*" ultimately returning an AST that +* represents "*(2,3) "to be used as the right side term for "+". +* +* @see OPToken +* +* @author djl 2009.03.14 +*/ +public class OPParser + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new OPParser that parses the given expression in the + * language defined by the given TokenTable. + * @param sExper string expression to parse + * @param table the TokenTable that defines the language to parse + * @param setOperators the operators this parser will use + */ + public OPParser(String sExper, TokenTable table, Set setOperators) + { + this(new StringReader(sExper), table, setOperators); + } + + /** + * Construct a new OPParser that parses the given Reader in the + * language defined by the given TokenTable. + * @param reader Reader with the chars to parse + * @param table the TokenTable that defines the language to parse + * @param setOperators the operators this parser will use + */ + public OPParser(Reader reader, TokenTable table, Set setOperators) + { + m_scanner = new OPScanner(table, setOperators); + m_scanner.scan(reader); + } + + /** + * Construct a new OPParser initialized to gthe given Scanner + * + * @param scanner OPScanner that is already initialized + */ + public OPParser(OPScanner scanner) + { + m_scanner = scanner; + m_scanner.reset(); + } + + + // ----- Parser API ----------------------------------------------------- + + /** + * Obtain the OPScanner that backs this parser + * + * @return the OPScanner that acts as a token source + */ + public OPScanner getScanner() + { + return m_scanner; + } + + /** + * Parse the next expression into an Abstract Syntax Tree. + * + * @param the term type + * + * @return an ASTNode that represents the structured meaning of the + * expression being parsed + */ + public T parse() + { + return (T) expression(0); + } + + /** + * Parse the next expression into an Abstract Syntax Tree using the + * given right binding power. Parsing will continue so long as the right + * binding power is less than the current tokens left binding power. + * This is the central algorithm of Vaughan Pratt's Top Down Operator + * Precedence Parser. + * + * @param nRightBindingPower defines how strong token must bind to the + * left before halting parsing + * + * @return an ASTNode that represents the structured meaning of the + * expression being parsed + */ + public Term expression(int nRightBindingPower) + { + OPScanner scanner = m_scanner; + OPToken t = m_scanner.getCurrent(); + if (t == null) + { + return AtomicTerm.createNull(); + } + scanner.next(); + Term left = t.nud(this); + while (((t = scanner.getCurrent()) != null) && + nRightBindingPower < t.leftBindingPower()) + { + scanner.next(); + left = t.led(this, left); + } + return left; + } + + + // ----- Utility List Gathering API ------------------------------------- + + /** + * Parse a comma separated sequence of expressions upto the given end + * marker. Return an array of ASTNodes + * + * @param sEndMarker defines the symbol that ends the sequence + * + * @return an ASTNode array that represents the sequence + */ + public Term[] nodeList(String sEndMarker) + { + return nodeList(sEndMarker, false); + } + + + /** + * Parse a comma separated sequence of expressions upto the given end + * marker. Given flag controll if end of stream overides the end marker. + * Return an array of ASTNodes + * + * @param sEndMarker defines the symbol that ends the sequence + * @param fEndStreamAllowed flag to overide testing for sEndMarker if + * at the end of stream + * + * @return an ASTNode array that represents the sequence + */ + public Term[] nodeList(String sEndMarker, boolean fEndStreamAllowed) + { + List lst = new ArrayList(); + OPScanner scanner = m_scanner; + + while (!scanner.isEndOfStatement() && !scanner.matches(sEndMarker)) + { + Term t = expression(0); + lst.add(t); + if (fEndStreamAllowed && scanner.isEndOfStatement()) + { + return (Term[]) lst.toArray(new Term[lst.size()]); + } + if (scanner.matches(sEndMarker)) + { + break; + } + scanner.advance(","); + } + if (!scanner.matches(sEndMarker)) + { + throw new OPException("Unfullfilled expectation \"" + + sEndMarker + "\" not found!"); + } + return (Term[]) lst.toArray(new Term[lst.size()]); + } + + /** + * Parse a comma separated sequence of expressions to the of the tokens. + * Return an array of ASTNodes + * + * @return an ASTNode array that represents the sequence + */ + public Term[] nodeList() + { + List lst = new ArrayList(); + OPScanner scanner = m_scanner; + + while (!scanner.isEndOfStatement()) + { + Term t = expression(0); + lst.add(t); + if (scanner.isEndOfStatement()) + { + break; + } + if (!scanner.advanceWhenMatching(",")) + { + break; + } + } + return (Term[]) lst.toArray(new Term[lst.size()]); + } + + /** + * Build an array of ASTNodes by processing the this tokens nest as + * a comma separated list. + * of BaseTokens. + * + * @param nest the nest of BaseTokens to process + * + * @return an array of AstNodes + */ + public Term[] readNestedCommaSeparatedList(NestedBaseTokens nest) + { + OPScanner scanner = m_scanner; + ArrayList lst = new ArrayList(); + + scanner.pushStream(new BaseTokenStream(nest)); + while (!scanner.isEndOfStatement()) + { + Term t = expression(0); + lst.add(t); + if (getScanner().isEndOfStatement()) + { + break; + } + getScanner().advance(","); + } + + getScanner().popStream(); + return (Term[]) lst.toArray(new Term[lst.size()]); + } + + + // ----- data members --------------------------------------------------- + + /** + * The OPScanner used as the stream of tokens + */ + OPScanner m_scanner; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPScanner.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPScanner.java new file mode 100644 index 0000000000000..a741ec6986dc3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPScanner.java @@ -0,0 +1,509 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.base.*; + +import java.io.Reader; +import java.io.StringReader; +import java.util.Set; + + +/** +* OPScanner gives clients a streaming api that returns a next +* OPToken by processing a java.lang.String using a TokenTable to convert +* lower level BaseTokens into the high functionality OPTokens. Since +* BaseTokens support a composite sequence token OPScanner supports nested +* scanning. +* +* @author djl 2009.03.02 +*/ +public class OPScanner + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new OPScanner using the given TokenTable. + * + * @param tokenTable the TokenTable that defines the mapping + * from BaseTokens + * @param setOperators the set of operators to use + */ + public OPScanner(TokenTable tokenTable, Set setOperators) + { + m_tokenTable = tokenTable; + m_setOperators = setOperators; + } + + // ----- initialization API --------------------------------------------- + + /** + * Initialize a new OPScanner to process the given String and load first + * token into current. + * + * @param s the String to convert to a stream of tokens + */ + public void scan(String s) + { + scan(new StringReader(s)); + } + + /** + * Initialize a new OPScanner to process the given Reader and load first + * token into current. + * + * @param r the Reader to use as source for a stream of tokens + */ + public void scan(Reader r) + { + BaseTokenScanner baseScanner = new BaseTokenScanner(r); + m_data = new BaseTokenStream( + (SequenceBaseToken) baseScanner.scan()); + next(); + } + + /** + * Reset this scanner to process the current BaseTokenStream + * + */ + public void reset() + { + m_current = m_data.getCurrentToken(); + classify(); + } + + /** + * The given flag determines the strictness in that an unknown token + * throws an exception otherwise they are turned into an identifier. The + * default setting is false. + * + * @param fStrict boolean to control the strictness + */ + public void setStrictness(boolean fStrict) + { + m_fStrict = fStrict; + } + + + // ----- OPScanner Streaming API ---------------------------------------- + + /** + * Move to the next token in the stream and return it. + * + * @return the current token + */ + public OPToken next() + { + m_current = m_data.next(); + return classify(); + } + + /** + * If the Scanner is at the end answer true otherwise answer false. + * + * @return the boolean that answers as to whether the stream is atEnd + */ + public boolean isEnd() + { + return m_current == null; + } + + /** + * If the scanner is at the end of a statement return true. + * A statement end is signified by either a semi-colon or the + * end of the token stream. + * + * @return true if the scanner is at the end of a statement + */ + public boolean isEndOfStatement() + { + return m_current == null || m_current.match(EndOfStatementOPToken.INSTANCE.getValue()); + } + + /** + * Answer the current OPToken. + * + * @return the current token + */ + public OPToken getCurrent() + { + return m_currentToken; + } + + /** + * Answer the current BaseToken. + * + * @return the current BaseToken + */ + public BaseToken getCurrentBaseToken() + { + return m_current; + } + + /** + * Answer the string representation of the current BaseToken. + * + * @return the string representation of the current BaseToken + */ + public String getCurrentAsString() + { + if (m_current == null || m_current.isCompound()) + { + return null; + } + return ((LeafBaseToken) m_current).getValue(); + } + + /** + * Answer the string representation of the next BaseToken. + * + * @return the string representation of the next BaseToken + */ + public String peekNextAsString() + { + BaseToken token = m_data.peek(); + if (token == null || token.isCompound()) + { + return null; + } + return ((LeafBaseToken) token).getValue(); + } + + /** + * Answer the string representation of the next BaseToken. + * + * @return the string representation of the next BaseToken + */ + public BaseToken peekNext() + { + return m_data.peek(); + } + + /** + * Answer the String[] representation of the next two BaseTokens. + * + * @return the String[] representation of the next BaseToken + */ + public String[] peekNext2AsStrings() + { + BaseToken[] tokens = m_data.peek2(); + if (tokens[0] == null || tokens[0].isCompound()) + { + return null; + } + if (tokens[1] == null || tokens[1].isCompound()) + { + return null; + } + + return new String[] { + ((LeafBaseToken) tokens[0]).getValue(), + ((LeafBaseToken) tokens[1]).getValue()}; + } + + + /** + * Answer the string representation of the current BaseToken and advance. + * + * @return the string representation of the current BaseToken + */ + public String getCurrentAsStringWithAdvance() + { + String s = getCurrentAsString(); + next(); + return s; + } + + + // ----- Public OPScanner API ------------------------------------------- + + /** + * Test to see if the current BaseToken's string matches the given string. + * The token table's ignoringCase flag is consulted to see if case matters. + * + * @param sWord the string to match with + * + * @return a boolean indication of the success of the match + */ + public boolean matches(String sWord) + { + return matches(sWord, m_tokenTable.isIgnoringCase()); + } + + /** + * Test to see if the current BaseToken's string matches the given string. + * The given flag controls whether case is interesting. + * + * @param sWord the string to match with + * @param fIgnoreCase the flag that indicates if case is interesting + * + * + * @return a boolean indication of the success of the match + */ + public boolean matches(String sWord,boolean fIgnoreCase) + { + return m_current != null && + m_current.match(sWord, fIgnoreCase); + } + + + /** + * Test to see if the current BaseToken's string matches the given string + * and advance if true. * The token table's ignoringCase flag is consulted + * to see if case matters. + * + * @param sWord the string to match with + * + * @return a boolean indication of the success of the match + */ + public boolean advanceWhenMatching(String sWord) + { + boolean f = matches(sWord); + if (f) + { + next(); + } + return f; + } + + /** + * Advance to the next token and expect it to match the given string. + * If expectations are not met then throw an Exception. The token table's + * ignoringCase flag is consulted to see if case matters. + * + * @param s the string that should match the next token + * + * @return the next OPToken + */ + public OPToken advance(String s) + { + return advance(s, m_tokenTable.isIgnoringCase()); + } + /** + * Advance to the next token and expect it to match the given string. + * If expectations are not met then throw an Exception. The given flag + * controls whether case is interesting. + * + * @param s the string that should match the next token + * @param fIgnoreCase the flag that indicates if case is interesting + * + * @return the next OPToken + */ + public OPToken advance(String s, boolean fIgnoreCase) + { + if (matches(s,fIgnoreCase)) + { + return next(); + } + else + { + throw new OPException("Unfullfilled expectation \""+ + s + "\" not found!"); + } + } + + /** + * Advance to the next token. + * + * @return the next OPToken + */ + public OPToken advance() + { + return next(); + } + + /** + * Remember the current BaseTokenStream and stream over the given stream. + * + * @param ts the BaseTokenStream to now stream over + */ + public void pushStream(BaseTokenStream ts) + { + ts.setLink(m_data); + m_data = ts; + next(); + } + + /** + * Restore the remembered BaseTokenStream as the source of streaming. + */ + public void popStream() + { + BaseTokenStream old = m_data; + m_data = m_data.getLink(); + old.setLink(null); + reset(); + } + + /** + * Enable the token named by the given string. + * + * @param name the name of the OPToken to enable + */ + public void enableTokenNamed(String name) + { + m_tokenTable.enable(name); + } + + /** + * Disable the token named by the given string. + * + * @param name the name of the OPToken to disable + */ + public void disableTokenNamed(String name) + { + m_tokenTable.disable(name); + } + + + // ----- helper functions ---------------------------------------------- + + /** + * Figure out how to map the current BaseToken to a OPToken and return it. + * + * @return the current OPToken + */ + protected OPToken classify() + { + try + { + if (m_current == null) + { + return m_currentToken = EndOPToken.INSTANCE; + } + if (m_current.isNest()) + { + NestedBaseTokens nt = (NestedBaseTokens) m_current; + String start = + Character.toString(nt.getNestStart()); + NestingOPToken orig = + (NestingOPToken) m_tokenTable.lookup(start); + if (orig == null) + { + throw new OPException("Unknown Nesting character '"+ + start + "' found!"); + } + try { + NestingOPToken nestTok = (NestingOPToken) orig.clone(); + return m_currentToken = nestTok.setNest(nt); + } catch (CloneNotSupportedException ex) + { + throw new OPException("Unexpected Nesting Scanning Failure :" + + ex.getMessage()); + } + } + + if (m_current.isPunctuation()) + { + PunctuationBaseToken ptoken = + (PunctuationBaseToken) m_current; + + m_currentToken = m_tokenTable.lookup(ptoken.getValue()); + if (m_currentToken == null) + { + if (m_fStrict) + { + throw new OPException("Unknown punctuation \""+ + ptoken.getValue() + "\" not found!"); + } + else + { + m_currentToken = + m_tokenTable.newIdentifier(ptoken.getValue()); + } + } + return m_currentToken; + } + + if (m_current.isOperator()) + { + OperatorBaseToken otoken =(OperatorBaseToken) m_current; + + m_currentToken = m_tokenTable.lookup(otoken.getValue()); + if (m_currentToken == null) + { + if (m_fStrict) + { + throw new OPException("Unknown Operator \""+ + otoken.getValue() + "\" not found!"); + } + else + { + m_currentToken = + m_tokenTable.newIdentifier(otoken.getValue()); + } + } + return m_currentToken; + } + if (m_current.isLiteral()) + { + LiteralBaseToken bt = (LiteralBaseToken) m_current; + + m_currentToken = + m_tokenTable.newLiteral(bt.getValue(), bt.getType()); + return m_currentToken; + } + if (m_current.isIdentifier()) + { + String id = ((IdentifierBaseToken) m_current).getValue(); + + m_currentToken = m_tokenTable.lookup(id); + if (m_currentToken != null) + { + return m_currentToken; + } + m_currentToken = m_tokenTable.newIdentifier(id); + return m_currentToken; + } + + throw new OPException("Scanner Classification failure on : " + + m_current.toString()); + + } + catch (IndexOutOfBoundsException ex) + { + m_current = null; + m_currentToken = EndOPToken.INSTANCE; + return m_currentToken; + } + } + + // ----- data members --------------------------------------------------- + + /** + * The source of BaseTokens to process + */ + protected BaseTokenStream m_data; + + /** + * The current BaseTokens from data + */ + protected BaseToken m_current; + + /** + * The current OPToken translated from current + */ + protected OPToken m_currentToken; + + /** + * The TokenTable that defines the translation from BaseTokens to OPTokens + */ + protected TokenTable m_tokenTable; + + /** + * The set of valid operators used by this scanner + */ + protected Set m_setOperators; + + /** + * If strict then unknown tokens throw an exception otherwise + * they are turned into an identifier + */ + boolean m_fStrict = false; + + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPToken.java new file mode 100644 index 0000000000000..31926ac6cd782 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/OPToken.java @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; +import com.tangosol.util.HashHelper; + + +/** +* OPToken is the root class for the Top Down Operator Precedence Parsing +* framework's tokens. This framework was first done by Vaughan Pratt in 1973 +* an is undergoing a bit of a renaissance with people that find the typical +* formal grammer tool a bit to heavyweight. +* +* The fundamental idea behind "Pratt Parsers" is that Tokens are objects that +* posses methods that allow them to make precedence decisions, match other +* tokens, and build abstract syntax trees (AST). The central issue of the +* precedence problem is that given an operand object between two operators, +* should the operand be bound to the left operator or the right operator? +* obj1 OP1 obj2 OP2 obj3 like: (1 + 2 * 3) +* Does obj2 bind to OP1 or to OP2? The technique we will use has Token +* objects "know" their precedence levels, and implement methods called "nud" +* (the null denotation in Pratt speak) and "led" (the left denotation). +* A nud method "should" have no interest in the tokens to the left while +* a "led" method does. A nud method is typically used on values such as +* variables and literals and by prefix operators like '-', or 'not'. A led +* method is used by infix operators and suffix operators. A token will often +* have both a nud method and a led method with the canonical example being '-' +* which is both a prefix operator and an infix operator. +* The heart of Pratt's technique is the "expression" function. It takes a +* right binding power that controls how aggressively that it should bind to +* tokens on its right. We also pass the parser to the token objects so that +* their functions may look at context and have access to the tokenizer. +* +* @see OPParser +* +* @author djl 2009.03.14 +*/ +public class OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new OPToken. + */ + public OPToken() + { + } + + /** + * Construct a new OPToken with the given parameters. + * + * @param sId string representation of the literal + */ + public OPToken(String sId) + { + m_sValue = sId; + } + + /** + * Construct a new OPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp The binding precedence for this token + */ + public OPToken(String sId, int nBp) + { + m_sValue = sId; + m_nBindingPower = nBp; + } + + /** + * Construct a new OPToken with the given parameters. + * + * @param sId string representation of the token + * @param sAstName the type code for this literal token + */ + public OPToken(String sId, String sAstName) + { + m_sValue = sId; + m_sLedASTName = sAstName; + } + + /** + * Construct a new OPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp The binding precedence for this token + * @param sAstName the name for this tokens AST + */ + public OPToken(String sId, int nBp, String sAstName) + { + m_sValue = sId; + m_nBindingPower = nBp; + m_sLedASTName = sAstName; + } + + /** + * Construct a new OPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp The binding precedence for this token + * @param sLedASTName the name for this tokens AST + * @param sNudASTName the name for this tokens AST + */ + public OPToken(String sId, int nBp, String sLedASTName, + String sNudASTName) + { + m_sValue = sId; + m_nBindingPower = nBp; + m_sLedASTName = sLedASTName; + m_sNudASTName = sNudASTName; + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * Obtain the power that this token will bind to the left. + * + * @return the left binding power + */ + public int leftBindingPower() + { + return m_nBindingPower; + } + + /** + * Process this token in the context of parser p with the null + * denotation. A nud method typically will have no interest in the token + * to the left. The processing results in an Abstract Syntax Tree Node + * that captures the meaning + * + * @param parser the parser that is the context for parsing + * + * @return an AstNode + */ + public Term nud(OPParser parser) + { + throw new OPException("Unexpected use of " + getId() + + " in prefix position"); + } + + /** + * Process this token and possibly the given leftNodein the context of + * a parser with the left denotation. A led method typically will be + * interested t in the token to the left. The processing results in an + * Abstract Syntax Tree Node that captures the meaning + * + * @param parser the parser that is the context for parsing + * @param leftNode an ast Term that the token is possibly interested in + * + * @return an AstNode + */ + public Term led(OPParser parser, Term leftNode) + { + throw new OPException("Unexpected use of " + getId() + + " in infix position"); + } + + + // ----- AST Factory API ------------------------------------------------ + + /** + * Create an Abstract Syntax Node for the given arguments. If the + * astName argument is not null then use it for the functor and the + * given functor argument become the first child Term. + * + * @param sAstName classification functor or null + * @param sFunctor functor for ast node to be constructed + * + * @return a Term representing the AST + */ + protected Term newAST(String sAstName, String sFunctor) + { + return sAstName == null ? + AtomicTerm.createString(sFunctor) : + Terms.newTerm(m_sLedASTName, AtomicTerm.createString(sFunctor)); + } + + /** + * Create an Abstract Syntax Node for the given arguments. If the + * astName argument is not null then use it for the functor otherwise + * just assume the Term t is good. + * + * @param sAstName classification functor or null + * @param term an Term that is part of the ast + * + * @return a Term representing the AST + */ + protected Term newAST(String sAstName, Term term) + { + return sAstName == null? term : Terms.newTerm(sAstName, term); + } + + /** + * Create an Abstract Syntax Node for the given arguments. If the + * astName argument is not null then use it for the functor and the + * given functor argument become the first child Term. + * + * @param sAstName classification functor or null + * @param sFunctor functor for ast node to be constructed + * @param term an Term that is part of the ast + * + * @return a Term representing the AST + */ + protected Term newAST(String sAstName, String sFunctor, Term term) + { + return sAstName == null ? + Terms.newTerm(sFunctor, term) : + Terms.newTerm(sAstName, AtomicTerm.createString(sFunctor), term); + } + + /** + * Create an Abstract Syntax Node for the given arguments. If the + * astName argument is not null then use it for the functor and the + * given functor argument become the first child Term. + * + * @param sAstName classification functor or null + * @param sFunctor functor for ast node to be constructed + * @param t1 an Term that is part of the ast + * @param t2 an Term that is part of the ast + * + * @return a Term representing the AST + */ + protected Term newAST(String sAstName, String sFunctor, Term t1, Term t2) + { + return sAstName == null ? + Terms.newTerm(sFunctor, t1, t2) : + Terms.newTerm(sAstName, + AtomicTerm.createString(sFunctor), t1, t2); + } + + /** + * Create an Abstract Syntax Node for the given arguments. If the + * astName argument is not null then use it for the functor and the + * given functor argument become the first child Term. + * + * @param sAstName classification functor or null + * @param sFunctor functor for ast node to be constructed + * @param t1 an Term that is part of the ast + * @param t2 an Term that is part of the ast + * @param t3 an Term that is part of the ast + * + * @return a Term representing the AST + */ + protected Term newAST(String sAstName, String sFunctor, + Term t1, Term t2, Term t3) + { + return sAstName == null ? + Terms.newTerm(sFunctor, t1, t2, t3) : + Terms.newTerm(sAstName, + AtomicTerm.createString(sFunctor), t1, t2, t3); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Obtain the string representation of this token. + * + * @return the string representation + */ + public String getId() + { + return m_sValue; + } + + /** + * Set the string representation of this token to the given id. + * + * @param sId the string representation for the token + */ + public void setId(String sId) + { + m_sValue = sId; + } + + /** + * Get The binding precedence of this token. + * + * @return The binding precedence + */ + public int getBindingPower() + { + return m_nBindingPower; + } + + /** + * Set The binding precedence that this token will use for binding to the right. + * + * @param nBp the power power for this token + */ + public void setBindingPower(int nBp) + { + m_nBindingPower = nBp; + } + + /** + * Get nud AST Name for this token + * + * @return the nud ast name for this token + */ + public String getNudASTName() + { + return m_sNudASTName; + } + + /** + * Set the nud AST Name for this token to be the given string + * + * @param sAstName the nud ast name for this token + */ + public void setNudASTName(String sAstName) + { + m_sNudASTName = sAstName; + } + + /** + * Get led AST Name for this token + * + * @return the led ast name for this token + */ + public String getLedASTName() + { + return m_sLedASTName; + } + + /** + * Set the led AST Name for this token to be the given string + * + * @param sAstName the led ast name for this token + */ + public void setLedASTName(String sAstName) + { + m_sLedASTName = sAstName; + } + + /** + * Get a string value that identifies this token + * + * @return the a string that identifies this token + */ + public String getValue() + { + return m_sValue; + } + + /** + * Set the AST Name for this token to be the given string + * + * @param s the ast name for this token + */ + public void setValue(String s) + { + m_sValue = s; + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Return a human-readable description for this token. + * + * @return a String description of the token + */ + public String toString() + { + return getClass().getName() + " " + getValue(); + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + + if (o == null || getClass() != o.getClass()) + { + return false; + } + + OPToken opToken = (OPToken) o; + + return m_sValue == null ? opToken.m_sValue == null + : m_sValue.equals(opToken.m_sValue); + } + + @Override + public int hashCode() + { + return HashHelper.hash(m_sValue, 0); + } + +// ----- constants ------------------------------------------------------ + + /** + * The AST node name for a Binary Operator node. + */ + public static String BINARY_OPERATOR_NODE = "binaryOperatorNode"; + + /** + * The AST node name for a Binding Node. + */ + public static String BINDING_NODE = "bindingNode"; + + /** + * The AST node name for a Method Call Node. + */ + public static String CALL_NODE = "callNode"; + + /** + * The AST node name for a De-referencing Node. + */ + public static String DEREF_NODE = "derefNode"; + + /** + * The AST node name for a Field List Node. + */ + public static String FIELD_LIST = "fieldList"; + + /** + * The AST node name for an Identifier Node. + */ + public static String IDENTIFIER_NODE = "identifier"; + + /** + * The AST node name for a List of Values Node. + */ + public static String LIST_NODE = "listNode"; + + /** + * The AST node name for a Literal Node. + */ + public static String LITERAL_NODE = "literal"; + + /** + * The AST node name for a Unary Operator Node. + */ + public static String UNARY_OPERATOR_NODE = "unaryOperatorNode"; + + // ----- binding precedence constants ----------------------------------- + + /** + * The binding precedence for keyword tokens + */ + public static final int PRECEDENCE_KEYWORD = -1; + + /** + * The binding precedence for identifier tokens + */ + public static final int PRECEDENCE_IDENTIFIER = 1; + + /** + * The binding precedence for assignment operators assignment + * such as = += -= *= /= %= &= ^= |= <<= >>= >>>= + */ + public static final int PRECEDENCE_ASSIGNMENT = 20; + + /** + * The binding precedence for logical tokens such as &&, ||, etc + */ + public static final int PRECEDENCE_LOGICAL = 30; + + /** + * The binding precedence for bitwise logical tokens such as &, |, ^ etc + */ + public static final int PRECEDENCE_LOGICAL_BITWISE = 35; + + /** + * The binding precedence for relational operators such as ==, <=, like, contains etc + */ + public static final int PRECEDENCE_RELATIONAL = 40; + + /** + * The binding precedence for bitwise operators such as >>> >> and << + */ + public static final int PRECEDENCE_BITWISE = 45; + + /** The binding precedence for sum arithmetic, i.e. + and - */ + public static final int PRECEDENCE_SUM = 50; + + /** + * The binding precedence for product arithmetic, multiplication, division, mod * / % + */ + public static final int PRECEDENCE_PRODUCT = 60; + + /** + * The binding precedence for exponent arithmetic, i.e. raising by a power + */ + public static final int PRECEDENCE_EXPONENT = 61; + + /** + * The binding precedence for other unary operators: pre-increment, pre-decrement, plus, minus, + * logical negation, bitwise complement, type cast ++expr --expr +expr -expr ! ~ (type) + */ + public static final int PRECEDENCE_UNARY = 75; + + /** + * The binding precedence for unary post operators such as post-increment and post-decrement + * of the form expr++ or expr-- + */ + public static final int PRECEDENCE_UNARY_POST = 76; + + /** + * The binding precedence for parentheses ( ) [ ] + */ + public static final int PRECEDENCE_PARENTHESES = 80; + + // ----- data members --------------------------------------------------- + + /** + * The string value of the literal. + */ + protected String m_sValue; + + /** + * A functor for building ast for led method. + */ + protected String m_sLedASTName = null; + + /** + * A functor for building ast for nud method. + */ + protected String m_sNudASTName = null; + + /** + * The power that this token binds, typically to the left. + */ + protected int m_nBindingPower = -1; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/ParenOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/ParenOPToken.java new file mode 100644 index 0000000000000..89106e936b264 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/ParenOPToken.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Terms; + + +/** +* ParenOPToken is used to process expressions that are between "(" and ")". +* This can be an arithmetic expression such as (a+(b*c)+d). This class can +* also process the argument list to function calls such as f(a,b,c). Finally +* this class can process list literals such as (1,3,4,5). +* +* @author djl 2009.03.14 +*/ +public class ParenOPToken + extends NestingOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new ParenOpToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public ParenOPToken(String sId, int nBp) + { + super(sId, nBp); + } + + /** + * Construct a new ParenOpToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sLedASTName the name for this tokens AST + * @param sNudASTName the name for this tokens AST + */ + public ParenOPToken(String sId, int nBp, String sLedASTName, + String sNudASTName) + { + super(sId, nBp, sLedASTName, sNudASTName); + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + String sFunctor; + if (leftNode.isAtom()) + { + sFunctor = ((AtomicTerm)leftNode).getValue(); + } + else + { + sFunctor = ((AtomicTerm)leftNode.termAt(1)).getValue(); + } + Term[] aLst = p.readNestedCommaSeparatedList(getNest()); + Term node = Terms.newTerm(sFunctor, aLst); + String sLedASTName = getLedASTName(); + if (sLedASTName == null) + { + return node; + } + else + { + return newAST(sLedASTName, node); + } + } + + /** + * {@inheritDoc} + */ + public Term nud(OPParser p) + { + Term[] aLst = p.readNestedCommaSeparatedList(getNest()); + return aLst.length == 1 ? aLst[0] + : Terms.newTerm((getNudASTName()== null ? getId() : + getNudASTName()),aLst); + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PathOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PathOPToken.java new file mode 100644 index 0000000000000..b11e740008134 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PathOPToken.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; + + +/** +* PathOPToken is used to implement dereferencing paths where you have +* a sequence of identifiers or function calls seperated by a path separator. +* +* @author djl 2009.03.14 +*/ +public class PathOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new PathOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public PathOPToken(String sId, int nBp) + { + super(sId, nBp); + } + + /** + * Construct a new PathOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sLedASTName the name for this tokens AST + */ + public PathOPToken(String sId, int nBp, String sLedASTName) + { + super(sId, nBp, sLedASTName); + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + // Coaless Terms that are the same into one Term that represents + // the path + Term rightNode = p.expression(getBindingPower() - 1); + Term t = leftNode; + String sLedASTName = getLedASTName(); + String sFunctor = sLedASTName == null ? getId() : sLedASTName; + + if (!t.getFunctor().equals(sFunctor)) + { + t = newAST(sFunctor, leftNode); + } + if (rightNode.getFunctor().equals(sFunctor)) + { + for (int i = 1, c = rightNode.length() ; i <= c; ++i) + { + t = t.withChild(rightNode.termAt(i)); + } + } + else + { + t = t.withChild(rightNode); + } + return t; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PeekOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PeekOPToken.java new file mode 100644 index 0000000000000..0da222f1065c5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PeekOPToken.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + +import com.tangosol.coherence.dsltools.termtrees.Term; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A PeekOPToken is a token that contains other {@link OPToken} + * instances. It will defer calls to the {@link #nud(OPParser)} and + * {@link #led(OPParser, Term)} methods to these other tokens. The exact + * instance of the OPToken deferred to will be determined by + * looking ahead at the next token in the token stream and calling the + * contained OPToken with that id. + * + * @author jk 2014.02.12 + * @since Coherence 12.2.1 + */ +public class PeekOPToken + extends OPToken + { + // ----- constructors -------------------------------------------------- + + /** + * Construct a new PeekOPToken with the given parameters. + * + * @param sId string representation of the token + * @param tokens the tokens to defer to + */ + public PeekOPToken(String sId, OPToken... tokens) + { + this(sId, -1, null, null, tokens); + } + + /** + * Construct a new PeekOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBindingPower the precedence binding power of this token + * @param tokens the tokens to defer to + */ + public PeekOPToken(String sId, int nBindingPower, OPToken... tokens) + { + this(sId, nBindingPower, null, null, tokens); + } + + /** + * Construct a new PeekOPToken with the given parameters. + * + * @param sId string representation of the token + * @param sASTName the type code for this token + * @param tokens the tokens to defer to + */ + public PeekOPToken(String sId, String sASTName, OPToken... tokens) + { + this(sId, -1, sASTName, null, tokens); + } + + /** + * Construct a new PeekOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBindingPower the precedence binding power of this token + * @param sASTName the type code for this token + * @param tokens the tokens to defer to + */ + public PeekOPToken(String sId, int nBindingPower, String sASTName, OPToken... tokens) + { + this(sId, nBindingPower, sASTName, null, tokens); + } + + /** + * Construct a new PeekOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBindingPower the precedence binding power of this token + * @param sLedASTName the name for this tokens AST + * @param sNudASTName the name for this tokens AST + * @param aTokens the tokens to defer to + */ + public PeekOPToken(String sId, int nBindingPower, String sLedASTName, String sNudASTName, OPToken...aTokens) + { + super(sId, nBindingPower, sLedASTName, sNudASTName); + Map mapTokens = f_mapTokens = new LinkedHashMap<>(); + + for (OPToken token : aTokens) + { + mapTokens.put(token.getId(), token); + } + } + + // ----- PeekOPToken API ------------------------------------------------ + + /** + * Add the specified {@link OPToken} to the {@link Map} of + * tokens that will be called depending on the next token parsed + * from the token stream. + * + * @param token the {@link OPToken} to call if the next token in the + * stream matches the specified {@link OPToken}s identifier + */ + public void addOPToken(OPToken token) + { + addOPToken(token.getId(), token); + } + + /** + * Add the specified {@link OPToken} to the {@link Map} of + * tokens that will be called depending on the next token parsed + * from the token stream. + * + * @param id the identifier to match with the next token in the + * stream + * @param token the {@link OPToken} to call if the next token in the + * stream matches the specified identifier + */ + public void addOPToken(String id, OPToken token) + { + f_mapTokens.put(id, token); + } + + /** + * Obtain the {@link OPToken} that is mapped to + * the specified identifier. + * + * @param sId the identifier to obtain the mapped {@link OPToken} for + * + * @return the {@link OPToken} mapped to the specified identifier + */ + public OPToken getOPToken(String sId) + { + return f_mapTokens.get(sId); + } + + /** + * The default nud method that will be called if there is no + * {@link OPToken} mapped for the token parsed from the token stream. + *

+ * This method may be overridden in sub-classes that require different + * default processing to the default {@link OPToken#nud(OPParser)} method. + * + * @param parser the current token stream's parser + * + * @return the result of calling the default nud method + */ + protected Term defaultNud(OPParser parser) + { + return super.nud(parser); + } + + /** + * The default led method that will be called if there is no + * {@link OPToken} mapped for the token parsed from the token + * stream. + * + * This method may be overridden in sub-classes that require different + * default processing to the default {@link OPToken#led(OPParser, Term)} + * method. + * + * @param parser the current token stream's parser + * @param leftNode the left node of the current AST + * + * @return the result of calling the default led method + */ + protected Term defaultLed(OPParser parser, Term leftNode) + { + return super.led(parser, leftNode); + } + + // ----- Operator Precedence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term nud(OPParser parser) + { + OPToken token = findMatchingOPToken(parser); + + return token != null + ? token.nud(parser) + : defaultNud(parser); + } + + /** + * {@inheritDoc} + */ + @Override + public Term led(OPParser parser, Term leftNode) + { + OPToken token = findMatchingOPToken(parser); + + return token != null + ? token.led(parser, leftNode) + : defaultLed(parser, leftNode); + } + + // ----- helper methods ------------------------------------------------- + + /** + * Return the {@link OPToken} mapped to the next token in the + * {@link OPParser}'s token stream. + * + * @param parser the OPParser providing the token stream + * + * @return the OPToken matching the next token in the stream or + * null if there is no {@link OPToken} mapped to the next + * token in the stream. + */ + protected OPToken findMatchingOPToken(OPParser parser) + { + for (Map.Entry entry : f_mapTokens.entrySet()) + { + OPScanner scanner = parser.getScanner(); + + if (scanner.advanceWhenMatching(entry.getKey())) + { + return entry.getValue(); + } + } + + return null; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Map} of {@link OPToken} instances that will be called + * depending on the value of the next token in the stream. + */ + protected final Map f_mapTokens; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PrefixOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PrefixOPToken.java new file mode 100644 index 0000000000000..ac2fa61b7c7e2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PrefixOPToken.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import com.tangosol.coherence.dsltools.termtrees.Term; + + +/** +* PrefixOPToken is used to implement prefix operators. Prefix operators +* are things like not, new, or "~". +* +* @author djl 2009.03.14 +*/ +public class PrefixOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new PrefixOPToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBP the binding power for this token + */ + public PrefixOPToken(String sId, int nBP) + { + super(sId, nBP); + } + + /** + * Construct a new PrefixOPToken with the given parameters. + * + * @param sId string representation of the token + * @param nBp the binding power for this token + * @param sASTName the name for this tokens AST + */ + public PrefixOPToken(String sId, int nBp, String sASTName) + { + this(sId, nBp); + m_sNudASTName = sASTName; + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term nud(OPParser p) + { + return newAST( + getNudASTName(), + getId(), + p.expression(getBindingPower())); + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PunctuationOPToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PunctuationOPToken.java new file mode 100644 index 0000000000000..6db36db7cee62 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/PunctuationOPToken.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +/** +* PunctuationOPToken is used in situations where you need a place holder to +* test for something like a ",". There is typically not useful +* to ASTs but serves as a separtor. +* +* @author djl 2009.03.14 +*/ +public class PunctuationOPToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new PunctuationOPToken with the given parameters. + * + * @param sId string identifier for this token + */ + public PunctuationOPToken(String sId) + { + super(sId); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/TokenTable.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/TokenTable.java new file mode 100644 index 0000000000000..cdb9361eb3c46 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/precedence/TokenTable.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.precedence; + + +import java.util.HashMap; +import java.util.HashSet; + + +/** +* TokenTable is a helper class used by Scanners to convert BaseTokens to +* to OPTokens. TokenTables have protocol to add tokens under a name, alias +* tokens, and control the enabeleded state of tokens. +* +* @author djl 2009.03.14 +*/ +public class TokenTable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new OPToken. + */ + public TokenTable() + { + } + + /** + * Construct a new TokenTable with the given parameters to set the + * ast names for identifiers and literals. + * + * @param sIdentifierASTName identifiers ast names + * @param sLiteralASTName literals ast name + */ + public TokenTable(String sIdentifierASTName, String sLiteralASTName) + { + m_sIdentifierASTName = sIdentifierASTName; + m_sIiteralASTName = sLiteralASTName; + } + + /** + * Use given boolean to decide whether to ignore case for keyword + * operations + * + * @param fIgnore the boolean to use to control case interest + * + */ + public void setIgnoreCase(boolean fIgnore) + { + m_fIgnoreCase = fIgnore; + } + + /** + * Answer wheather the receiver is ignoringCase + * + * @return the ignoreCase flag + */ + public boolean isIgnoringCase() + { + return m_fIgnoreCase; + } + + /** + * Lookup an OPToken by name + * + * @param sName the name of the token to lookup + * + * @return the token found under name or null if absent + */ + public OPToken lookup(String sName) + { + String nm = m_fIgnoreCase ? sName.toLowerCase() : sName; + if (m_disabled.contains(nm)) + { + return null; + } + Object o = m_table.get(nm); + if (o == null) + { + return null; + } + else + { + return (OPToken) o; + } + } + + /** + * Add a token t under the given name + * + * @param sName string identifier for this token + * @param token the token to add + * + * @return the given token + */ + public OPToken addToken(String sName, OPToken token) + { + m_table.put(sName, token); + return token; + } + + /** + * Add a token t under the given name with given ast name + * + * @param sName string identifier for this token + * @param token the token to add + * @param sASTName the token's led symbol + * + * @return the given token + */ + public OPToken addToken(String sName, OPToken token, String sASTName) + { + m_table.put(sName, token); + token.setLedASTName(sASTName); + return token; + } + + /** + * Add a token t under the given name with given led and nud names + * + * @param sName string identifier for this token + * @param token the token to add + * @param sLedName the token's led symbol + * @param sNudName the token's nud symbol + * + * @return the given token + */ + public OPToken addToken(String sName, OPToken token, String sLedName, + String sNudName) + { + m_table.put(sName, token); + token.setLedASTName(sLedName); + token.setNudASTName(sNudName); + return token; + } + + + /** + * Add a token t under the id stored in the token + * + * @param token the token to add + * + * @return the given token + */ + public OPToken addToken(OPToken token) + { + m_table.put(token.getId(), token); + return token; + } + + /** + * Add a token t with given ast name + * + * @param token the token to add + * @param sASTName the token to add + * + * @return the given token + */ + public OPToken addToken(OPToken token, String sASTName) + { + m_table.put(token.getId(), token); + token.setLedASTName(sASTName); + return token; + } + + /** + * Add a token alias under the given name for a token already installed + * + * @param sName string identifier for token alias + * @param sInstalled the name of a token already in the table + */ + public void alias(String sName, String sInstalled) + { + Object o = m_table.get(sInstalled); + if (o != null) + { + m_table.put(sName, o); + } + } + + /** + * Disable an installed token stored under the given name + * + * @param sName string identifier for token to disable + */ + public void disable(String sName) + { + m_disabled.add(sName); + } + + /** + * Enable an installed token stored under the given name + * + * @param sName string identifier for token to enable + */ + public void enable(String sName) + { + m_disabled.remove(sName); + } + + + // ----- token factory -------------------------------------------------- + + /** + * Construct a new literal OPToken instance with the given parameters. + * + * @param sValue string representation of the literal + * @param nTypeCode the type code for this literal token + * + * @return the token of some appropriate class + */ + public OPToken newLiteral(String sValue, int nTypeCode ) + { + return new LiteralOPToken(sValue, nTypeCode, m_sIiteralASTName); + } + + /** + * Construct a new identifier OPToken instance with the given id. + * + * @param sValue string representation of the literal + * + * @return the token of some appropriate class + */ + public OPToken newIdentifier(String sValue) + { + return new IdentifierOPToken(sValue, m_sIdentifierASTName); + } + + + // ----- data members --------------------------------------------------- + + /** + * The Map that holds the name to token mappings + */ + HashMap m_table = new HashMap(); + + /** + * A set of token names that are currently disabled + */ + HashSet m_disabled = new HashSet(); + + /** + * A ast name for literals + */ + boolean m_fIgnoreCase = false; + + /** + * A ast name for identifiers + */ + String m_sIdentifierASTName = null; + + /** + * A ast name for literals + */ + String m_sIiteralASTName = null; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termlanguage/ColonToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termlanguage/ColonToken.java new file mode 100644 index 0000000000000..c46af3d9044a0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termlanguage/ColonToken.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.termlanguage; + + +import com.tangosol.coherence.dsltools.precedence.OPToken; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.Terms; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; + + +/** +* Colon is used to make attributes in a list or bag. The syntax is a:b +* where b is any Term.The results is the Term .attr.(a(b)) +* +* @author djl 2009.08.31 +*/ +public class ColonToken + extends OPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new ColonToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public ColonToken(String sId, int nBp) + { + super(sId, nBp); + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + Term rightNode = p.expression(getBindingPower() - 1); + if (leftNode instanceof AtomicTerm) + { + String sKey = ((AtomicTerm)leftNode).getValue(); + return Terms.newTerm(".attr.",Terms.newTerm(sKey,rightNode)); + } + else + { + return Terms.newTerm(".pair.",leftNode,rightNode); + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termlanguage/CurlyToken.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termlanguage/CurlyToken.java new file mode 100644 index 0000000000000..ff7111f2d044f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termlanguage/CurlyToken.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.termlanguage; + + +import com.tangosol.coherence.dsltools.precedence.NestingOPToken; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.termtrees.Term; +import com.tangosol.coherence.dsltools.termtrees.AtomicTerm; +import com.tangosol.coherence.dsltools.termtrees.Terms; + + +/** +* CurlyToken is used to process expressions between bracketing characters +* such as are between "{" and "}" which should result in a bag +* such as {1,3,4,5}. It can be used as a literal or with a functor. +* +* @author djl 2009.08.31 +*/ +public class CurlyToken + extends NestingOPToken + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new CurlyToken with the given parameters. + * + * @param sId string identifier for this token + * @param nBp the binding power for this token + */ + public CurlyToken(String sId, int nBp) + { + super(sId, nBp); + } + + + // ----- Operator Presidence API ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Term led(OPParser p, Term leftNode) + { + Term[] aLst = p.readNestedCommaSeparatedList(getNest()); + if (leftNode instanceof AtomicTerm) + { + String sFunctor = ((AtomicTerm)leftNode).getValue(); + return Terms.newTerm(sFunctor, Terms.newTerm(".bag.",aLst)); + } + else + { + return Terms.newTerm(".object.",leftNode,Terms.newTerm(".bag.",aLst)); + } + } + + /** + * {@inheritDoc} + */ + public Term nud(OPParser p) + { + Term[] aLst = p.readNestedCommaSeparatedList(getNest()); + return Terms.newTerm(".bag.",aLst); + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termlanguage/TermLanguage.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termlanguage/TermLanguage.java new file mode 100644 index 0000000000000..d417683423d1b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termlanguage/TermLanguage.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.dsltools.termlanguage; + + +import com.tangosol.coherence.dsltools.base.LiteralBaseToken; + +import com.tangosol.coherence.dsltools.precedence.ParenOPToken; +import com.tangosol.coherence.dsltools.precedence.PathOPToken; +import com.tangosol.coherence.dsltools.precedence.PunctuationOPToken; +import com.tangosol.coherence.dsltools.precedence.ListOpToken; +import com.tangosol.coherence.dsltools.precedence.LiteralOPToken; +import com.tangosol.coherence.dsltools.precedence.TokenTable; + + +/** +* TermLanguage is a simple language for building Terms. It is a superset +* of JSON. A Term can be a, Atom which is a literal which can be a +* Number, String, or Symbol or the constants true, false, or null. This +* results in an AtomicTerm. A term can also be a "functor" applied to a list +* of arguments that are themselves Terms. This results in a NodeTerm. +* Nested list Terms are syntaticaly supported by enclosing a list of Terms +* between "[" and "]". This list is translated to a NodeTerm with a functor +* of ".list.". The characters "{" and"}" create NodeTerms that are similar +* to lists but imply no ordering hence we translate to a NodeTerm with a +* functor of ".bag.". If curlies are used with a functor then we create a +* special NodeTerm whose distunguised single element is the bag of elements. +* This list is translated to a NodeTerm with a functor of ".list.". The ":" +* character is used as a shorthand for denoting attributes where a:b is +* translated to the NodeTerm ".attr.(a(b)).Finally, ";" is used to build +* sequences. A run of Terms separated by ";" results in a special NodeTerm +* with functor ".sequence." +* +* Much of our inspiration came from the E-Language's +* @see TermL +* and +* @see Mathematica +* +* examples: +* +* "a" -> AtomicTerm("a", String) +* 2 -> AtomicTerm("2",Integer) +* a -> AtomicTerm("a",Symbol) +* f(x) -> NodeTerm("f", AtomicTerm("a", Symbol)) +* +* the rest of the example translations will use literal Term Language. +* [1,2,3] -> .list.(1,2,3) +* {1,3,3} -> .bag.(1,2,3) +* foo(a:"b" z:[1,2,[3]]) -> foo(.attr.(a(b), .attr.(.list.(1,2,.list.(3))) +* obj{a:1, b:2} -> obj(.bag.(.attr.(a(1)), .attr.(b(2)) +* if( f(a), a, else: do(a);do(b);compute(c)) -> +* if(f(a), a, .attr.(else(.sequence.(do(a),do(b),compute(c)))) +* +* @author djl 2009.08.31 +*/ +public class TermLanguage + { + /** + * Return an initialized TokenTable for the Term Language. + * + * @return a TokenTable for the Term Language + */ + public static TokenTable tokenTable() + { + if (s_tokens == null) + { + s_tokens = new TokenTable(); + s_tokens.addToken("true", + new LiteralOPToken(LiteralBaseToken.createBoolean("true"))); + s_tokens.addToken("false", + new LiteralOPToken(LiteralBaseToken.createBoolean("false"))); + s_tokens.addToken("null", + new LiteralOPToken(LiteralBaseToken.createNull("null"))); + s_tokens.addToken(new ParenOPToken("(", 80, null, ".list.")); + s_tokens.addToken(new ListOpToken("[", 80,".list.")); + s_tokens.addToken(new CurlyToken("{", 80)); + s_tokens.addToken(new ColonToken(":", 80)); + s_tokens.addToken(new PathOPToken(";", 80, ".sequence.")); + s_tokens.addToken(new PunctuationOPToken(",")); + } + return s_tokens; + } + + + // ----- static data members -------------------------------------------- + + /** + * The TokenTable. + */ + private static TokenTable s_tokens; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/AtomicTerm.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/AtomicTerm.java new file mode 100644 index 0000000000000..7ac1cf3d4b666 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/AtomicTerm.java @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.termtrees; + + +/** +* AtomicTerms is the class used to represent literal Terms such as String +* and Numbers. The functor() method for AtomicTerms return a type name for +* the stored literals. +* +* @author djl 2009.08.31 +*/ +public class AtomicTerm + extends Term + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new AtomicTerm with the given parameters. + * + * @param sValue the String representation of the literal + * @param nType the type code for the given literal + */ + public AtomicTerm(String sValue, int nType) + { + m_sValue = sValue; + m_nTypeCode = nType; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Obtain the typecode for the node. + * + * @return the typecode + */ + public int getTypeCode() + { + return m_nTypeCode; + } + + /** + * Obtain the string value for the node. + * + * @return the string value + */ + public String getValue() + { + return m_sValue; + } + + // ----- Term API ------------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String getFunctor() + { + switch (m_nTypeCode) + { + case STRINGLITERAL: return "String"; + case SHORTLITERAL: return "Short"; + case INTEGERLITERAL: return "Integer"; + case FLOATLITERAL: return "Float"; + case LONGLITERAL: return "Long"; + case DOUBLELITERAL: return "Double"; + case BOOLEANLITERAL: return "Boolean"; + case NULLLITERAL: return "Null"; + case SYMBOLLITERAL: return "Symbol"; + default: return null; + } + } + + /** + * {@inheritDoc} + */ + public String fullFormString() + { + return toString(); + } + + /** + * {@inheritDoc} + */ + public boolean isNumber() + { + return m_nTypeCode >= INTEGERLITERAL && m_nTypeCode <= DOUBLELITERAL; + } + + /** + * {@inheritDoc} + */ + public Term withChild(Term t) + { + return new NodeTerm(".list.",this,t); + } + + /** + * {@inheritDoc} + */ + public Term[] children() + { + return new Term[0]; + } + + /** + * {@inheritDoc} + */ + public Term termAt(int index) + { + if (index == 0) + { + return new AtomicTerm(getFunctor(), STRINGLITERAL); + } + return null; + } + + /** + * {@inheritDoc} + */ + public Term findChild(String fn) + { + return null; + } + + /** + * {@inheritDoc} + */ + public boolean termEqual(Term t) + { + if (t == null) + { + return false; + } + if (t.isAtom()) + { + AtomicTerm ta = (AtomicTerm) t; + if (m_nTypeCode == ta.m_nTypeCode) + { + if (m_sValue == ta.m_sValue) + { + return true; + } + else + { + if (m_sValue == null || ta.m_sValue == null) + { + return false; + } + else + { + return m_sValue.equals(ta.m_sValue); + } + } + } + } + return false; + } + + + // ----- AtomicTerm API ------------------------------------------------- + + /** + * Obtain the Object representation of the node. This will be one of + * the java Types String, Integer, Long, Float, Double, or Boolean. + * + * @return the Object + */ + public Object getObject() + { + String s = m_sValue; + switch (m_nTypeCode) + { + case STRINGLITERAL: return s; + case SHORTLITERAL: return Short.valueOf(s); + case INTEGERLITERAL: return Integer.valueOf(s); + case FLOATLITERAL: return Float.valueOf(s); + case LONGLITERAL: return Long.valueOf(s); + case DOUBLELITERAL: + if ("nan".equalsIgnoreCase(s)) + { + return Double.NaN; + } + else if ("infinity".equalsIgnoreCase(s)) + { + return Double.POSITIVE_INFINITY; + } + return Double.valueOf(s); + case BOOLEANLITERAL: return Boolean.valueOf(m_sValue); + case NULLLITERAL: return null; + case SYMBOLLITERAL: return s; + default: return null; + } + } + + /** + * Obtain the Number representation of the node. + * + * @return the Comparable + */ + public Number getNumber() + { + String s = m_sValue; + switch (m_nTypeCode) + { + case SHORTLITERAL: return Short.valueOf(s); + case INTEGERLITERAL: return Integer.valueOf(s); + case FLOATLITERAL: return Float.valueOf(s); + case LONGLITERAL: return Long.valueOf(s); + case DOUBLELITERAL: return Double.valueOf(s); + default: return null; + } + } + + /** + * Make negative the given number that supposedly came from this node. + * + * @param num a Number that was created by this node + * + * @return a negated + */ + public Number negativeNumber(Number num) + { + switch (m_nTypeCode) + { + case SHORTLITERAL: return Short.valueOf((short) -num.shortValue()); + case INTEGERLITERAL: return Integer.valueOf(0 - num.intValue()); + case FLOATLITERAL: return Float.valueOf((float) (0.0 - num.floatValue())); + case LONGLITERAL: return Long.valueOf(0l - num.longValue()); + case DOUBLELITERAL: return Double.valueOf(0.0d - num.doubleValue()); + default: return null; + } + } + + /** + * Make negavite the representation of this node. + * + */ + public void negate() + { + if (m_sValue.startsWith("-")) + { + m_sValue = m_sValue.substring(1); + } + else + { + m_sValue = "-" + m_sValue; + } + } + + /** + * Test whether the value is of a valid number format. + * + * @return the the results of testing for numeric format validity + */ + public boolean isValidNumber() + { + String s = m_sValue; + try + { + switch (m_nTypeCode) + { + case SHORTLITERAL: + Short.valueOf(s); + break; + + case INTEGERLITERAL: + Integer.valueOf(s); + break; + + case FLOATLITERAL: + Float.valueOf(s); + break; + + case LONGLITERAL: + Long.valueOf(s); + break; + + case DOUBLELITERAL: + Double.valueOf(s); + break; + + default: return false; + } + } + catch (NumberFormatException ex) + { + return false; + } + return true; + } + + + // ----- TermWalker methods --------------------------------------------- + + public void accept(TermWalker walker) + { + walker.acceptAtom(getFunctor(), this); + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Return a human-readable description for this Node. + * + * @return a String description of the Node + */ + public String toString() + { + int nt = m_nTypeCode; + StringBuffer str = new StringBuffer(); + if (nt == STRINGLITERAL) + { + str.append('"'); + } + str.append(getValue()); + if (nt == FLOATLITERAL) + { + str.append('f'); + } + else if (nt == LONGLITERAL) + { + str.append('l'); + } + else if (nt == SHORTLITERAL) + { + str.append('s'); + } + else if (nt == STRINGLITERAL) + { + str.append('"'); + } + return str.toString(); + } + + + // ----- static literal creation healper's ----------------------------- + + /** + * Create new AtomicTerm representing a String with given value + * + * @param value the text of the literal + * + * @return a AtomicTerm for a String + */ + public static AtomicTerm createString(String value) + { + return new AtomicTerm(value,STRINGLITERAL); + } + + /** + * Create new AtomicTerm representing a Short with given value + * + * @param value the text of the literal + * + * @return a AtomicTerm for a Integer + */ + public static AtomicTerm createShort(String value) + { + return new AtomicTerm(value, SHORTLITERAL); + } + + /** + * Create new AtomicTerm representing a Integer with given value + * + * @param value the text of the literal + * + * @return a AtomicTerm for a Integer + */ + public static AtomicTerm createInteger(String value) + { + return new AtomicTerm(value, INTEGERLITERAL); + } + + /** + * Create new AtomicTerm representing a Long with given value + * + * @param value the text of the literal + * + * @return a AtomicTerm for a Long + */ + public static AtomicTerm createLong(String value) + { + return new AtomicTerm(value, LONGLITERAL); + } + + /** + * Create new AtomicTerm representing a float with given value + * + * @param value the text of the literal + * + * @return a AtomicTerm for a Float + */ + public static AtomicTerm createFloat(String value) + { + return new AtomicTerm(value, FLOATLITERAL); + } + + /** + * Create new AtomicTerm representing a Double with given value + * + * @param value the text of the literal + * + * @return a AtomicTerm for a Double + */ + public static AtomicTerm createDouble(String value) + { + return new AtomicTerm(value, DOUBLELITERAL); + } + + /** + * Create new AtomicTerm representing a Boolean with given value + * + * @param value the text of the literal + * + * @return a AtomicTerm for a boolean + */ + public static AtomicTerm createBoolean(String value) + { + return new AtomicTerm(value, BOOLEANLITERAL); + } + + /** + * Create new AtomicTerm representing a null with given value + * + * @param value the text of the literal + * + * @return a AtomicTerm for a null + */ + public static AtomicTerm createNull(String value) + { + return new AtomicTerm(value, NULLLITERAL); + } + /** + * Create new AtomicTerm representing a null. + * + * @return a AtomicTerm for a null + */ + public static AtomicTerm createNull() + { + return new AtomicTerm("null", NULLLITERAL); + } + + /** + * Create new AtomicTerm representing a Symbol with given value + * + * @param value the text of the literal + * + * @return a AtomicTerm for a Symbol + */ + public static AtomicTerm createSymbol(String value) + { + return new AtomicTerm(value, SYMBOLLITERAL); + } + // ----- constants ------------------------------------------------------ + + /** + * The numberic code for a string literal + */ + public static final int STRINGLITERAL = 1; + + /** + * The numberic code for a integer literal + */ + public static final int INTEGERLITERAL = 2; + + /** + * The numberic code for a long literal + */ + public static final int LONGLITERAL = 3; + + /** + * The numberic code for a float literal + */ + public static final int FLOATLITERAL = 4; + + /** + * The numberic code for a double literal + */ + public static final int DOUBLELITERAL = 5; + + /** + * The numberic code for a boolean literal + */ + public static final int BOOLEANLITERAL = 6; + + /** + * The numberic code for a boolean literal + */ + public static final int NULLLITERAL = 7; + + /** + * The numberic code for a symbol literal + */ + public static final int SHORTLITERAL = 8; + + /** + * The numberic code for a symbol literal + */ + public static final int SYMBOLLITERAL = 9; + + // ----- data members --------------------------------------------------- + + /** + * The string value of this node. + */ + String m_sValue; + + /** + * The type code for this node. + */ + int m_nTypeCode; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/NodeTerm.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/NodeTerm.java new file mode 100644 index 0000000000000..0980d8d198693 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/NodeTerm.java @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.termtrees; + + +/** +* NodeTerm is the class used to represent trees of Terms that can have +* children. +* +* @author djl 2009.08.31 +*/ +public class NodeTerm + extends Term + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new TermNode with the given functor. + * + * @param sFunctor the functor for the Term + */ + public NodeTerm(String sFunctor) + { + this(sFunctor, new Term[0]); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + */ + public NodeTerm(String sFunctor, Term t1) + { + this(sFunctor, new Term[] {t1}); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + * @param t2 a child term + */ + public NodeTerm(String sFunctor, Term t1, Term t2) + { + this(sFunctor, new Term[] {t1, t2}); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + * @param t2 a child term + * @param t3 a child term + */ + public NodeTerm(String sFunctor, Term t1, Term t2, Term t3) + { + this(sFunctor, new Term[] {t1, t2, t3}); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + * @param t2 a child term + * @param t3 a child term + * @param t4 a child term + */ + public NodeTerm(String sFunctor, Term t1, Term t2, Term t3, Term t4) + { + this(sFunctor, new Term[] {t1, t2, t3, t4}); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + * @param t2 a child term + * @param t3 a child term + * @param t4 a child term + * @param t5 a child term + */ + public NodeTerm(String sFunctor, Term t1, Term t2, Term t3, Term t4, + Term t5) + { + this(sFunctor, new Term[] {t1, t2, t3, t4, t5}); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param aTerms children of the node + */ + public NodeTerm(String sFunctor, Term[] aTerms) + { + m_sFunctor = sFunctor; + m_aTerms = aTerms; + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param term a term + * @param aTerms children of the node + */ + public NodeTerm(String sFunctor, Term term, Term[] aTerms) + { + m_sFunctor = sFunctor; + m_aTerms = new Term[aTerms.length +1]; + m_aTerms[0] = term; + for (int i =1, c = m_aTerms.length; i < c; i++) + { + m_aTerms[i] = aTerms[i-1]; + } + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param aTerms children of the node + * @param term a term + */ + public NodeTerm(String sFunctor, Term[] aTerms, Term term) + { + int c = aTerms.length; + m_sFunctor = sFunctor; + m_aTerms = new Term[aTerms.length +1 ]; + m_aTerms[aTerms.length] = term; + + for (int i =0; i < c; i++) + { + m_aTerms[i] = aTerms[i]; + } + } + + + // ----- Term API ------------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String getFunctor() + { + return m_sFunctor; + } + + /** + * {@inheritDoc} + */ + public boolean isLeaf() + { + return m_aTerms.length > 0; + } + + /** + * {@inheritDoc} + */ + public boolean isAtom() + { + return false; + } + + /** + * {@inheritDoc} + */ + public Term[] children() + { + return m_aTerms; + } + + /** + * {@inheritDoc} + */ + public int length() + { + return m_aTerms.length; + } + + /** + * {@inheritDoc} + */ + public Term termAt(int index) + { + if (index == 0) + { + return AtomicTerm.createSymbol(getFunctor()); + } + return m_aTerms[index-1]; + } + + /** + * {@inheritDoc} + */ + public Term withChild(Term t) + { + Term[] temp = new Term[m_aTerms.length + 1]; + for (int i = 0, c = m_aTerms.length; i< c; i++) + { + temp[i] = m_aTerms[i]; + } + temp[m_aTerms.length] = t; + m_aTerms = temp; + return this; + } + + /** + * {@inheritDoc} + */ + public boolean termEqual(Term t) + { + if (t == null) + { + return false; + } + if (t.isAtom()) + { + return false; + } + NodeTerm nt = (NodeTerm)t; + if (!m_sFunctor.equals(nt.m_sFunctor)) + { + return false; + } + int count = m_aTerms.length; + Term[] aMyTerms = m_aTerms; + Term[] aOtherTerms = nt.m_aTerms; + if (count != aOtherTerms.length) + { + return false; + } + if (count == 0) + { + return true; + } + for (int i = 0; i < count; ++i) + { + if (!aMyTerms[i].termEqual(aOtherTerms[i])) + { + return false; + } + } + return true; + } + /** + * {@inheritDoc} + */ + public String fullFormString() + { + StringBuffer ans = new StringBuffer(); + ans.append(m_sFunctor); + ans.append("("); + for (int i = 0, c = m_aTerms.length; i < c; i++) + { + ans.append(m_aTerms[i].fullFormString()); + if (i != m_aTerms.length-1) + { + ans.append(", "); + } + } + ans.append(")"); + return ans.toString(); + } + + + // ----- TermWalker methods --------------------------------------------- + + public void accept(TermWalker walker) + { + walker.acceptNode(getFunctor(),this); + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Return a human-readable description for this Node. + * + * @return a String description of the Node + */ + public String toString() + { + StringBuffer ans = new StringBuffer(); + + if (m_sFunctor.equals(".attr.")) + { + Term t = m_aTerms[0]; + ans.append(t.getFunctor()); + ans.append(":"); + ans.append(t.termAt(1).toString()); + return ans.toString(); + } + if (m_sFunctor.equals(".pair.")) + { + Term t = m_aTerms[0]; + Term t2 = m_aTerms[1]; + ans.append(t.toString()); + ans.append(":"); + ans.append(t2.toString()); + return ans.toString(); + } + if (!m_sFunctor.equals(".bag") && length() == 1 + && m_aTerms[0].getFunctor().equals(".bag.")) + { + ans.append(m_sFunctor); + ans.append(m_aTerms[0].toString()); + return ans.toString(); + } + if (m_sFunctor.equals(".sequence.")) + { + for (int i = 0; i < m_aTerms.length; i++) + { + ans.append(m_aTerms[i].toString()); + if (i != m_aTerms.length-1) + { + ans.append("; "); + } + } + return ans.toString(); + } + + if (m_sFunctor.equals(".list.")) + { + ans.append("["); + } + else if (m_sFunctor.equals(".bag.")) + { + ans.append("{"); + } + else + { + ans.append(m_sFunctor); + ans.append("("); + } + for (int i = 0; i < m_aTerms.length; i++) + { + ans.append(m_aTerms[i].toString()); + if (i != m_aTerms.length-1) + { + ans.append(", "); + } + } + if (m_sFunctor.equals(".list.")) + { + ans.append("]"); + } + else if (m_sFunctor.equals(".bag.")) + { + ans.append("}"); + } + else + { + ans.append(")"); + } + return ans.toString(); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Set the receiver's functor to be the given String + * + * @param sFunctor the new functor for this Term + * + */ + public void setFunctor(String sFunctor) + { + m_sFunctor = sFunctor; + } + + + // ----- data members --------------------------------------------------- + + /** + * The functor for this node. + */ + String m_sFunctor; + + /** + * The children Terms of this node. + */ + Term[] m_aTerms; + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/Term.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/Term.java new file mode 100644 index 0000000000000..136dde3d3dfb8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/Term.java @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.termtrees; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * Term is the abstract class used to represent trees of Terms (term-trees). + * A term (a node of a term tree) consists of a special label called a + * functor and a sequence of child terms (called arguments). Strings, Numbers, + * and Symbols are atomic-terms and are the leaves of a term-tree, i.e. they + * have no children. + * + * Trees of symbols lie midway between sequences of symbols and graphs of + * symbols in expressiveness and therefor represent somewhat of a sweet-spot + * in representation space. Historically, term-trees can be found in Prolog + * and Mathematica and are used for many of the same purposes that others use + * S-Expressions or XML. All are generic means for representing trees of + * symbols, and are useful for representing a great variety of kinds of data + * + * There exist encodings that freely go between S-Expressions or XML into + * term-trees. Powerful matching techniques exist that operate over + * term-trees and at this point in time are 50 years old. Techniques that are + * 60 years old show how powerful term-trees can be as the implementation + * vehicle for interpreters and compilers. Finally, + * "Term Language" (TL), the literal expression of term-trees is much more + * readable and writable than either S-expressions or XML and is an excelent. + * way to test the output of a parser by comparing a simple literal. + * + * The Term protocol distunguishes between atoms (such as literals) and nodes + * with children, and leaf nodes that are atoms or nodes without children. + * All Terms have a functor which acts somewhat like a type or classifier. + * + * Term-trees are especially useful as Abstract Syntax Trees (AST) and in + * implementing expression languages. + * + * @author djl 2009.08.31 + */ +public abstract class Term + implements Iterable + { + + // ----- Term API ------------------------------------------------------- + + /** + * Obtain the functor representation of the Term. + * + * @return the functor + */ + public abstract String getFunctor(); + + /** + * Obtain the child term at the given index. The index is 1 based + * for children and with at(0) returning the functor as an AtomicTerm. + * Beware, your 0 based habits can cause problems but 1 based indexing + * is useful since the functor is an interesting part of the information + * space. We are bowing here to the wisdom of Mathematica Expressions. + * + * @param index index of the child or functor to return + * + * @return the child Term or functor as AtomicTerm if index is 0 + */ + public abstract Term termAt(int index); + + /** + * Obtain the childern Terms + * + * @return the children of the receiver + */ + public abstract Term[] children(); + + /** + * Join the receiver with the given child Term. AtomicTerms will + * construct a general list term (functor .list.) and NodeTerms + * may be mutated. + * + * @param t the term to join with + * + * @return the Term resulting from joining. + */ + public abstract Term withChild(Term t); + + /** + * Answer whether the receiver is equal to the given Term. + * Terms are equal if their functors are equal and their children + * are termEqual to the children of the given term. + * + * @param t the Term to check for termEqual + * + * @return the boolean result of the comparison + */ + public abstract boolean termEqual(Term t); + + /** + * Answer a String representation of the Term that is allowed to + * show more internal details than toString() + * which does not compress information. Similar to Object.toString(). + * + * @return a String representation of the receiver + */ + public abstract String fullFormString(); + + /** + * Answer whether the receiver has children. + * + * @return the boolean result of the isLeaf() test + */ + public boolean isLeaf() + { + return true; + } + + /** + * Answer whether the receiver is an Atomic Term. + * + * @return the boolean result of the isAtom() test + */ + public boolean isAtom() + { + return true; + } + + /** + * Answer whether the receiver is an Atomic Term representing a Number. + * + * @return the boolean result of the isNumber() test + */ + public boolean isNumber() + { + return false; + } + + /** + * Answer whether the length of the receivers children + * + * @return the length + */ + public int length() + { + return children().length; + } + + // ----- Term search and matching api ----------------------------------- + + /** + * Find the Term amoungst the children whose functor equals the + * given functor. + * + * @param sFunctor the functor to search for + * + * @return the found Term or null if not found + */ + public Term findChild(String sFunctor) + { + Term[] aTerms = children(); + + for (int i = 0, c = aTerms.length; i < c; ++i) + { + Term t = aTerms[i]; + if (sFunctor.equals(t.getFunctor())) + { + return t; + } + } + + return null; + } + + /** + * Find the Term amoungst the children whose functor equals the + * given functor that has a singleton child. + * + * @param sFunctor the functor to search for + * + * @return the found Term's first child or null if not found + */ + public Term findAttribute(String sFunctor) + { + Term[] aTerms = children(); + + for (int i = 0, c = aTerms.length; i < c; i++) + { + Term t = aTerms[i]; + if (sFunctor.equals(t.getFunctor())) + { + if (t.length() == 1) + { + return t.termAt(1); + } + } + } + + return null; + } + + /** + * Answer whether the receiver's children is equal to the given Terms + * children. Terms are equal if their functors are equal and their children + * are termEqual to the children of the given term. + * + * @param t term whose children are to be checked for equality + * + * @return the found Term or null if not found + */ + public boolean childrenTermEqual(Term t) + { + if (t == null) + { + return false; + } + + Term[] aMyTerms = children(); + int count = aMyTerms.length; + Term[] aOtherTerms = t.children(); + + if (count != aOtherTerms.length) + { + return false; + } + + if (count == 0) + { + return true; + } + + for (int i = 0; i < count; ++i) + { + if (!aMyTerms[i].termEqual(aOtherTerms[i])) + { + return false; + } + } + + return true; + } + + /** + * Find the Term amongst the children whose functor equals the + * given functor. + * + * @param t the functor to search for + * + * @return the found Term or null if not found + */ + public boolean headChildrenTermEqual(Term t) + { + if (t == null) + { + return false; + } + + if (t.isAtom()) + { + return false; + } + + Term[] aMyTerms = children(); + int count = aMyTerms.length; + Term[] aOtherTerms = t.children(); + + if (count > aOtherTerms.length) + { + return false; + } + + if (count == 0) + { + return true; + } + + for (int i = 0; i < count; ++i) + { + if (!aMyTerms[i].termEqual(aOtherTerms[i])) + { + return false; + } + } + + return true; + } + + // ----- TermWalker methods --------------------------------------------- + + /** + * Do a dispatch back to the given walker. + * + * @param walker the TermWalker that implements the visitor for Terms + * + */ + public void accept(TermWalker walker) + { + walker.acceptTerm(getFunctor(), this); + } + + // ----- Iterable interface --------------------------------------------- + + @Override + public Iterator iterator() + { + return new TermIterator(this); + } + + // ----- inner class: TermIterator -------------------------------------- + + /** + * This {@link Iterator} implementation iterates over + * the child {@link Term}s of a given {@link Term}. + * + * @author jk 2014.02.25 + * @since Coherence 12.2.1 + */ + public class TermIterator + implements Iterator + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct an {@link Iterator} that will iterate over the child + * {@link Term}s of the specified {@link Term}. + * + * @param termParent the term to iterate over + */ + public TermIterator(Term termParent) + { + f_termParent = termParent; + m_nIndex = 1; + } + + // ----- Iterator interface ----------------------------------------- + + @Override + public boolean hasNext() + { + return m_nIndex <= f_termParent.length(); + } + + @Override + public Term next() + { + if (!hasNext()) + { + throw new NoSuchElementException("Attempt to read past end of Term Iterator"); + } + + return f_termParent.termAt(m_nIndex++); + } + + /** + * @throws UnsupportedOperationException as this iterator does not support removal + * of elements. + */ + @Override + public void remove() + { + throw new UnsupportedOperationException("Term Iterator does not support remove"); + } + + // ----- data members ----------------------------------------------- + + /** + * The Term who's children this {@link TermIterator} iterates over. + */ + private final Term f_termParent; + + /** + * A pointer to the next child term the iterator will return. + */ + private int m_nIndex; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/TermWalker.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/TermWalker.java new file mode 100644 index 0000000000000..fb006b6905f90 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/TermWalker.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.termtrees; + + +/** +* TermWalker is a visitor class that provides a framework for walking +* Term Trees +* +* @author djl 2009.08.31 +*/ +public interface TermWalker + { + // ----- TermWalker API ------------------------------------------------- + + /** + * The receiver has been dispatched to from the given node. + * + * @param sFunctor the node functor + * @param term the NodeTerm + */ + public void acceptNode(String sFunctor, NodeTerm term); + + /** + * The receiver has been dispatched to from the given atom. + * + * @param sFunctor the node functor + * @param atomicTerm the AtomicTerm + */ + public void acceptAtom(String sFunctor, AtomicTerm atomicTerm); + + /** + * The receiver has been dispatched to from the given atom. + * + * @param sFunctor the node functor + * @param term the Term + */ + public void acceptTerm(String sFunctor, Term term); + + /** + * Return the result of the previous TermTree walk. + * This value could be null if no trees have been walked + * or the last tree walk resulted in an undetermined state. + * + * @return the result of the previous TermTree walk + */ + public Object getResult(); + + /** + * Return the result of the walking the specified TermTree. + * This value could be null if the tree walk results in + * an undetermined state. + * + * @param term the term tree to walk to obtain a result object + * + * @return the result of walking the specified TermTree + */ + public Object walk(Term term); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/Terms.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/Terms.java new file mode 100644 index 0000000000000..599ebe62bbe3f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/dsltools/termtrees/Terms.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.dsltools.termtrees; + + +import com.tangosol.coherence.dslquery.CoherenceQueryLanguage; +import com.tangosol.coherence.dsltools.precedence.OPParser; +import com.tangosol.coherence.dsltools.termlanguage.TermLanguage; + + +/** +* Terms is a utility class that provides static convenience methods for +* the construction of Terms. Terms also provides a convenience interface +* for converting String in the TermLanguage to trees of Terms +* +* @author djl 2009.08.31 +*/ +public class Terms + { + // ----- Factory API ---------------------------------------------------_ + + /** + * Construct a new TermNode with the given functor. + * + * @param sFunctor the functor for the Term + * + * @return a term tree + */ + public static Term newTerm(String sFunctor) + { + return new NodeTerm(sFunctor); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param aTerms an children of the node + * + * @return a term tree + */ + public static Term newTerm(String sFunctor, Term[] aTerms) + { + return new NodeTerm(sFunctor, aTerms); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + * + * @return a term tree + */ + public static Term newTerm(String sFunctor, Term t1) + { + return new NodeTerm(sFunctor, t1); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + * @param t2 a child term + * + * @return a term tree + */ + public static Term newTerm(String sFunctor, Term t1, Term t2) + { + return new NodeTerm(sFunctor, t1,t2); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + * @param t2 a child term + * @param t3 a child term + * + * @return a term tree + */ + public static Term newTerm(String sFunctor, Term t1, Term t2, Term t3) + { + return new NodeTerm(sFunctor, t1,t2,t3); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + * @param t2 a child term + * @param t3 a child term + * @param t4 a child term + * + * @return a term tree + */ + public static Term newTerm(String sFunctor, + Term t1, Term t2, Term t3, Term t4) + { + return new NodeTerm(sFunctor, t1,t2,t3,t4); + } + + /** + * Construct a new TermNode with the given functor and given Terms. + * + * @param sFunctor the functor for the Term + * @param t1 a child term + * @param t2 a child term + * @param t3 a child term + * @param t4 a child term + * @param t5 a child term + * + * @return a term tree + */ + public static Term newTerm(String sFunctor, + Term t1, Term t2, Term t3, Term t4, Term t5) + { + return new NodeTerm(sFunctor, t1,t2,t3,t4,t5); + } + + /** + * Create a Tree of Terms using the Term Language in the given String + * + * @param s String representing a Term tree + * + * @return a term tree + */ + public static Term create(String s) + { + return create(s, f_language); + } + + /** + * Create a Tree of Terms using the Term Language in the given String + * + * @param s String representing a Term tree + * + * @return a term tree + */ + public static Term create(String s, CoherenceQueryLanguage language) + { + if (language == null) + { + language = f_language; + } + + OPParser p = new OPParser(s, TermLanguage.tokenTable(), language.getOperators()); + p.getScanner().setStrictness(false); + return p.parse(); + } + + /** + * The default {@link CoherenceQueryLanguage} used by this QueryHelper when no language + * is provided to methods. + */ + protected static final CoherenceQueryLanguage f_language = new CoherenceQueryLanguage(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/http/AbstractHttpServer.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/http/AbstractHttpServer.java new file mode 100644 index 0000000000000..b65a430d265c6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/http/AbstractHttpServer.java @@ -0,0 +1,933 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.http; + +import com.oracle.coherence.common.net.SSLSocketProvider; +import com.oracle.coherence.common.net.SocketProvider; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.Service; +import com.tangosol.net.Session; + +import com.tangosol.net.options.WithClassLoader; + +import com.tangosol.net.security.IdentityAsserter; +import com.tangosol.net.security.JAASIdentityAsserter; +import com.tangosol.net.security.UsernameAndPassword; + +import com.tangosol.util.Base; +import com.tangosol.util.ClassHelper; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import java.security.Principal; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; + +import java.security.cert.Certificate; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; + +import javax.security.auth.Subject; + +import javax.ws.rs.core.SecurityContext; + +import org.glassfish.hk2.api.DynamicConfiguration; +import org.glassfish.hk2.api.DynamicConfigurationService; +import org.glassfish.hk2.api.ServiceLocator; +import org.glassfish.hk2.api.ServiceLocatorFactory; + +import org.glassfish.hk2.utilities.BuilderHelper; + +import org.glassfish.jersey.server.ApplicationHandler; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ResourceConfig; + +/** + * Abstract base class for {@link HttpServer} implementations. + * + * @author as 2011.06.16 + */ +public abstract class AbstractHttpServer + implements HttpServer, HttpServerStats + { + + // ----- HttpServer interface ------------------------------------------ + + /** + * {@inheritDoc} + */ + public void setAuthMethod(String sMethod) + { + if (AUTH_BASIC.equalsIgnoreCase(sMethod) || + AUTH_CERT.equalsIgnoreCase(sMethod) || + AUTH_CERT_BASIC.equalsIgnoreCase(sMethod) || + AUTH_NONE.equalsIgnoreCase(sMethod)) + { + m_sAuthMethod = sMethod; + } + else + { + throw new IllegalArgumentException("unsupported method: " + sMethod); + } + } + + /** + * {@inheritDoc} + */ + public void setSession(Session session) + { + m_session = session; + } + + /** + * {@inheritDoc} + */ + public void setLocalAddress(String sAddr) + { + m_sAddr = sAddr; + } + + /** + * {@inheritDoc} + */ + public String getListenAddress() + { + return getLocalAddress(); + } + + /** + * {@inheritDoc} + */ + public int getListenPort() + { + return m_nPort; + } + + /** + * {@inheritDoc} + */ + public void setLocalPort(int nPort) + { + m_nPort = nPort; + } + + /** + * {@inheritDoc} + */ + public void setParentService(Service service) + { + m_serviceParent = service; + } + + /** + * {@inheritDoc} + */ + public void setResourceConfig(ResourceConfig config) + { + setResourceConfig(Collections.singletonMap("/", config)); + } + + /** + * {@inheritDoc} + */ + public void setResourceConfig(Map mapConfig) + { + m_mapResourceConfig.clear(); + m_mapResourceConfig.putAll(mapConfig); + } + + /** + * {@inheritDoc} + */ + public void setSocketProvider(SocketProvider provider) + { + m_socketProvider = provider; + } + + /** + * {@inheritDoc} + */ + public synchronized void start() + throws IOException + { + if (!m_fStarted) + { + startInternal(); + m_fStarted = true; + } + } + + /** + * {@inheritDoc} + */ + public synchronized void stop() + throws IOException + { + if (m_fStarted) + { + stopInternal(); + m_fStarted = false; + } + } + + // ----- HttpServerStats interface -------------------------------------- + + @Override + public long getRequestCount() + { + return m_cRequestCount; + } + + @Override + public float getAverageRequestTime() + { + return m_cRequestCount == 0 ? 0 : (float) m_ltdTotalRequestTime / m_cRequestCount; + } + + @Override + public float getRequestsPerSecond() + { + return m_cRequestCount == 0 ? 0 : + (m_cRequestCount * 1.0f) / + ((float) (Base.getSafeTimeMillis() - m_ldtResetTime) / 1000.0f); + } + + @Override + public long getErrorCount() + { + return m_cErrors; + } + + @Override + public long getHttpStatusCount(int nPrefix) + { + validatePrefix(nPrefix); + + return f_aStatusCodes[nPrefix - 1]; + } + + @Override + public void resetStats () + { + m_cRequestCount = 0L; + m_ltdTotalRequestTime = 0L; + m_cErrors = 0L; + m_ldtResetTime = Base.getSafeTimeMillis(); + + for (int i = 0; i < f_aStatusCodes.length ; i++) + { + f_aStatusCodes[i] = 0L; + } + } + + /** + * Increment the request count. + */ + protected void incrementRequestCount () + { + m_cRequestCount++; + } + + /** + * Add to the total request time. + * + * @param ltdStartTime the start of the request + */ + protected void logRequestTime(long ltdStartTime) + { + m_ltdTotalRequestTime += Base.getLastSafeTimeMillis() - ltdStartTime; + } + + /** + * Increment the number of errors. + */ + protected void incrementErrors() + { + m_cErrors++; + } + + /** + * Add to the total of status codes. + * + * @param nStatusCode the status code to add + */ + protected void logStatusCount(int nStatusCode) + { + if (nStatusCode >= 100 && nStatusCode <= 600) + { + f_aStatusCodes[nStatusCode / 100 - 1]++; + } + } + + /** + * Validate that the prefix is a valid value. + * + * @param nPrefix the prefix to validate + */ + private void validatePrefix(int nPrefix) + { + if (nPrefix < 0 || nPrefix > f_aStatusCodes.length) + { + throw new IllegalArgumentException("Prefix must be between 0 and " + f_aStatusCodes.length); + } + } + /** + * Helper to dump current stats. + */ + protected void dumpStats() + { + StringBuilder sb = new StringBuilder("HTTP statistics: ") + .append(new Date()) + .append('\n').append(" Start time: ").append(new Date(m_ldtResetTime)) + .append('\n') + .append("Request Count=").append(getRequestCount()) + .append(", Avg Req Time=").append(getAverageRequestTime()) + .append(", Req/sec=").append(getRequestsPerSecond()) + .append(", Errors=").append(getErrorCount()) + .append("\nStatus counts: "); + + for (int i = 0; i < f_aStatusCodes.length; i++) + { + sb.append("Status ") + .append((i + 1) * 100) + .append('=') + .append(f_aStatusCodes[i]) + .append(' '); + } + + CacheFactory.log(sb.toString(), CacheFactory.LOG_INFO); + } + + // ----- abstract methods ----------------------------------------------- + + /** + * Start the server. + * + * @throws IOException if an error occurs + */ + protected abstract void startInternal() + throws IOException; + + /** + * Stop the server. + * + * @throws IOException if an error occurs + */ + protected abstract void stopInternal() + throws IOException; + + /** + * Factory method for Jersey container instances. + * + * @param config the resource configuration + * @param locator the parent service locator + * + * @return container instance + */ + protected abstract Object instantiateContainer(ResourceConfig config, ServiceLocator locator); + + // ----- helpers -------------------------------------------------------- + + /** + * Create and configure a Jersey container that will process HTTP requests. + * + * @param resourceConfig resource configuration + * + * @return the container + */ + protected Object createContainer(ResourceConfig resourceConfig) + { + ServiceLocatorFactory factory = ServiceLocatorFactory.getInstance(); + ServiceLocator locator = factory.create(getClass().getName()); + DynamicConfiguration config = locator + .getService(DynamicConfigurationService.class) + .createDynamicConfiguration(); + + if (getParentService() != null) + { + config.bind(BuilderHelper.createConstantDescriptor(getParentService(), null, Service.class)); + } + config.bind(BuilderHelper.createConstantDescriptor(getSession(), null, Session.class)); + + config.commit(); + + return instantiateContainer(resourceConfig, locator); + } + + /** + * Perform HTTP Basic authentication and return authenticated Subject. + * + * @param sAuth the value of Authorization header from the request + + * @return authenticated Subject if successful, null otherwise + */ + protected Subject authenticate(String sAuth) + { + if (sAuth != null && sAuth.startsWith("Basic ")) + { + sAuth = sAuth.substring("Basic ".length()); + + String[] values = fromBase64(sAuth).split(":"); + if (values.length == 2) + { + String sUsername = values[0]; + String sPassword = values[1]; + + try + { + return getIdentityAsserter().assertIdentity( + new UsernameAndPassword(sUsername, sPassword), + getParentService()); + } + catch (SecurityException ignore) + { + // fall through and return null + } + } + } + + return null; + } + + /** + * Creates Subject instance using principal and credentials from the + * SSL session. + * + * @param session SSL session + * + * @return Subject for the client + * + * @throws SSLPeerUnverifiedException if the client is not authenticated + */ + protected Subject getSubjectFromSession(SSLSession session) + throws SSLPeerUnverifiedException + { + Set setPrincipals = + Collections.singleton(session.getPeerPrincipal()); + Set setCredentials = new HashSet<>( + Arrays.asList(session.getPeerCertificates())); + + return new Subject(true, setPrincipals, setCredentials, Collections.emptySet()); + } + + /** + * Handle HTTP(S) request. + * + * @param app web application that should handle request + * @param request the request + * @param subject the subject, can be null + * + * @throws IOException if an error occurs + */ + protected void handleRequest(final ApplicationHandler app, + final ContainerRequest request, + final Subject subject) + throws IOException + { + if (subject == null) + { + app.handle(request); + } + else + { + try + { + Subject.doAs(subject, new PrivilegedExceptionAction() + { + public Object run() + throws IOException + { + app.handle(request); + return null; + } + }); + } + catch (PrivilegedActionException e) + { + Exception cause = e.getException(); + if (cause instanceof RuntimeException) + { + throw (RuntimeException) cause; + } + else + { + throw (IOException) cause; + } + } + } + } + + // ----- Base64 support ------------------------------------------------- + + /** + * Converts a byte array into a Base64 encoded string. + * @param buffer byte array to encode + * @return Base64 encoding of buffer + */ + public static String toBase64(byte[] buffer) + { + byte[] result = java.util.Base64.getEncoder().encode(buffer); + + try + { + return new String(result, "ASCII"); + } + catch (UnsupportedEncodingException e) + { + return new String(result); + } + } + + /** + * Converts a string into a Base64 encoded string. + * @param text string to encode + * @return Base64 encoding of text + */ + public static String toBase64(String text) + { + return toBase64(text.getBytes()); + } + + /** + * Converts a byte array into a Base64 decoded string. + * @param buffer byte array to decode + * @return Base64 decoding of buffer + */ + public static String fromBase64(byte[] buffer) + { + byte[] result = java.util.Base64.getDecoder().decode(buffer); + + try + { + return new String(result, "ASCII"); + } + catch (UnsupportedEncodingException e) + { + return new String(result); + } + } + + /** + * Converts a string into a Base64 decoded string + * @param text string to decode + * @return Base64 decoding of text + */ + public static String fromBase64(String text) + { + return fromBase64(text.getBytes()); + } + + // ----- inner class: SimpleSecurityContext ----------------------------- + + /** + * Simple implementation of the SecurityContext interface. + */ + public static class SimpleSecurityContext + implements SecurityContext + { + /** + * Create a new SimpleSecurityContext instance. + * + * @param sAuthScheme string value of the authentication scheme used + * to protect resources + * @param principal the Principal containing the name of the + * current authenticated user + * @param fSecure a boolean value indicating whether a request + * was made using a secure channel, such as HTTPS + */ + public SimpleSecurityContext(String sAuthScheme, + Principal principal, boolean fSecure) + { + m_sAuthScheme = sAuthScheme; + m_principal = principal; + m_fSecure = fSecure; + } + + /** + * Return the string value of the authentication scheme used to + * protect the resource. + */ + public String getAuthenticationScheme() + { + return m_sAuthScheme; + } + + /** + * Return a Principal object containing the name of the current + * authenticated user. + */ + public Principal getUserPrincipal() + { + return m_principal; + } + + /** + * Return a boolean indicating whether this request was made using a + * secure channel, such as HTTPS. + */ + public boolean isSecure() + { + return m_fSecure; + } + + /** + * Return a boolean indicating whether the authenticated user is + * included in the specified logical "role". + * + * @param sRole the name of the role + */ + public boolean isUserInRole(String sRole) + { + return false; + } + + /** + * The authentication scheme. + */ + private String m_sAuthScheme; + + /** + * The current authenticated principal. + */ + private Principal m_principal; + + /** + * True if the request was made using a secure channel, false + * otherwise. + */ + private boolean m_fSecure; + } + + // ----- accessors ------------------------------------------------------ + + /** + * Return the address the server should listen on. + * + * @return the address + */ + public String getLocalAddress() + { + return m_sAddr; + } + + /** + * Return the port number the server should listen on. + * + * @return the port number + */ + public int getLocalPort() + { + return m_nPort; + } + + /** + * Return the service that is embedding this server. + * + * @return the parent service + */ + public Service getParentService() + { + return m_serviceParent; + } + + /** + * Return the Coherence {@link Session} to use. + * + * @return the Session + */ + public Session getSession() + { + return m_session != null + ? m_session + : Session.create(WithClassLoader.autoDetect()); + } + + /** + * Return the SocketProvider to use. + * + * @return the SocketProvider + */ + public SocketProvider getSocketProvider() + { + return m_socketProvider; + } + + /** + * Return the SSLContext to use. + * + * @return the SSLContext + */ + public SSLContext getSSLContext() + { + return isSecure() + ? ((SSLSocketProvider) m_socketProvider).getDependencies().getSSLContext() + : null; + } + + /** + * Return the SSLParameters to use. + * + * @return the SSLParameters + */ + public SSLParameters getSSLParameters() + { + return isSecure() + ? ((SSLSocketProvider) m_socketProvider).getDependencies().getSSLParameters() + : null; + } + + /** + * Return map of context names to Jersey resource configurations. + * + * @return map of context names to Jersey resource configurations + */ + public Map getResourceConfig() + { + return m_mapResourceConfig; + } + + /** + * Return identity asserter to use for HTTP basic authentication. + * + * @return the identity asserter to use + */ + public IdentityAsserter getIdentityAsserter() + { + return m_identityAsserter; + } + + /** + * Configure the identity asserter to use for HTTP basic authentication. + * + * @param asserter the identity asserter to use + */ + protected void setIdentityAsserter(IdentityAsserter asserter) + { + m_identityAsserter = asserter; + } + + /** + * Return true if this server should use HTTP basic authentication. + * + * @return true if HTTP basic authentication should be used + */ + protected boolean isAuthMethodBasic() + { + String sAuthMethod = m_sAuthMethod; + return AUTH_BASIC.equalsIgnoreCase(sAuthMethod) + || AUTH_CERT_BASIC.equalsIgnoreCase(sAuthMethod); + } + + /** + * Return true if this server should use client certificates for + * authentication. + * + * @return true if client certificates should be used for authentication + */ + protected boolean isAuthMethodCert() + { + String sAuthMethod = m_sAuthMethod; + return AUTH_CERT.equalsIgnoreCase(sAuthMethod) + || AUTH_CERT_BASIC.equalsIgnoreCase(sAuthMethod); + } + + /** + * Return true if this server should not require client authentication. + * + * @return true if client authentication is not required + */ + protected boolean isAuthMethodNone() + { + return AUTH_NONE.equalsIgnoreCase(m_sAuthMethod); + } + + /** + * Return true if this server uses SSL to secure communication. + * + * @return true if this server uses SSL + */ + protected boolean isSecure() + { + return m_socketProvider instanceof SSLSocketProvider; + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + public String toString() + { + return ClassHelper.getSimpleName(getClass()) + + "{Protocol=" + (isSecure() ? "HTTPS" : "HTTP") + + ", AuthMethod=" + m_sAuthMethod + "}"; + } + + // ----- data members --------------------------------------------------- + + /** + * Authentication method. Valid values are 'basic', 'cert', 'cert+basic', + * and 'none'. + */ + protected String m_sAuthMethod = AUTH_NONE; + + /** + * Coherence session. + */ + protected Session m_session; + + /** + * Address server should listen on. + */ + protected String m_sAddr = DEFAULT_ADDRESS; + + /** + * Port number server should listen on. + */ + protected int m_nPort = DEFAULT_PORT; + + /** + * Parent service. + */ + protected Service m_serviceParent; + + /** + * Map of context names to Jersey resource configurations. + */ + protected final Map m_mapResourceConfig = new HashMap<>(); + + /** + * SocketProvider used by the server. + */ + protected SocketProvider m_socketProvider; + + /** + * Identity asserter to use with HTTP basic authentication. + */ + protected IdentityAsserter m_identityAsserter = DEFAULT_IDENTITY_ASSERTER; + + /** + * Flag specifying whether the server is already started. + */ + protected boolean m_fStarted; + + /** + * Total number of requests. + */ + private long m_cRequestCount = 0L; + + /** + * Total request time. + */ + private long m_ltdTotalRequestTime = 0L; + + /** + * Total number of errors when processing requests. + */ + private long m_cErrors = 0L; + + /** + * Count of status code responses. + */ + private final long[] f_aStatusCodes = new long[5]; + + /** + * The time stats were last reset. + */ + private long m_ldtResetTime = Base.getSafeTimeMillis(); + + // ----- constants ------------------------------------------------------ + + /** + * Default HTTP server address. + */ + public static final String DEFAULT_ADDRESS = "localhost"; + + /** + * Default HTTP server port. + */ + public static final int DEFAULT_PORT = 0; + + /** + * Default identity asserter. + */ + public static final IdentityAsserter DEFAULT_IDENTITY_ASSERTER = + new JAASIdentityAsserter("CoherenceREST"); + + /** + * HTTP basic authentication. + */ + public static final String AUTH_BASIC = "basic"; + + /** + * Certificate authentication. + */ + public static final String AUTH_CERT = "cert"; + + /** + * Certificate authentication. + */ + public static final String AUTH_CERT_BASIC = "cert+basic"; + + /** + * No authentication. + */ + public static final String AUTH_NONE = "none"; + + /** + * Realm for HTTP basic authentication. + */ + public static final String HTTP_BASIC_REALM = "Coherence REST"; + + /** + * Attribute name that should be used to store Subject for the request. + */ + public static final String ATTR_SUBJECT = "__SUBJECT"; + + /** + * Authorization header. + */ + public static final String HEADER_AUTHORIZATION = "Authorization"; + + /** + * WWW-Authenticate header. + */ + protected static final String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; + + /** + * A {@link Principal} with no name. + */ + protected static final Principal EMPTY_PRINCIPAL = () -> null; + + /** + * Symbolic reference for {@code /}. + */ + protected static final String SLASH = "/"; + + /** + * Symbolic reference for character {@code /}. + */ + protected static final char SLASH_CHAR = SLASH.charAt(0); + + /** + * The HTTP header value to return when basic authentication is enabled and required. + */ + protected static final String DEFAULT_BASIC_AUTH_HEADER_VALUE = "Basic realm=\"" + HTTP_BASIC_REALM + '"'; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/http/DefaultHttpServer.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/http/DefaultHttpServer.java new file mode 100644 index 0000000000000..67639c139db7e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/http/DefaultHttpServer.java @@ -0,0 +1,634 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.http; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsExchange; +import com.sun.net.httpserver.HttpsParameters; +import com.sun.net.httpserver.HttpsServer; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.util.Base; +import com.tangosol.util.DaemonThreadFactory; + +import java.io.IOException; +import java.io.OutputStream; + +import java.net.InetSocketAddress; +import java.net.URI; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; + +import javax.security.auth.Subject; + +import javax.ws.rs.core.Application; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.UriBuilder; + +import org.glassfish.hk2.api.ServiceLocator; + +import org.glassfish.jersey.internal.MapPropertiesDelegate; + +import org.glassfish.jersey.server.ApplicationHandler; +import org.glassfish.jersey.server.ContainerException; +import org.glassfish.jersey.server.ContainerRequest; +import org.glassfish.jersey.server.ContainerResponse; +import org.glassfish.jersey.server.ResourceConfig; + +import org.glassfish.jersey.server.spi.Container; +import org.glassfish.jersey.server.spi.ContainerLifecycleListener; +import org.glassfish.jersey.server.spi.ContainerResponseWriter; + +/** + * Implementation of {@link HttpServer} that uses Sun's lightweight HTTP + * server to handle requests. + *

+ * This implementation is not recommended for production environments. + * + * @author as 2011.06.08 + */ +public class DefaultHttpServer + extends AbstractHttpServer + { + + // ----- AbstractHttpServer implementation ------------------------------ + + /** + * {@inheritDoc} + */ + protected void startInternal() + throws IOException + { + System.setProperty("sun.net.httpserver.nodelay", "true"); + HttpServer server = createHttpServer(); + server.start(); + m_server = server; + resetStats(); + } + + /** + * {@inheritDoc} + */ + protected void stopInternal() + throws IOException + { + m_server.stop(0); + m_server = null; + } + + /** + * {@inheritDoc} + */ + public String getListenAddress() + { + if (m_sListenAddress == null) + { + m_sListenAddress = m_server.getAddress().getHostString(); + } + return m_sListenAddress; + } + + /** + * {@inheritDoc} + */ + public int getListenPort() + { + if (m_nListenPort == 0) + { + m_nListenPort = m_server.getAddress().getPort(); + } + return m_nListenPort; + } + + // ----- helpers -------------------------------------------------------- + + /** + * Creates a new HttpServer which will manage all root resource and + * provider classes declared by the resource configuration. + * + * @return the new HttpServer instance + * + * @throws IOException if an error occurs while creating the container + */ + protected HttpServer createHttpServer() + throws IOException + { + InetSocketAddress addr = new InetSocketAddress(getLocalAddress(), getLocalPort()); + + HttpServer server; + if (isSecure()) + { + HttpsServer sslServer = HttpsServer.create(addr, 0); + + final SSLParameters sslParams = getSSLParameters(); + sslServer.setHttpsConfigurator(new HttpsConfigurator(getSSLContext()) + { + public void configure(HttpsParameters params) + { + params.setSSLParameters(sslParams); + params.setNeedClientAuth(isAuthMethodCert()); + } + }); + + server = sslServer; + } + else + { + server = HttpServer.create(addr, 0); + } + + server.setExecutor(Executors.newCachedThreadPool( + new DaemonThreadFactory("DefaultHttpServerThread-"))); + + for (Map.Entry entry : getResourceConfig().entrySet()) + { + HttpHandler handler = (HttpHandler) createContainer(entry.getValue()); + if (isAuthMethodBasic()) + { + handler = new BasicAuthenticationHandler(handler); + } + + server.createContext(entry.getKey(), handler); + } + + return server; + } + + // ----- inner class: BasicAuthenticationHandler ------------------------ + + /** + * HTTP basic authenticator. + */ + private class BasicAuthenticationHandler + implements HttpHandler + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct BasicAuthenticationHandler instance. + * + * @param handler request handler to delegate to + */ + public BasicAuthenticationHandler(HttpHandler handler) + { + m_handler = handler; + } + + // ----- HttpHandler methods ---------------------------------------- + + /** + * Authenticate user based on the credentials specified in the + * Authorization request header and delegate request processing to + * wrapped HttpHandler if authentication is successful. + * + * @param exchange HTTP exchange for the request + * + * @throws IOException if an error occurs + */ + public void handle(final HttpExchange exchange) + throws IOException + { + String sAuth = exchange.getRequestHeaders().getFirst(HEADER_AUTHORIZATION); + Subject subject = authenticate(sAuth); + if (subject == null) + { + exchange.getResponseHeaders().set(HEADER_WWW_AUTHENTICATE, DEFAULT_BASIC_AUTH_HEADER_VALUE); + exchange.sendResponseHeaders(401, -1); + } + else + { + exchange.setAttribute(ATTR_SUBJECT, subject); + m_handler.handle(exchange); + } + } + + // ----- data members ----------------------------------------------- + + /** + * Request handler to delegate to when authentication is successful. + */ + protected final HttpHandler m_handler; + } + + // ----- inner class: HttpServerContainer ------------------------------- + + @Override + protected Object instantiateContainer(ResourceConfig config, ServiceLocator locator) + { + return new HttpServerContainer(config.getApplication(), locator); + } + + /** + * Class copied from jersey-server Jersey module and modified to + * create security context for the request when HTTP basic or client + * certificate authentication is enabled. + */ + private class HttpServerContainer + implements HttpHandler, Container + { + + // ----- constructors ----------------------------------------------- + + /** + * Create new lightweight Java SE HTTP server container. + * + * @param application JAX-RS / Jersey application to be deployed on the container. + * @param parentLocator parent HK2 service locator. + */ + HttpServerContainer(final Application application, final ServiceLocator parentLocator) + { + this.m_hApplication = new ApplicationHandler(application, null, parentLocator); + } + + // ----- HttpHandler interface -------------------------------------- + + /** + * Handle the given request and generate an appropriate response. See + * HttpExchange for a description of the steps involved in handling + * an exchange. + * + * @param exchange the exchange containing the request from the + * client and used to send the response + * + * @throws IOException on I/O error + */ + public void handle(HttpExchange exchange) + throws IOException + { + Writer responseWriter = null; + incrementRequestCount(); + long ldtStart = Base.getLastSafeTimeMillis(); + + try + { + // this is a URI that contains the path, query and fragment + // components + URI uriExchange = exchange.getRequestURI(); + + // the base path specified by the HTTP context of the HTTP + // handler in decoded form + String sDecodedBasePath = exchange.getHttpContext().getPath(); + + // ensure that the base path ends with a '/' + if (!sDecodedBasePath.endsWith(SLASH)) + { + if (sDecodedBasePath.equals(uriExchange.getPath())) + { + // This is an edge case where the request path does not + // end in a '/' and is equal to the context path of the + // HTTP handler. Both the request path and base path need + // to end in a '/'. Currently the request path is modified. + uriExchange = UriBuilder.fromUri(uriExchange). + path(SLASH).build(); + } + sDecodedBasePath += SLASH; + } + + // the following is madness - there is no easy way to get the + // complete URI of the HTTP request! + String sScheme = (exchange instanceof HttpsExchange) ? "https" : "http"; + + URI uriBase; + List listHostHeader = exchange.getRequestHeaders().get("Host"); + if (listHostHeader == null) + { + InetSocketAddress addr = exchange.getLocalAddress(); + uriBase = new URI(sScheme, null, addr.getHostName(), + addr.getPort(), sDecodedBasePath, null, null); + } + else + { + uriBase = new URI(sScheme + "://" + listHostHeader.get(0) + sDecodedBasePath); + } + + Subject subject = null; + String sAuth = null; + if (isAuthMethodBasic()) + { + sAuth = SecurityContext.BASIC_AUTH; + subject = (Subject) exchange.getAttribute("__SUBJECT"); + } + else if (isSecure() && isAuthMethodCert()) + { + sAuth = SecurityContext.CLIENT_CERT_AUTH; + + SSLSession session = ((HttpsExchange) exchange).getSSLSession(); + subject = getSubjectFromSession(session); + } + + ContainerRequest request = new ContainerRequest( + uriBase, + uriBase.resolve(uriExchange), + exchange.getRequestMethod(), + new SimpleSecurityContext(sAuth, + subject == null ? exchange.getPrincipal() : subject.getPrincipals().iterator().next(), isSecure()), + new MapPropertiesDelegate()); + request.setEntityStream(exchange.getRequestBody()); + request.getHeaders().putAll(exchange.getRequestHeaders()); + responseWriter = new Writer(exchange); + request.setWriter(responseWriter); + handleRequest(m_hApplication, request, subject); + } + catch (Exception e) + { + CacheFactory.log( + "Caught unhandled exception while processing an HTTP request: " + + Base.printStackTrace(e) + ); + exchange.getResponseHeaders().clear(); + exchange.sendResponseHeaders(500, -1); + incrementErrors(); + } + finally + { + if (responseWriter != null) + { + responseWriter.commit(); + } + } + + logRequestTime(ldtStart); + logStatusCount(exchange.getResponseCode()); + } + + // ----- Container interface -------------------------------- + + @Override + public ResourceConfig getConfiguration() + { + return m_hApplication.getConfiguration(); + } + + @Override + public ApplicationHandler getApplicationHandler() + { + return m_hApplication; + } + + /** + * Called when reloading of the container is requested. + */ + @Override + public void reload() + { + reload(getConfiguration()); + } + + @Override + public void reload(ResourceConfig config) + { + m_hApplication.onShutdown(this); + m_hApplication = new ApplicationHandler(config); + m_hApplication.onReload(this); + m_hApplication.onStartup(this); + } + + /** + * Inform this container that the server has been started. + * + * This method must be implicitly called after the server containing this container is started. + */ + void onServerStart() + { + this.m_hApplication.onStartup(this); + } + + /** + * Inform this container that the server is being stopped. + * + * This method must be implicitly called before the server containing this container is stopped. + */ + void onServerStop() + { + this.m_hApplication.onShutdown(this); + } + + // ----- inner class: Writer ---------------------------------------- + + /** + * ContainerResponseWriter implementation for this container. + */ + private class Writer + implements ContainerResponseWriter + { + + // ----- constructors -------------------------------------------- + + /** + * Create a new Writer for the given HTTP exchange. + * + * @param exchange the exchange containing the request from the + * client and used to send the response + */ + public Writer(HttpExchange exchange) + { + this.m_exchange = exchange; + this.m_closed = new AtomicBoolean(false); + } + + // ----- ContainerResponseWriter interface ---------------------- + + /** + * Write the status and headers of the response and return an + * output stream for the web application to write the entity of + * the response. + * + * @param cbContent >=0 if the content length in bytes of the + * entity to be written is known, otherwise -1. + * Containers may use this value to determine + * whether the "Content-Length" header can be + * set or utilize chunked transfer encoding. + * @param response the container response. The status and + * headers are obtained from the response. + * + * @return the output stream to write the entity (if any) + * + * @throws IOException on I/O error + */ + public OutputStream writeResponseStatusAndHeaders(long cbContent, + ContainerResponse response) + throws ContainerException + { + HttpExchange exchange = m_exchange; + + Headers headers = exchange.getResponseHeaders(); + for (Map.Entry> entry : + response.getStringHeaders().entrySet()) + { + List list = new ArrayList(); + for (String o : entry.getValue()) + { + list.add(o); + } + headers.put(entry.getKey(), list); + } + + try + { + if (response.getStatus() == 204) + { + // work around bug in LW HTTP server: + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6886436 + exchange.sendResponseHeaders(response.getStatus(), -1); + } + else + { + exchange.sendResponseHeaders(response.getStatus(), + getResponseLength(cbContent)); + } + } + catch (IOException e) + { + throw new ContainerException("Error during writing out the response headers.", e); + } + return exchange.getResponseBody(); + } + + /** + * Finish writing the response. This enables the container + * response writer to clean up any state or flush any streams. + * + * @throws IOException on I/O error + */ + public void finish() + throws IOException + { + } + + @Override + public boolean suspend(long timeOut, TimeUnit timeUnit, TimeoutHandler hTimeout) + { + throw new UnsupportedOperationException("Method suspend is not support by the container."); + } + + @Override + public void setSuspendTimeout(long timeout, TimeUnit timeUnit) + throws IllegalStateException + { + throw new UnsupportedOperationException("Method setSuspendTimeout is not support by the container."); + } + + @Override + public void failure(Throwable error) + { + try + { + m_exchange.sendResponseHeaders(500, getResponseLength(0)); + } + catch (IOException e) + { + CacheFactory.log("Unable to send a failure response: " + + Base.printStackTrace(e), CacheFactory.LOG_WARN); + + } + finally + { + commit(); + rethrow(error); + } + } + + @Override + public boolean enableResponseBuffering() + { + return true; + } + + @Override + public void commit() + { + if (m_closed.compareAndSet(false, true)) + { + m_exchange.close(); + } + } + + // ----- helper methods ----------------------------------------- + + /** + * Return the response length for the specified number of bytes. + * + * @param cb the number of bytes in the reponse + * + * @return the response length + */ + private long getResponseLength(long cb) + { + return cb == 0 ? -1 : cb < 0 ? 0 : cb; + } + + private void rethrow(Throwable error) + { + if (error instanceof RuntimeException) + { + throw (RuntimeException) error; + } + else + { + throw new ContainerException(error); + } + } + + // ----- data members ------------------------------------------- + + /** + * The HTTP exchange. + */ + final HttpExchange m_exchange; + + /** + * A flag to indicate whether the writer is closed. + */ + final AtomicBoolean m_closed; + } + + // ----- data members ----------------------------------------------- + + /** + * The application. + */ + private volatile ApplicationHandler m_hApplication; + + /** + * The container listener. + */ + private volatile ContainerLifecycleListener m_containerListener; + } + + // ----- data members --------------------------------------------------- + + /** + * HTTP server instance. + */ + protected HttpServer m_server; + + /** + * The cached listen address of this server. + */ + protected String m_sListenAddress; + + /** + * The cached listen port of this server. + */ + protected int m_nListenPort; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/http/HttpServer.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/http/HttpServer.java new file mode 100644 index 0000000000000..446c9d05c4f55 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/http/HttpServer.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.http; + +import com.oracle.coherence.common.net.SocketProvider; + +import com.tangosol.net.Session; +import com.tangosol.net.Service; + +import java.io.IOException; + +import java.util.Iterator; +import java.util.Map; +import java.util.ServiceLoader; + +import org.glassfish.jersey.server.ResourceConfig; + +import javax.ws.rs.core.Application; + +/** + * An interface implemented by embedded HTTP servers. + * + * @author as 2011.06.03 + */ +public interface HttpServer + { + // ----- factory methods ------------------------------------------------ + + /** + * Return a {@link HttpServer} implementation to use. + *

+ * The {@link HttpServer} instance will be discovered by the + * {@link ServiceLoader}. This assumes that there is a single + * {@link HttpServer} service on the classpath. If multiple + * implementations are available then the first instance + * discovered will be returned. + *

+ * If the service loader is unable to discover any {@link HttpServer} + * implementations then an instance of {@link DefaultHttpServer} will + * be returned. + *

+ * After creating the {@link HttpServer} this method will use the {@link ServiceLoader} + * to discover instances of {@link Application JAX-RS Applications} and + * add them to the {@link HttpServer}. + * + * @return the {@link HttpServer} to use + */ + public static HttpServer create() + { + ServiceLoader loaderServer = ServiceLoader.load(HttpServer.class); + Iterator itServer = loaderServer.iterator(); + + return itServer.hasNext() ? itServer.next() : new DefaultHttpServer(); + } + + // ----- configuration methods ------------------------------------------ + + /** + * Set the client authentication method to use. + *

+ * Valid values basic for HTTP basic authentication, cert + * for client certificate authentication, cert+basic for both + * client certificate and HTTP basic authentication, and none for + * no authentication. + * + * @param sMethod the authentication method to use + */ + public void setAuthMethod(String sMethod); + + /** + * Set Coherence session to use. + * + * @param session the Coherence session + */ + public void setSession(Session session); + + /** + * Set the address server should listen on. + * + * @param sAddr the address + */ + public void setLocalAddress(String sAddr); + + /** + * Set the port number server should listen on. + * + * @param nPort the port number + */ + public void setLocalPort(int nPort); + + /** + * Set the Service that is embedding this HttpServer. + * + * @param service the parent service + */ + public void setParentService(Service service); + + /** + * Set the Jersey ResourceConfig to use. + *

+ * This method will register specified application under the root context, + * which is equivalent to: + * + * setResourceConfig(Collections.singletonMap("/", config)); + * + * + * @param config the resource config for a Jersey web application + */ + public void setResourceConfig(ResourceConfig config); + + /** + * Set the map of Jersey ResourceConfig to use. + * + * @param mapConfig the map of context names to corresponding Jersey + * resource configs to use + */ + public void setResourceConfig(Map mapConfig); + + /** + * Set the SocketProvider to use. + * + * @param provider the SocketProvider + */ + public void setSocketProvider(SocketProvider provider); + + // ----- lifecycle methods ---------------------------------------------- + + /** + * Start the server. + * + * @throws IOException if an error occurs + */ + public void start() + throws IOException; + + /** + * Stop the server. + * + * @throws IOException if an error occurs + */ + public void stop() + throws IOException; + + // ----- runtime information -------------------------------------------- + + /** + * Get the server's listen address. + * + * @return the server's listen address + * + * @since 12.2.1.4.0 + */ + String getListenAddress(); + + /** + * Get the server's listen port + * + * @return the listen port + * + * @since 12.2.1.4.0 + */ + int getListenPort(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/http/HttpServerStats.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/http/HttpServerStats.java new file mode 100644 index 0000000000000..46abc6cce3e17 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/http/HttpServerStats.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.http; + +/** + * Defines basic statistics that can be collected by an HTTP server implementation. + * + * @author tam 2015.08.22 + * @since 12.2.1.1 + */ +public interface HttpServerStats + { + /** + * Return the number of requests serviced since the HTTP server was started + * or the statistics were reset. + * + * @return the number of requests services + */ + public long getRequestCount(); + + /** + * Return the average processing time in milliseconds. + * + * @return the average processing time in milliseconds + */ + public float getAverageRequestTime(); + + /** + * Return the number of requests per second since the statistics were reset. + * + * @return the number of requests per second since the statistics were reset + */ + public float getRequestsPerSecond(); + + /** + * Return the number of requests that caused errors. + * + * @return the number of requests that caused errors + */ + public long getErrorCount(); + + /** + * Return the count of Http status codes with the given prefix. + * E.g. 1=100-199, 2=200-299, 3=300-399, 4=400-499, 5=500-599 + * + * @param nPrefix the prefix to return count for + * + * @return the count of Http status codes with the given prefix + */ + public long getHttpStatusCount(int nPrefix); + + /** + * Reset the statistics. + */ + public void resetStats(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/DefaultRequestHandler.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/DefaultRequestHandler.java new file mode 100644 index 0000000000000..0b39c06b0953f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/DefaultRequestHandler.java @@ -0,0 +1,831 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached; + +import com.oracle.coherence.common.base.Continuation; + +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.coherence.memcached.processor.AddReplaceProcessor; +import com.tangosol.coherence.memcached.processor.AppendPrependProcessor; +import com.tangosol.coherence.memcached.processor.DeleteProcessor; +import com.tangosol.coherence.memcached.processor.GetProcessor; +import com.tangosol.coherence.memcached.processor.IncrDecrProcessor; +import com.tangosol.coherence.memcached.processor.MemcachedAsyncProcessor; +import com.tangosol.coherence.memcached.processor.PutProcessor; +import com.tangosol.coherence.memcached.processor.TouchProcessor; + +import com.tangosol.coherence.memcached.server.Connection.ConnectionFlowControl; +import com.tangosol.coherence.memcached.server.DataHolder; +import com.tangosol.coherence.memcached.server.MemcachedHelper; + +import com.tangosol.io.ByteArrayWriteBuffer; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.NamedCache; +import com.tangosol.net.Service; +import com.tangosol.net.Session; + +import com.tangosol.net.security.IdentityAsserter; +import com.tangosol.net.security.UsernameAndPassword; + +import com.tangosol.util.Base; +import com.tangosol.util.Filter; + +import com.tangosol.util.InvocableMap.EntryProcessor; + +import java.io.DataInput; +import java.io.IOException; + +import java.nio.ByteBuffer; + +import java.security.PrivilegedExceptionAction; + +import java.util.concurrent.Executor; + +import javax.security.auth.Subject; + +import static com.tangosol.net.cache.TypeAssertion.withoutTypeChecking; + +/** + * Memcached default request handler implementation. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class DefaultRequestHandler + implements RequestHandler + { + + // ----- Constructors --------------------------------------------------- + /** + * Construct a DefaultRequestHandler according to the specified parameters. + * + * @param sCacheName the cache name + * @param parentSvc the parent ProxyService + * @param sAuthMethod the authentication method to use + * @param fBinaryPassThru the flag indicating if binary pass-thru is enabled + * @param asserter Identity Asserter + * @param executor Task Executor + * @param flowControl Connection Flow Control + */ + public DefaultRequestHandler(String sCacheName, Service parentSvc, String sAuthMethod, boolean fBinaryPassThru, + IdentityAsserter asserter, Executor executor, ConnectionFlowControl flowControl) + { + f_sCacheName = sCacheName; + f_parentService = parentSvc; + f_sAuthMethod = sAuthMethod.toUpperCase(); + f_fBinaryPassThru = fBinaryPassThru; + f_identityAsserter = asserter; + f_executor = executor; + f_flowControl = flowControl; + if (f_sAuthMethod.equals(NONE_AUTH_METHOD)) + { + // no security configured. Can get the cache reference now. + ensureCache(/*Subject*/ null); + } + } + + // ----- RequestHandler methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Subject getSubject(Request request) + { + int nOpCode = request.getOpCode(); + if (f_sAuthMethod.equals(NONE_AUTH_METHOD) || + nOpCode == 0x1e || nOpCode == 0x20 || nOpCode == 0x21) // opCodes for auth requests + { + return null; + } + else + { + if (m_subject == null) + { + throw new SecurityException("Null subject"); + } + + return m_subject; + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onGet(Request request, Response response) + throws IOException + { + fireEP(request, response, new GetProcessor(f_fBinaryPassThru)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onGetComplete(Request request, Response response, Object oReturn) + throws IOException + { + int nResponseCode = getResponseCode(oReturn); + response.setResponseCode(nResponseCode); + if (nResponseCode == Response.ResponseCode.OK.getCode()) + { + DataHolder holder = toDataHolder(oReturn); + ByteBuffer bufExtras = (ByteBuffer) ByteBuffer.allocate(FLAG_SIZE).clear().mark(); + + bufExtras.putInt(holder.getFlag()).reset(); + response.setVersion(getVersion(holder)).setExtras(bufExtras).setValue(holder.getValue()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onSet(Request request, Response response) + throws IOException + { + long lVersion = request.getVersion(); + DataInput inExtras = request.getExtras(); + int nFlag = inExtras.readInt(); + int nExpiry = MemcachedHelper.calculateExpiry(inExtras.readInt()); + fireEP(request, response, new PutProcessor(request.getValue(), nFlag, lVersion, nExpiry, f_fBinaryPassThru)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onSetComplete(Request request, Response response, Object oReturn) + throws IOException + { + response.setResponseCode(getResponseCode(oReturn)).setVersion(getVersion(oReturn)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onAdd(Request request, Response response) + throws IOException + { + addReplace(request, response, /*fInsert*/true); + } + + /** + * {@inheritDoc} + */ + @Override + public void onAddComplete(Request request, Response response, Object oReturn) + throws IOException + { + addReplaceComplete(request, response, oReturn); + } + + /** + * {@inheritDoc} + */ + @Override + public void onReplace(Request request, Response response) + throws IOException + { + addReplace(request, response, /*fInsert*/false); + } + + /** + * {@inheritDoc} + */ + @Override + public void onReplaceComplete(Request request, Response response, Object oReturn) + throws IOException + { + addReplaceComplete(request, response, oReturn); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDelete(Request request, Response response) + throws IOException + { + fireEP(request, response, new DeleteProcessor()); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDeleteComplete(Request request, Response response, Object oReturn) + throws IOException + { + response.setResponseCode(getResponseCode(oReturn)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onIncrement(Request request, Response response) + throws IOException + { + incrDecr(request, response, /*fIncr*/true); + } + + /** + * {@inheritDoc} + */ + @Override + public void onIncrementComplete(Request request, Response response, Object oReturn) + throws IOException + { + incrDecrComplete(request, response, oReturn); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDecrement(Request request, Response response) + throws IOException + { + incrDecr(request, response, /*fIncr*/false); + } + + /** + * {@inheritDoc} + */ + @Override + public void onDecrementComplete(Request request, Response response, Object oReturn) + throws IOException + { + incrDecrComplete(request, response, oReturn); + } + + /** + * {@inheritDoc} + */ + @Override + public void onAppend(Request request, Response response) + throws IOException + { + appendPrepend(request, response, /*fAppend*/true); + } + + /** + * {@inheritDoc} + */ + @Override + public void onAppendComplete(Request request, Response response, Object oReturn) + throws IOException + { + appendPrependComplete(request, response, oReturn); + } + + /** + * {@inheritDoc} + */ + @Override + public void onPrepend(Request request, Response response) + throws IOException + { + appendPrepend(request, response, /*fAppend*/false); + } + + /** + * {@inheritDoc} + */ + @Override + public void onPrependComplete(Request request, Response response, Object oReturn) + throws IOException + { + appendPrependComplete(request, response, oReturn); + } + + /** + * {@inheritDoc} + */ + @Override + public void onFlush(Request request, Response response) + throws IOException + { + DataInput extras = request.getExtras(); + int nExpiry = (extras == null) ? 0 : MemcachedHelper.calculateExpiry(extras.readInt()); + EntryProcessor ep = (nExpiry == 0) ? new DeleteProcessor() + : new TouchProcessor(nExpiry, /*fBlind*/true, f_fBinaryPassThru); + getCache().invokeAll((Filter) null, new MemcachedAsyncProcessor(this, request, response, ep)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onFlushComplete(Request request, Response response, Object oReturn) + throws IOException + { + response.setResponseCode(ResponseCode.OK.getCode()); + } + + /** + * {@inheritDoc} + */ + @Override + public void onTouch(Request request, Response response) + throws IOException + { + DataInput extras = request.getExtras(); + int nExpiry = (extras == null) ? 0 : MemcachedHelper.calculateExpiry(extras.readInt()); + fireEP(request, response, new TouchProcessor(nExpiry, /*fBlind*/true, f_fBinaryPassThru)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onTouchComplete(Request request, Response response, Object oReturn) + throws IOException + { + response.setResponseCode(getResponseCode(oReturn)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onGAT(Request request, Response response) + throws IOException + { + DataInput inExtras = request.getExtras(); + int nExpiry = MemcachedHelper.calculateExpiry(inExtras.readInt()); + fireEP(request, response, new TouchProcessor(nExpiry, /*fBlind*/false, f_fBinaryPassThru)); + } + + /** + * {@inheritDoc} + */ + @Override + public void onGATComplete(Request request, Response response, Object oReturn) + throws IOException + { + int nResponseCode = getResponseCode(oReturn); + response.setResponseCode(nResponseCode); + if (nResponseCode == Response.ResponseCode.OK.getCode()) + { + DataHolder holder = toDataHolder(oReturn); + response.setVersion(getVersion(holder)); + response.setValue(holder.getValue()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void onSASLList(Request request, Response response) + throws IOException + { + response.setResponseCode(Response.ResponseCode.OK.getCode()).setValue("PLAIN".getBytes()); + } + + /** + * {@inheritDoc} + */ + @Override + public void onSASLAuth(Request request, Response response) + throws IOException + { + String sKey = request.getKey(); + int nResponseCode = Response.ResponseCode.OK.getCode(); + + if (f_sAuthMethod.equals(NONE_AUTH_METHOD)) + { + nResponseCode = ResponseCode.UNKNOWN.getCode(); + } + else if (f_sAuthMethod.equals(PLAIN_AUTH_METHOD) && sKey.equals(PLAIN_AUTH_METHOD)) + { + try + { + m_subject = authenticate(request.getValue()); + ensureCache(m_subject); + } + catch (Throwable thr) + { + CacheFactory.log("Memcached authentication failure. " + thr, CacheFactory.LOG_ERR); + nResponseCode = ResponseCode.AUTH_ERROR.getCode(); + } + } + else + { + nResponseCode = ResponseCode.AUTH_ERROR.getCode(); + } + + response.setResponseCode(nResponseCode); + } + + /** + * {@inheritDoc} + */ + @Override + public void onSASLAuthStep(Request request, Response response) + throws IOException + { + response.setResponseCode(ResponseCode.UNKNOWN.getCode()); + } + + /** + * {@inheritDoc} + */ + @Override + public void flush() + { + if (m_asyncProcessor != null) + { + m_asyncProcessor.flush(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean checkBacklog(Continuation backlogEndedContinuation) + { + MemcachedAsyncProcessor proc = m_asyncProcessor; + return proc != null && proc.checkBacklog(backlogEndedContinuation); + } + + /** + * Handle add or replace request. + * + * @param request Request + * @param response Response + * @param fInsert Flag to indicate if its an add or replace operation. + * + * @throws IOException + */ + protected void addReplace(Request request, Response response, boolean fInsert) + throws IOException + { + long lVersion = request.getVersion(); + DataInput inExtras = request.getExtras(); + int nFlag = inExtras.readInt(); + int nExpiry = MemcachedHelper.calculateExpiry(inExtras.readInt()); + fireEP(request, response, new AddReplaceProcessor(request.getValue(), nFlag, lVersion, nExpiry, + fInsert, f_fBinaryPassThru)); + } + + /** + * Handle add or replace response from the Async EntryProcessor. + * + * @param request Request + * @param response Response + * @param oReturn Object returned from the EP. + * + * @throws IOException + */ + protected void addReplaceComplete(Request request, Response response, Object oReturn) + throws IOException + { + response.setResponseCode(getResponseCode(oReturn)).setVersion(getVersion(oReturn)); + } + + /** + * Handle increment/decrement request. + * + * @param request Request + * @param response Response + * @param fIncr Flag to indicate if its an increment operation. + * + * @throws IOException + */ + protected void incrDecr(Request request, Response response, boolean fIncr) + throws IOException + { + long lVersion = request.getVersion(); + DataInput inExtras = request.getExtras(); + long lIncr = inExtras.readLong(); + long lInitial = inExtras.readLong(); + int nExpiry = MemcachedHelper.calculateExpiry(inExtras.readInt()); + fireEP(request, response, new IncrDecrProcessor(lInitial, lIncr, fIncr, lVersion, nExpiry, f_fBinaryPassThru)); + } + + + /** + * Handle incr or decr response from the Async EntryProcessor. + * + * @param request Request + * @param response Response + * @param oReturn Object returned from the EP. + * + * @throws IOException + */ + protected void incrDecrComplete(Request request, Response response, Object oReturn) + throws IOException + { + int nResponseCode = getResponseCode(oReturn); + response.setResponseCode(nResponseCode); + if (nResponseCode == ResponseCode.OK.getCode()) + { + DataHolder holder = toDataHolder(oReturn); + Long lValue = IncrDecrProcessor.getLong(holder.getValue()); + ByteArrayWriteBuffer buf = new ByteArrayWriteBuffer(8); + buf.getBufferOutput().writeLong(lValue); + response.setValue(buf.toByteArray()).setVersion(getVersion(holder)); + } + } + + /** + * Handle Append/Prepend request. + * + * @param request Request + * @param response Response + * @param fAppend true iff the request is an APPEND operation + * + * @throws IOException + */ + protected void appendPrepend(Request request, Response response, boolean fAppend) + throws IOException + { + long lVersion = request.getVersion(); + fireEP(request, response, new AppendPrependProcessor(request.getValue(), lVersion, fAppend, f_fBinaryPassThru)); + } + + /** + * Handle append or prepend response from the Async EntryProcessor. + * + * @param request Request + * @param response Response + * @param oReturn Object returned from the EP. + * + * @throws IOException + */ + protected void appendPrependComplete(Request request, Response response, Object oReturn) + throws IOException + { + int nResponseCode = getResponseCode(oReturn); + response.setResponseCode(nResponseCode).setVersion(getVersion(oReturn)); + } + + /** + * Get the response code from the EntryProcessor return value. + * + * @param oValue the object received from the EntryProcessor + * + * @return the response code + */ + protected int getResponseCode(Object oValue) + { + if (oValue instanceof Response.ResponseCode) + { + return ((Response.ResponseCode) oValue).getCode(); + } + return Response.ResponseCode.OK.getCode(); + } + + /** + * Get the version from the EntryProcessor return value. + * + * @param oValue the object received from the EntryProcessor + * + * @return the Response code + */ + protected long getVersion(Object oValue) + { + long lVersion = 0; + if (oValue instanceof DataHolder) + { + lVersion = ((DataHolder) oValue).getVersion(); + } + else if (oValue instanceof Long) // return value itself is the version + { + lVersion = ((Long) oValue).longValue(); + } + return lVersion; + } + + /** + * Convert the object to a DataHolder. + * + * @param obj the Object to convert to DataHolder + * + * @return the object as a DataHolder + */ + protected DataHolder toDataHolder(Object obj) + { + if (obj instanceof DataHolder) + { + return (DataHolder) obj; + } + + throw new IllegalArgumentException("Cannot convert " + obj + " to DataHolder"); + } + + /** + * Perform SASL Plain Authentication and return authenticated Subject. + * + * @param abPayload the value from the request message + * + * @return the authenticated Subject if successful, null otherwise + */ + protected Subject authenticate(byte[] abPayload) + { + // payload structure - authorization token ASCII-NUL Username ASCII-NUL Password + String sPayload = new String(abPayload); + int of1 = sPayload.indexOf(SEP); // authorization token. + int of2 = sPayload.indexOf(SEP, of1 + 1); // username token + String sUser = sPayload.substring(of1 + 1, of2); + String sPwd = sPayload.substring(of2 + 1); + return f_identityAsserter.assertIdentity(new UsernameAndPassword(sUser, sPwd), f_parentService); + } + + /** + * Ensure the NamedCache associated with the RequestHandler. + * + * @param subject Subject associated with the client + */ + protected void ensureCache(final Subject subject) + { + f_flowControl.pauseReads(); + f_executor.execute(new Runnable() + { + @Override + public void run() + { + try + { + System.out.println("@@@@ Requesting Memcached Cache " + f_sCacheName + " using subject " + subject); + + if (subject == null) + { + System.out.println("@@@@ No Subject Provided"); + + if (f_parentService instanceof Session) + { + System.out.println("@@@@ Using Session " + f_parentService + " class " + f_parentService.getClass()); + + m_cache = ((Session) f_parentService).getCache(f_sCacheName, withoutTypeChecking()); + } + else + { + System.out.println("@@@@ Using Cache Factory"); + + m_cache = CacheFactory.getCache(f_sCacheName, withoutTypeChecking()); + } + } + else + { + System.out.println("@@@@ Using Subject " + subject); + + m_cache = Subject.doAs(subject, new PrivilegedExceptionAction() + { + public NamedCache run() + throws IOException + { + return f_parentService instanceof Session + ? ((Session) f_parentService).getCache(f_sCacheName, withoutTypeChecking()) + : CacheFactory.getCache(f_sCacheName, withoutTypeChecking()); + } + }); + } +// +// m_cache = (subject == null) +// ? f_parentService instanceof Session +// ? ((Session) f_parentService).getCache(f_sCacheName, withoutTypeChecking()) +// : CacheFactory.getCache(f_sCacheName, withoutTypeChecking()) +// : Subject.doAs(subject, new PrivilegedExceptionAction() +// { +// public NamedCache run() +// throws IOException +// { +// return f_parentService instanceof Session +// ? ((Session) f_parentService).getCache(f_sCacheName, withoutTypeChecking()) +// : CacheFactory.getCache(f_sCacheName, withoutTypeChecking()); +// } +// }); + + System.out.println("@@@@ Memcached Cache = " + m_cache); + } + catch (Throwable thr) + { + CacheFactory.log("Memcached adapter failed to get cache " + f_sCacheName + ": " + + Base.printStackTrace(thr), CacheFactory.LOG_ERR); + } + finally + { + f_flowControl.resumeReads(); + } + } + }); + } + + /** + * Execute the given EntryProcessor async. + * + * @param request Request + * @param response Response + * @param ep EntryProcess to execute async + */ + protected void fireEP(Request request, Response response, EntryProcessor ep) + { + MemcachedAsyncProcessor asyncProcessor = new MemcachedAsyncProcessor(this, request, response, ep); + getCache().invoke(request.getKey(), asyncProcessor); + m_asyncProcessor = asyncProcessor; + } + + /** + * Return a valid cache reference. + * + * @return NamedCache + * + * @throws RuntimeException if cache ref. is null + */ + protected NamedCache getCache() + { + if (m_cache == null) + { + throw new RuntimeException("Null cache"); + } + return m_cache; + } + + // ----- data members --------------------------------------------------- + + /** + * Cache name that is associated with the memcached adapter + */ + protected final String f_sCacheName; + + /** + * NamedCache associated with the Request handler/connection. + */ + protected NamedCache m_cache; + + /** + * Flag indicating if binary pass thru is enabled. + */ + protected final boolean f_fBinaryPassThru; + + /** + * The configured authorization method. + */ + protected final String f_sAuthMethod; + + /** + * Parent proxy service associated with the Memcached Adapter. + */ + protected final Service f_parentService; + + /** + * The authenticated subject. + */ + protected Subject m_subject; + + /** + * IdentityAsserter to use with SASL PLAIN authentication. + */ + protected final IdentityAsserter f_identityAsserter; + + /** + * Task Executor + */ + protected final Executor f_executor; + + /** + * Connection FlowControl. + */ + protected final ConnectionFlowControl f_flowControl; + + /** + * Last EP fired by the handler. Its ref. is needed to flush the service queue. + */ + protected MemcachedAsyncProcessor m_asyncProcessor; + + // ----- constants ------------------------------------------------------ + + /** + * ASCII NULL. + */ + protected static final byte SEP = 0; // US-ASCII + + /** + * Flag size. + */ + protected static final int FLAG_SIZE = 4; + + /** + * NONE Auth method; implies no authentication. + */ + protected static final String NONE_AUTH_METHOD = "NONE"; + + /** + * PLAIN Auth method; implies username/pwd authentication. + */ + protected static final String PLAIN_AUTH_METHOD = "PLAIN"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/Request.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/Request.java new file mode 100644 index 0000000000000..e671a59cec034 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/Request.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached; + +import com.oracle.coherence.common.base.Disposable; + +import com.tangosol.net.cache.KeyAssociation; + +import java.io.DataInput; + +/** + * Memcached request interface. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public interface Request + extends Disposable, KeyAssociation + { + /** + * Return the request op code. + * + * @return the request op code + */ + public int getOpCode(); + + /** + * Return the extra data associated with this request; the content is operation specific. + * + * @return the extra data associated with the request + */ + public DataInput getExtras(); + + /** + * Return the key associated with this request. + * + * @return key the key associated with this request + */ + public String getKey(); + + /** + * Return the value associated with this request. + * + * @return the value associated with this request. + */ + public byte[] getValue(); + + /** + * Return the version associated with this request. + * + * @return version associated with this request + */ + public long getVersion(); + + /** + * Return the Response object for this request. + * + * @return the Response object + */ + public Response getResponse(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/RequestHandler.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/RequestHandler.java new file mode 100644 index 0000000000000..a3cede7b9894f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/RequestHandler.java @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached; + +import com.oracle.coherence.common.base.Continuation; + +import java.io.IOException; + +import javax.security.auth.Subject; + +/** + * Memcached RequestHandler Interface. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public interface RequestHandler + { + /** + * Return the current subject associated with the handler. + * + * @param request the request to determine the subject for + * + * @return the Subject associated with the specified request + * + * @throws SecurityException if the request handler is configured with an + * authorization method and the request does not carry a subject + */ + Subject getSubject(Request request); + + /** + * Handle a Get Request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onGet(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Get EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onGetComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle a Set Request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onSet(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Set EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onSetComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle an Add request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onAdd(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Add EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onAddComplete(Request request, Response response, Object oReturn) + throws IOException; + + + /** + * Handle a Replace request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onReplace(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Replace EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onReplaceComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle a Delete request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onDelete(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Delete EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onDeleteComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle an Increment request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onIncrement(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Increment EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onIncrementComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle a Decrement request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onDecrement(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Decrement EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onDecrementComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle an Append request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onAppend(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Append EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onAppendComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle a Prepend request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onPrepend(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Prepend EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onPrependComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle a Flush request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onFlush(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Flush EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onFlushComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle a Touch request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onTouch(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the Touch EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onTouchComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle Get and Touch requests. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onGAT(Request request, Response response) + throws IOException; + + /** + * Handle the Response of the GAT EntryProcessor. + * + * @param request the Request + * @param response the Response + * @param oReturn Object returned from the EP + * + * @throws IOException + */ + void onGATComplete(Request request, Response response, Object oReturn) + throws IOException; + + /** + * Handle a supported SASL mechanisms request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onSASLList(Request request, Response response) + throws IOException; + + /** + * Handle SASL Authentication. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onSASLAuth(Request request, Response response) + throws IOException; + + /** + * Handle a SASL authentication continuation request. + * + * @param request the Request + * @param response the Response + * + * @throws IOException + */ + void onSASLAuthStep(Request request, Response response) + throws IOException; + + /** + * Signal the Handler that there are no more requests to execute in the + * current batch. For async execution, this allows the Handler to flush all + * the batched requests. + */ + void flush(); + + /** + * Check if the associated cache service is backlogged. + * + * @param backlogEndedContinuation Continuation to call when backlog ends. + * + * @return true iff associated cache service is backlogged. + */ + boolean checkBacklog(Continuation backlogEndedContinuation); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/Response.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/Response.java new file mode 100644 index 0000000000000..dccd8ceae4253 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/Response.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached; + +import com.oracle.coherence.common.base.Disposable; + +import java.nio.ByteBuffer; + +/** + * Memcached Response Interface. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public interface Response + extends Disposable + { + /** + * Set the response code. + * + * @param nResponseCode the response code to set + * + * @return this Response + */ + public Response setResponseCode(int nResponseCode); + + /** + * Get the response code. + * + * @return response code + */ + public int getResponseCode(); + + /** + * Set the version for the entry. + * + * @param lVersion the version to set + * + * @return this Response + */ + public Response setVersion(long lVersion); + + /** + * Set the key in the response. + * + * @param sKey the Key to set + * + * @return this Response + */ + public Response setKey(String sKey); + + /** + * Set the value in the response. + * + * @param abValue the value to set + * + * @return this Response + */ + public Response setValue(byte[] abValue); + + /** + * Set the extra data for the response. + * + * @param extras payload for the extra data field + * + * @return this Response + */ + public Response setExtras(ByteBuffer extras); + + // ----- inner enum: ResponseCode --------------------------------------- + + /** + * Memcached Response Codes. + */ + public static enum ResponseCode + { + OK(0x0000), + KEYNF(0x0001), + KEYEXISTS(0x0002), + TOOLARGE(0x0003), + INVARG(0x0004), + NOT_STORED(0x0005), + NAN(0x0006), + AUTH_ERROR(0x0008), + AUTH_CONTINUE(0x0009), + UNKNOWN(0x0081), + OOM(0x00082), + NOT_SUPPORTED(0x0083), + INTERNAL_ERROR(0x0084), + BUSY(0x085), + TEMPORARY_FAILURE(0x086); + + /** + * Constructor. + * + * @param nCode numeric Response code + */ + ResponseCode(int nCode) + { + m_sCode = (short) nCode; + } + + /** + * Get the response code. + * + * @return numeric response code + */ + public short getCode() + { + return m_sCode; + } + + /** + * Numeric response code. + */ + protected short m_sCode; + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/AddReplaceProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/AddReplaceProcessor.java new file mode 100644 index 0000000000000..6d9dadcf9e099 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/AddReplaceProcessor.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.processor; + +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.coherence.memcached.server.MemcachedHelper; + +import com.tangosol.io.ExternalizableLite; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.Binary; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.InvocableMap.Entry; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * AddReplaceProcessor is an EntryProcessor that will conditionally add or replace + * the cache entry based on the memcached protocol rules. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class AddReplaceProcessor + extends PutProcessor + implements ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor (necessary for the ExternalizableLite interface). + */ + public AddReplaceProcessor() + { + } + + /** + * Constructor. + * + * @param abValue byte[] value to store + * @param nFlag flag sent in the memcached request + * @param lVersion data version sent in the memcached request + * @param nExpiry expiry for the entry + * @param fAllowInsert flag to indicate if it is an add or replace operation + * @param fBinaryPassThru binary pass-thru flag needed to deserialize the binary entry + */ + public AddReplaceProcessor(byte[] abValue, int nFlag, long lVersion, int nExpiry, boolean fAllowInsert, + boolean fBinaryPassThru) + { + super(abValue, nFlag, lVersion, nExpiry, fBinaryPassThru); + m_fAllowInsert = fAllowInsert; + } + + // ----- EntryProcessor methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object process(Entry entry) + { + // Memcached binary protocol constraints: + // * Add MUST fail if the item already exist + // * Replace MUST fail if the item doesn't exist + BinaryEntry binaryEntry = MemcachedHelper.getBinaryEntry(entry); + Binary binValue = binaryEntry.getBinaryValue(); + + if (!m_fAllowInsert && binValue == null) + { + return ResponseCode.KEYNF; + } + if (m_fAllowInsert && binValue != null) + { + return ResponseCode.KEYEXISTS; + } + + return super.process(entry); + } + + // ----- ExternalizableLite methods ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) + throws IOException + { + super.readExternal(in); + m_fAllowInsert = in.readBoolean(); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) + throws IOException + { + super.writeExternal(out); + out.writeBoolean(m_fAllowInsert); + } + + // ----- PortableObject methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_fAllowInsert = in.readBoolean(10); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeBoolean(10, m_fAllowInsert); + } + + // ----- data members --------------------------------------------------- + + /** + * Flag indicating if its an add or replace operation. + */ + protected boolean m_fAllowInsert; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/AppendPrependProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/AppendPrependProcessor.java new file mode 100644 index 0000000000000..5a0818b8637af --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/AppendPrependProcessor.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.processor; + +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.coherence.memcached.server.DataHolder; +import com.tangosol.coherence.memcached.server.MemcachedHelper; + +import com.tangosol.io.ExternalizableLite; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.net.BackingMapManagerContext; + +import com.tangosol.util.Binary; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.InvocableMap.Entry; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import java.nio.ByteBuffer; + +/** + * AppendPrependProcessor is an EntryProcessor that will append or prepend the + * passed in value to the existing value in the cache. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class AppendPrependProcessor + extends PutProcessor + implements ExternalizableLite, PortableObject + { + + // ----- constructors --------------------------------------------------- + + /** + * Default constructor (necessary for the ExternalizableLite interface). + */ + public AppendPrependProcessor() + { + } + + /** + * Constructor. + * + * @param obDelta delta value to be prepended or appended. + * @param lVersion data version sent in the memcached request + * @param fAppend flag to indicate if the delta has to be appended or prepended. + * @param fBinaryPassThru binary pass-thru flag needed to deserialize the binary entry + */ + public AppendPrependProcessor(byte[] obDelta, long lVersion, boolean fAppend, boolean fBinaryPassThru) + { + super(null, 0, lVersion, 0, fBinaryPassThru); + m_abDelta = obDelta; + m_fAppend = fAppend; + } + + // ----- EntryProcessor methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object process(Entry entry) + { + BinaryEntry binaryEntry = MemcachedHelper.getBinaryEntry(entry); + Binary binValue = binaryEntry.getBinaryValue(); + if (binValue == null) + { + return ResponseCode.KEYNF; + } + + BackingMapManagerContext mgrCtx = binaryEntry.getBackingMapContext().getManagerContext(); + DataHolder holder = MemcachedHelper.convertToDataHolder(binValue, mgrCtx, m_fBinaryPassThru); + byte[] abValueOld = holder.getValue(); + ByteBuffer buf = ByteBuffer.allocate(abValueOld.length + m_abDelta.length); + + if (m_fAppend) + { + buf.put(abValueOld); + buf.put(m_abDelta); + } + else + { + buf.put(m_abDelta); + buf.put(abValueOld); + } + m_abValue = buf.array(); + m_nFlag = holder.getFlag(); + + return super.process(entry); + } + + // ----- ExternalizableLite methods ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) + throws IOException + { + super.readExternal(in); + int len = in.readInt(); + if (len > 0) + { + m_abDelta = new byte[len]; + in.readFully(m_abDelta); + } + m_fAppend = in.readBoolean(); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) + throws IOException + { + super.writeExternal(out); + if (m_abDelta == null) + { + out.writeInt(0); + } + else + { + out.writeInt(m_abDelta.length); + out.write(m_abDelta); + } + out.writeBoolean(m_fAppend); + } + + // ----- PortableObject methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_abDelta = in.readByteArray(10); + m_fAppend = in.readBoolean(11); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeByteArray(10, m_abDelta); + out.writeBoolean(11, m_fAppend); + } + + // ----- data members --------------------------------------------------- + + /** + * Delta value to be appended or prepended. + */ + protected byte[] m_abDelta; + + /** + * Flag to indicate if its an append or prepend operation. + */ + protected boolean m_fAppend; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/DeleteProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/DeleteProcessor.java new file mode 100644 index 0000000000000..da2b7f609f6d4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/DeleteProcessor.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.processor; + +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.io.ExternalizableLite; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.InvocableMap.Entry; + +import com.tangosol.util.processor.AbstractProcessor; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * DeleteProcessor deletes the binary entry. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class DeleteProcessor + extends AbstractProcessor + implements ExternalizableLite, PortableObject + { + + // ----- constructors --------------------------------------------------- + + /** + * Default constructor (necessary for the ExternalizableLite interface). + */ + public DeleteProcessor() + { + } + + // ----- EntryProcessor methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object process(Entry entry) + { + if (entry.isPresent()) + { + entry.remove(/*fSynthetic*/ false); + return ResponseCode.OK; + } + return ResponseCode.KEYNF; + } + + // ----- ExternalizableLite methods ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) + throws IOException + { + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) + throws IOException + { + } + + // ----- PortableObject methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) + throws IOException + { + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter out) + throws IOException + { + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/GetProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/GetProcessor.java new file mode 100644 index 0000000000000..d472945a04d7b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/GetProcessor.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.processor; + +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.coherence.memcached.server.MemcachedHelper; + +import com.tangosol.io.ExternalizableLite; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.net.BackingMapManagerContext; + +import com.tangosol.util.Binary; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.InvocableMap.Entry; + +import com.tangosol.util.processor.AbstractProcessor; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * GetProcessor is an EntryProcessor that will fetch the value and the decorations (flag and version) + * of the binary entry associated with the key. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class GetProcessor + extends AbstractProcessor + implements ExternalizableLite, PortableObject + { + + // ----- constructors --------------------------------------------------- + + /** + * Default constructor (necessary for the ExternalizableLite interface). + */ + public GetProcessor() + { + } + + /** + * Constructor + * + * @param fBinaryPassThru binary pass-thru flag needed to deserialize the binary entry + */ + public GetProcessor(boolean fBinaryPassThru) + { + m_fBinaryPassThru = fBinaryPassThru; + } + + // ----- EntryProcessor methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object process(Entry entry) + { + BinaryEntry binaryEntry = MemcachedHelper.getBinaryEntry(entry); + Binary binValue = binaryEntry.getBinaryValue(); + + if (binValue == null) + { + return ResponseCode.KEYNF; + } + + BackingMapManagerContext mgrCtx = binaryEntry.getBackingMapContext().getManagerContext(); + return MemcachedHelper.convertToDataHolder(binValue, mgrCtx, m_fBinaryPassThru); + } + + // ----- ExternalizableLite methods ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) + throws IOException + { + m_fBinaryPassThru = in.readBoolean(); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) + throws IOException + { + out.writeBoolean(m_fBinaryPassThru); + } + + // ----- PortableObject methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) + throws IOException + { + m_fBinaryPassThru = in.readBoolean(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter out) + throws IOException + { + out.writeBoolean(0, m_fBinaryPassThru); + } + + // ----- data members --------------------------------------------------- + + /** + * Flag to indicate if binary pass thru is enabled. + */ + protected boolean m_fBinaryPassThru; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/IncrDecrProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/IncrDecrProcessor.java new file mode 100644 index 0000000000000..69af219adcae8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/IncrDecrProcessor.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.processor; + +import com.tangosol.coherence.memcached.Response; +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.coherence.memcached.server.DataHolder; +import com.tangosol.coherence.memcached.server.MemcachedHelper; + +import com.tangosol.io.ExternalizableLite; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.net.BackingMapManagerContext; + +import com.tangosol.util.Binary; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.InvocableMap.Entry; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * IncrDecrProcessor is an EntryProcessor that will increment or decrement the + * stringified value stored in the cache. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class IncrDecrProcessor + extends PutProcessor + implements ExternalizableLite, PortableObject + { + + // ----- constructors --------------------------------------------------- + + /** + * Default constructor (necessary for the ExternalizableLite interface). + */ + public IncrDecrProcessor() + { + } + + /** + * Constructor. + * + * @param lInitial Initial seed value + * @param lIncr Increment or decrement value + * @param fIncr flag to indicate if its a increment or decrement operation + * @param lVersion data version sent in the memcached request + * @param nExpiry expiry for the entry + * @param fBinaryPassThru binary pass-thru flag needed to deserialize the binary entry + */ + public IncrDecrProcessor(long lInitial, long lIncr, boolean fIncr, long lVersion, int nExpiry, + boolean fBinaryPassThru) + { + super(null, 0, lVersion, nExpiry, fBinaryPassThru); + m_lInitial = lInitial; + m_lIncr = lIncr; + m_fIncr = fIncr; + } + + // ----- EntryProcessor methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object process(Entry entry) + { + // Memcached binary protocol constraints: + // Add or remove the specified amount to the requested counter. To set + // the value of the counter with add/set/replace, the object's data + // must be the ASCII representation of the value and not the byte values + // of a 64 bit integer. + // + // If the counter does not exist, one of two things may happen: + // 1. If the expiration value is all one-bits (0xffffffff), the + // operation will fail with NOT_FOUND. + // 2. For all other expiration values, the operation will succeed by seeding + // the value for this key with the provided initial value to expire with + // the provided expiration time and the flags will be set to zero. + // Decrementing a counter will never result in a "negative value" (or + // cause the counter to "wrap"); instead the counter is set to 0. + // Incrementing the counter may cause the counter to wrap. + + BinaryEntry binaryEntry = MemcachedHelper.getBinaryEntry(entry); + Binary binValue = binaryEntry.getBinaryValue(); + BackingMapManagerContext mgrCtx = binaryEntry.getBackingMapContext().getManagerContext(); + try + { + if (binValue == null) + { + if (m_nExpiry == 0xffffffff) + { + return ResponseCode.KEYNF; + } + m_abValue = String.valueOf(m_lInitial).getBytes("utf-8"); + } + else + { + DataHolder holder = MemcachedHelper.convertToDataHolder(binValue, mgrCtx, m_fBinaryPassThru); + long lValue = getLong(holder.getValue()); + + m_nFlag = holder.getFlag(); + m_abValue = String.valueOf(calculateNewValue(lValue)).getBytes(); + } + + Object oReturn = super.process(entry); + return oReturn instanceof Response.ResponseCode + ? oReturn + : MemcachedHelper.convertToDataHolder(binaryEntry.getBinaryValue(), mgrCtx, m_fBinaryPassThru); + } + catch (Exception ex) + { + return ResponseCode.NAN; + } + } + + // ----- ExternalizableLite methods ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) + throws IOException + { + super.readExternal(in); + m_lInitial = in.readLong(); + m_lIncr = in.readLong(); + m_fIncr = in.readBoolean(); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) + throws IOException + { + super.writeExternal(out); + out.writeLong(m_lInitial); + out.writeLong(m_lIncr); + out.writeBoolean(m_fIncr); + } + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) + throws IOException + { + super.readExternal(in); + m_lInitial = in.readLong(10); + m_lIncr = in.readLong(11); + m_fIncr = in.readBoolean(12); + } + + // ----- PortableObject methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter out) + throws IOException + { + super.writeExternal(out); + out.writeLong(10, m_lInitial); + out.writeLong(11, m_lIncr); + out.writeBoolean(12, m_fIncr); + } + + /** + * Calculate the updated value based on the memcached protocol rules. + * + * @param lValueOld old value + * + * @return computed new value + */ + protected long calculateNewValue(long lValueOld) + { + // decrementing cannot result in -ve value. + return (m_fIncr) + ? lValueOld + m_lIncr + : Math.max(0, lValueOld - m_lIncr); + } + + // ----- static helpers ------------------------------------------------- + + /** + * Get the long value from its stringified form. + * + * @param abValue byte[] of the stringified value + * + * @return the long value + */ + public static Long getLong(byte[] abValue) + { + return Long.valueOf(MemcachedHelper.getString(abValue)); + } + + // ----- data members --------------------------------------------------- + + /** + * Initial seed value. + */ + protected long m_lInitial; + + /** + * Increment/decrement value. + */ + protected long m_lIncr; + + /** + * Flag to indicate if this is an increment/decrement operation. + */ + protected boolean m_fIncr; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/MemcachedAsyncProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/MemcachedAsyncProcessor.java new file mode 100644 index 0000000000000..8b6f468d4919d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/MemcachedAsyncProcessor.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.processor; + +import com.tangosol.coherence.memcached.Request; +import com.tangosol.coherence.memcached.RequestHandler; +import com.tangosol.coherence.memcached.Response; +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.coherence.memcached.server.Task; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.util.Base; +import com.tangosol.util.InvocableMap.EntryProcessor; + +import com.tangosol.util.processor.AsynchronousProcessor; + +import java.util.Iterator; +import java.util.Map; + +/** + * MemcachedAsyncProcessor is an async marker/wrapper class for executing various + * memcached EP asynchronously. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class MemcachedAsyncProcessor + extends AsynchronousProcessor + { + + /** + * Constructor. + * @param handler RequestHandler to call when the EP returns. + * @param request Memcached request + * @param response Memcached resposne + * @param processor EP to execute async. + */ + public MemcachedAsyncProcessor(RequestHandler handler, Request request, Response response, EntryProcessor processor) + { + super(processor, ((Integer) request.getAssociatedKey()).intValue()); + m_handler = handler; + m_request = request; + m_response = response; + } + + /** + * {@inheritDoc} + */ + @Override + public void onComplete() + { + super.onComplete(); + RequestHandler handler = m_handler; + Request request = m_request; + Response response = m_response; + boolean fQuiet = false; + try + { + Object oReturn = getReturnValue(); + switch (m_request.getOpCode()) + { + case 0x00: // GET Request + { + handler.onGetComplete(request, response, oReturn); + break; + } + case 0x01: // SET Request + { + handler.onSetComplete(request, response, oReturn); + break; + } + case 0x02: // ADD Request + { + handler.onAddComplete(request, response, oReturn); + break; + } + case 0x03: // REPLACE Request + { + handler.onReplaceComplete(request, response, oReturn); + break; + } + case 0x04: // DELETE Request + { + handler.onDeleteComplete(request, response, oReturn); + break; + } + case 0x05: // INCREMENT Request + { + handler.onIncrementComplete(request, response, oReturn); + break; + } + case 0x06: // DECREMENT Request + { + handler.onDecrementComplete(request, response, oReturn); + break; + } + case 0x08: // FLUSH Request + { + handler.onFlushComplete(request, response, oReturn); + break; + } + case 0x09: // GETQ Request + { + handler.onGetComplete(request, response, oReturn); + if (response.getResponseCode() == ResponseCode.KEYNF.getCode()) + { + fQuiet = true; + } + break; + } + case 0x0a: // NO-OP Request + { + response.setResponseCode(ResponseCode.OK.getCode()); + break; + } + case 0x0c: // GETK Request + { + response.setKey(request.getKey()); + handler.onGetComplete(request, response, oReturn); + break; + } + case 0x0d: // GETKQ Request + { + response.setKey(request.getKey()); + handler.onGetComplete(request, response, oReturn); + if (response.getResponseCode() == ResponseCode.KEYNF.getCode()) + { + fQuiet = true; + } + break; + } + case 0x0e: // APPEND Request + { + handler.onAppendComplete(request, response, oReturn); + break; + } + case 0x0f: // PREPEND Request + { + handler.onPrependComplete(request, response, oReturn); + break; + } + case 0x11: // SETQ Request + { + handler.onSetComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + case 0x12: // ADDQ Request + { + handler.onAddComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + case 0x13: // REPLACEQ Request + { + handler.onReplaceComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + case 0x14: // DELETEQ Request + { + handler.onDeleteComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + case 0x15: // INCREMENTQ Request + { + handler.onIncrementComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + case 0x16: // DECREMENTQ Request + { + handler.onDecrementComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + case 0x18: // FLUSHQ Request + { + handler.onFlushComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + case 0x19: // APPENDQ Request + { + handler.onAppendComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + case 0x1a: // PREPENDQ Request + { + handler.onPrependComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + case 0x1c: // TOUCH Request + { + handler.onTouchComplete(request, response, oReturn); + break; + } + case 0x1d: // GAT Request + { + handler.onGATComplete(request, response, oReturn); + break; + } + case 0x1e: // GATQ Request + { + handler.onGATComplete(request, response, oReturn); + fQuiet = response.getResponseCode() == ResponseCode.OK.getCode(); + break; + } + default: + { + CacheFactory.log("Memcached adapter received unknown request in EP response: " + + request.getOpCode(), CacheFactory.LOG_ERR); + response.setResponseCode(Response.ResponseCode.INTERNAL_ERROR.getCode()); + } + } + } + catch (Throwable thr) + { + CacheFactory.log("Exception in handling memcached async response: "+ + Base.printStackTrace(thr), CacheFactory.LOG_ERR); + response.setResponseCode(Response.ResponseCode.INTERNAL_ERROR.getCode()); + fQuiet = false; + } + finally + { + Task.flush(response, fQuiet); + } + } + + /** + * Get the object returned by the EP. + * + * @return Object returned from the EP. + * + * @throws Exception + */ + protected Object getReturnValue() throws Exception + { + Map map = (Map) get(); + if (map != null) + { + Iterator itr = map.values().iterator(); + return (itr.hasNext()) ? itr.next() : null; + } + return null; + } + + // ----- data members --------------------------------------------------- + + /** + * RequestHandler to call when the async EP returns. + */ + protected RequestHandler m_handler; + + /** + * Memcached request. + */ + protected Request m_request; + + /** + * Memcached response. + */ + protected Response m_response; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/PutProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/PutProcessor.java new file mode 100644 index 0000000000000..965be4445ba97 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/PutProcessor.java @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.processor; + +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.coherence.memcached.server.DataHolder; +import com.tangosol.coherence.memcached.server.MemcachedHelper; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.WriteBuffer; +import com.tangosol.io.WriteBuffer.BufferOutput; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.net.BackingMapManagerContext; + +import com.tangosol.util.Base; +import com.tangosol.util.Binary; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.BinaryWriteBuffer; +import com.tangosol.util.ExternalizableHelper; +import com.tangosol.util.InvocableMap.Entry; + +import com.tangosol.util.processor.AbstractProcessor; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * PutProcessor is an EntryProcessor that will store the value and the decorations + * (flag and version) in the binary entry. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class PutProcessor + extends AbstractProcessor + implements ExternalizableLite, PortableObject + { + + // ----- constructors --------------------------------------------------- + + /** + * Default constructor (necessary for the ExternalizableLite interface). + */ + public PutProcessor() + { + } + + /** + * Constructor. + * + * @param abValue byte[] value + * @param nFlag flag sent in the memcached request + * @param lVersion data version sent in the memcached request + * @param nExpiry expiry for the entry + * @param fBinaryPassThru binary pass-thru flag needed to deserialize the binary entry + */ + public PutProcessor(byte[] abValue, int nFlag, long lVersion, int nExpiry, boolean fBinaryPassThru) + { + m_abValue = abValue; + m_nFlag = nFlag; + m_lVersion = lVersion; + m_nExpiry = nExpiry; + m_fBinaryPassThru = fBinaryPassThru; + } + + // ----- EntryProcessor methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object process(Entry entry) + { + /* + * Memcached binary protocol constraints: + * If the Data Version Check (CAS) is nonzero, the requested operation + * MUST only succeed if the item exists and has a CAS value identical to the provided value. + */ + BinaryEntry binaryEntry = MemcachedHelper.getBinaryEntry(entry); + Binary binValue = binaryEntry.getBinaryValue(); + BackingMapManagerContext ctxMgr = binaryEntry.getBackingMapContext().getManagerContext(); + long lVersion = m_lVersion; + DataHolder oHolder = (binValue != null) + ? MemcachedHelper.convertToDataHolder(binValue, ctxMgr, m_fBinaryPassThru) + : null; + if (lVersion != 0) + { + if (oHolder == null) + { + return ResponseCode.KEYNF; + } + else + { + if (oHolder.getVersion() != lVersion) + { + return ResponseCode.KEYEXISTS; + } + } + } + lVersion = (oHolder != null) ? oHolder.getVersion() + 1 : ++lVersion; + binaryEntry.updateBinaryValue(getDecoratedBinary(ctxMgr, lVersion)); + if (m_nExpiry > 0) + { + binaryEntry.expire(m_nExpiry); + } + return lVersion; + } + + // ----- ExternalizableLite methods ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) + throws IOException + { + int len = in.readInt(); + if (len > 0) + { + m_abValue = new byte[len]; + in.readFully(m_abValue); + } + m_nFlag = in.readInt(); + m_lVersion = in.readLong(); + m_nExpiry = in.readInt(); + m_fBinaryPassThru = in.readBoolean(); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) + throws IOException + { + if (m_abValue == null) + { + out.writeInt(0); + } + else + { + out.writeInt(m_abValue.length); + out.write(m_abValue); + } + + out.writeInt(m_nFlag); + out.writeLong(m_lVersion); + out.writeInt(m_nExpiry); + out.writeBoolean(m_fBinaryPassThru); + } + + // ----- PortableObject methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) + throws IOException + { + m_abValue = in.readByteArray(0); + m_nFlag = in.readInt(1); + m_lVersion = in.readLong(2); + m_nExpiry = in.readInt(3); + m_fBinaryPassThru = in.readBoolean(4); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter out) + throws IOException + { + out.writeByteArray(0, m_abValue); + out.writeInt(1, m_nFlag); + out.writeLong(2, m_lVersion); + out.writeInt(3, m_nExpiry); + out.writeBoolean(4, m_fBinaryPassThru); + } + + // ----- internal helper methods ---------------------------------------- + + /** + * Get the decorated binary. + * + * @param mgrCtx Backing map manager context + * @param lVersion Entry version + * + * @return decorated binary + */ + protected Binary getDecoratedBinary(BackingMapManagerContext mgrCtx, long lVersion) + { + Binary bin = m_fBinaryPassThru + ? new Binary(m_abValue) + : (Binary) mgrCtx.getValueToInternalConverter().convert(m_abValue); + WriteBuffer bufDeco = new BinaryWriteBuffer(12, 12); + try + { + BufferOutput out = bufDeco.getBufferOutput(); + out.writeInt(m_nFlag); + out.writeLong(lVersion); + + return (Binary) mgrCtx.addInternalValueDecoration( + bin, ExternalizableHelper.DECO_MEMCACHED, bufDeco.toBinary()); + } + catch (IOException e) + { + throw Base.ensureRuntimeException(e); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The byte[] value. + */ + protected byte[] m_abValue; + + /** + * Flag sent in the memcached request. + */ + protected int m_nFlag; + + /** + * Data version sent in the memcached request. + */ + protected long m_lVersion; + + /** + * Entry expiry time. + */ + protected int m_nExpiry; + + /** + * Flag to indicate if binary pass thru is enabled. + */ + protected boolean m_fBinaryPassThru; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/TouchProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/TouchProcessor.java new file mode 100644 index 0000000000000..e73902de43a9f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/processor/TouchProcessor.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.processor; + +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.coherence.memcached.server.MemcachedHelper; + +import com.tangosol.io.ExternalizableLite; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.net.BackingMapManagerContext; + +import com.tangosol.util.Binary; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.InvocableMap.Entry; + +import com.tangosol.util.processor.AbstractProcessor; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * TouchProcessor is an EntryProcessor that will update the expiry time of the entry and + * optionally return the stored entry. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class TouchProcessor + extends AbstractProcessor + implements ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor (necessary for the ExternalizableLite interface). + */ + public TouchProcessor() + { + } + + /** + * Constructor. + * + * @param nExpiry expiry for the entry + * @param fBlind flag to indicate if returned value is not required. + * @param fBinaryPassThru binary pass-thru flag needed to deserialize the binary entry + */ + public TouchProcessor(int nExpiry, boolean fBlind, boolean fBinaryPassThru) + { + m_nExpiry = nExpiry; + m_fBlind = fBlind; + m_fBinaryPassThru = fBinaryPassThru; + } + + // ----- EntryProcessor methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object process(Entry entry) + { + BinaryEntry binaryEntry = MemcachedHelper.getBinaryEntry(entry); + Binary binValue = binaryEntry.getBinaryValue(); + if (binValue == null) + { + return ResponseCode.KEYNF; + } + if (m_nExpiry >= 0) + { + binaryEntry.expire(m_nExpiry); + } + + BackingMapManagerContext mgrCtx = binaryEntry.getBackingMapContext().getManagerContext(); + return m_fBlind + ? null + : MemcachedHelper.convertToDataHolder(binValue, mgrCtx, m_fBinaryPassThru); + } + + // ----- ExternalizableLite methods ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) + throws IOException + { + m_nExpiry = in.readInt(); + m_fBlind = in.readBoolean(); + m_fBinaryPassThru = in.readBoolean(); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) + throws IOException + { + out.writeInt(m_nExpiry); + out.writeBoolean(m_fBlind); + out.writeBoolean(m_fBinaryPassThru); + } + + // ----- PortableObject methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) + throws IOException + { + m_nExpiry = in.readInt(0); + m_fBlind = in.readBoolean(1); + m_fBinaryPassThru = in.readBoolean(2); + } + + @Override + public void writeExternal(PofWriter out) + throws IOException + { + out.writeInt(0, m_nExpiry); + out.writeBoolean(1, m_fBlind); + out.writeBoolean(2, m_fBinaryPassThru); + } + + // ----- data members --------------------------------------------------- + + /** + * Expiry time + */ + protected int m_nExpiry; + + /** + * Flag to indicate if entry needs to be returned or not. + */ + protected boolean m_fBlind; + + /** + * Flag to indicate if binary pass thru is enabled. + */ + protected boolean m_fBinaryPassThru; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/BinaryConnection.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/BinaryConnection.java new file mode 100644 index 0000000000000..67cd6ba1c917d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/BinaryConnection.java @@ -0,0 +1,1226 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.server; + +import com.oracle.coherence.common.base.Disposable; + +import com.oracle.coherence.common.internal.net.socketbus.SharedBuffer; +import com.oracle.coherence.common.internal.net.socketbus.SharedBuffer.Disposer; +import com.oracle.coherence.common.internal.net.socketbus.SharedBuffer.Segment; + +import com.oracle.coherence.common.io.BufferManager; +import com.oracle.coherence.common.io.BufferSequence; + +import com.oracle.coherence.common.net.SelectionService; + +import com.tangosol.coherence.memcached.Request; +import com.tangosol.coherence.memcached.Response; + +import com.tangosol.internal.io.BufferSequenceWriteBufferPool; + +import com.tangosol.io.MultiBufferWriteBuffer; +import com.tangosol.io.ReadBuffer; +import com.tangosol.io.ReadBuffer.BufferInput; +import com.tangosol.io.WriteBuffer.BufferOutput; +import com.tangosol.net.CacheFactory; + +import com.tangosol.util.Base; + +import java.io.DataInput; +import java.io.IOException; + +import java.nio.ByteBuffer; + +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SocketChannel; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * Memcached Binary protocol connection. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class BinaryConnection + implements Connection, Disposer + { + + // ----- constructors --------------------------------------------------- + + /** + * Construct a Binary Connection. + * + * @param bufMgr BufferManager + * @param channel SocketChannel + * @param nConnId Connection Id + */ + public BinaryConnection(BufferManager bufMgr, SocketChannel channel, int nConnId) + { + m_bufferManager = bufMgr; + m_channel = channel; + m_nConnId = nConnId; + } + + // ----- Connection methods --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void setFlowControl(ConnectionFlowControl flowCtrl) + { + m_flowControl = flowCtrl; + } + + /** + * {@inheritDoc} + */ + @Override + public SocketChannel getChannel() + { + return m_channel; + } + + /** + * {@inheritDoc} + */ + @Override + public int getId() + { + return m_nConnId; + } + + /** + * {@inheritDoc} + */ + @Override + public List read() + throws IOException + { + try + { + long cbAlloc = m_cbRequired - m_cbReadable; + SharedBuffer[] aSharedBuffer = cbAlloc > 0 ? ensureCapacity(cbAlloc) : m_aSharedBuffer; + ByteBuffer[] aBuffer = getBuffers(aSharedBuffer); + int of = m_ofWritable; // offset into aBuffer from where to start writing + int cBuffer = m_cBufferWritable; // no. of writable buffers + long cb = read(aBuffer, of, cBuffer); + if (cb > 0) + { + for (; cBuffer > 0 && !aBuffer[of].hasRemaining(); ++of, --cBuffer) + { + aBuffer[of].reset(); // prepare buffers for reading + } + m_ofWritable = of; + m_cBufferWritable = cBuffer; + m_cbWritable -= cb; + long cbReady = m_cbReadable += cb; + + if (cbReady >= m_cbRequired) + { + // we have read enough bytes to parse the complete message. + if (cBuffer > 0 && aBuffer[of].position() > 0) + { + // multiple buffers to process + ByteBuffer buffLast = aBuffer[of]; + // save the current write position + int iPosWrite = buffLast.position(); + // the last buffer may be partially readable. Set the limit on the last buffer for reading + // and also reset the position to mark where we left reading in the last iteration. + // We will reset the limit when we start writing. + try + { + buffLast.limit(iPosWrite).reset(); + return onReady(aBuffer); + } + finally + { + // mark the position up to which we have read so that + // we could starting reading from that position in the + // next iteration. + buffLast.mark(); + // reset limit for writing into the buffer. + buffLast.limit(buffLast.capacity()).position(iPosWrite); + } + } + else + { + return onReady(aBuffer); + } + } + } + return Collections.emptyList(); + } + catch (IOException ioe) + { + throw ioe; + } + catch (Throwable thr) + { + CacheFactory.log(Base.printStackTrace(thr), CacheFactory.LOG_ERR); + throw Base.ensureRuntimeException(thr); + } + } + + /** + * {@inheritDoc} + */ + @Override + public int write() + throws IOException + { + try + { + Iterator itr = f_delegatedWrites.iterator(); + while (itr.hasNext()) + { + BinaryResponse response = itr.next(); + if (response.write()) + { + itr.remove(); + } + else + { + break; + } + } + return itr.hasNext() ? SelectionService.Handler.OP_WRITE : 0; + } + catch (Throwable thr) + { + throw Base.ensureRuntimeException(thr); + } + } + + // ----- Disposer methods ----------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void dispose(ByteBuffer buffer) + { + m_bufferManager.release(buffer); + } + + // ----- internal methods ----------------------------------------------- + + /** + * Create and return a list of Requests from the specified array of read buffers. + * + * @param aBuffer array of buffers holding data read from the socket channel + * + * @return the request list + * + * @throws IOException + */ + protected List onReady(ByteBuffer[] aBuffer) + throws IOException + { + ArrayList listRequest = new ArrayList(); + SharedBuffer[] aSharedBuffer = m_aSharedBuffer; + int ofReadable = m_ofReadable; + long cbReadable = m_cbReadable; + long cbRequired = m_cbRequired; + while (cbReadable >= cbRequired) + { + DisposableReadBuffer readBuffer = null; + if (aBuffer[ofReadable].remaining() >= cbRequired) + { + // We can read the reqd. bytes from the current buffer itself. + ByteBuffer buf = aBuffer[ofReadable]; + + // save the buffer limit. Carving out a segment will move the + // buffer's limit to the end of the segment position. + int nLimit = buf.limit(); + Segment segment = aSharedBuffer[ofReadable].getSegment(buf.position(), (int) cbRequired); + readBuffer = new DisposableReadBuffer(new Segment[] { segment }); + + // reset buffer limit and forward buffer position. + buf.position(buf.position() + (int) cbRequired).limit(nLimit); + } + else + { + // Reqd. bytes spans multiple buffers + long cbTmpRequired = cbRequired; + List listSegments = new ArrayList(); + while (cbTmpRequired > 0) + { + ByteBuffer buf = aBuffer[ofReadable]; + + // save the buffer limit. Carving out a segment will move the + // buffer's limit to the end of the segment position. + int nLimit = buf.limit(); + int cbAvailable = buf.remaining(); + if (cbTmpRequired > cbAvailable) + { + // carve out a segment for all of the remaining buffer content + listSegments.add(aSharedBuffer[ofReadable].getSegment()); + ofReadable++; + } + else + { + // only need to read buffer partially. + listSegments.add(aSharedBuffer[ofReadable].getSegment(buf.position(), (int) cbTmpRequired)); + + // reset buffer limit and forward buffer position after carving out the segment. + buf.position(buf.position() + (int) cbTmpRequired).limit(nLimit); + } + cbTmpRequired -= cbAvailable; + } + Segment[] aSegment = listSegments.toArray(new Segment[listSegments.size()]); + readBuffer = new DisposableReadBuffer(aSegment); + } + + cbReadable -= cbRequired; + if (m_fHeader) + { + BinaryHeader hdr = m_requestHeader = new BinaryHeader(readBuffer); + cbRequired = Math.abs(hdr.getBodyLength()); + m_fHeader = false; + } + else + { + BinaryRequest request = new BinaryRequest(m_requestHeader, readBuffer, m_bufferManager, this); + listRequest.add(request); + f_queueResponses.add(request.getResponse()); + cbRequired = HEADER_LEN; + m_fHeader = true; + } + } + + m_cbRequired = cbRequired; + + if (ofReadable > 0) + { + // detach buffers that have been completely read and compact remaining + // buffers to the front of the array + for (int i = 0, cBuffers = aBuffer.length; i < cBuffers; i++) + { + if (i < ofReadable) + { + // everything up to ofReadable has been read, so it needs to be + // detached and null'd out (it may later be copied over during + // compaction) + aSharedBuffer[i].detach(); + } + else + { + // everything past ofReadable is still "active" and should be + // compacted to the head of the array + aSharedBuffer[i - ofReadable] = aSharedBuffer[i]; + } + aSharedBuffer[i] = null; + } + } + + m_ofWritable -= ofReadable; + m_ofReadable = 0; + m_cbReadable = cbReadable; + return listRequest; + } + + /** + * Ensure read buffer capacity to read the required bytes. + * + * @param cbReqd the number of bytes to ensure capacity to read + * + * @return an array of SharedBuffers + */ + protected SharedBuffer[] ensureCapacity(long cbReqd) + { + SharedBuffer[] aSharedBuffer = m_aSharedBuffer; + int ofReadable = m_ofReadable; + int ofWritable = m_ofWritable; + long cbWritable = m_cbWritable; + int cBufferWritable = m_cBufferWritable; + int cBuffer = aSharedBuffer.length; + long cbAlloc = cbReqd - cbWritable; + + if (cbAlloc > 0) + { + // count no. of additional buffers needed + int nBufNeeded = (int) (cbReqd / BUF_SIZE) + 1; + int nBufAvailable = cBuffer - ofWritable; + if (nBufNeeded > nBufAvailable) + { + SharedBuffer[] aBufferNew = new SharedBuffer[cBuffer * 2]; + System.arraycopy(aSharedBuffer, 0, aBufferNew, 0, cBuffer); + aSharedBuffer = aBufferNew; + cBuffer = aSharedBuffer.length; + } + BufferManager manager = m_bufferManager; + for (int i = ofWritable; i < cBuffer && aSharedBuffer[i] == null && cbWritable < cbReqd; i++) + { + ByteBuffer buff = manager.acquirePref((int) Math.min(Integer.MAX_VALUE, BUF_SIZE)); + buff.clear().mark(); + int cbBuff = buff.remaining(); + cbAlloc -= Math.min(cbBuff, cbAlloc); + cbWritable += cbBuff; + ++cBufferWritable; + aSharedBuffer[i] = new SharedBuffer(buff, this).attach(); + } + } + m_aSharedBuffer = aSharedBuffer; + m_ofReadable = ofReadable; + m_ofWritable = ofWritable; + m_cbWritable = cbWritable; + m_cBufferWritable = cBufferWritable; + + return aSharedBuffer; + } + + /** + * Write responses in the order of the received requests. If there are pending responses, + * the current response will be queued else it will be written out on the adding thread. + * The queued responses will be written out on the SelectionService thread whenever + * the channel is write-ready. + * + * @param response the response to write + * + * @throws IOException + */ + public void writeResponse(BinaryResponse response) + throws IOException + { + if (f_delegatedWrites.isEmpty() && response.write()) + { + return; + } + f_delegatedWrites.add(response); + } + + /** + * Read from the socket channel. + * + * @param aBuf array of ByteBuffers to read into + * @param offset offset into the ByteBuffer array to read into + * @param length length of the ByteBuffer array + * + * @return number of bytes read + * + * @throws IOException + */ + protected long read(ByteBuffer[] aBuf, int offset, int length) + throws IOException + { + long cb = m_channel.read(aBuf, offset, length); + if (cb < 0) + { + throw new IOException("InputShutdown during reading"); + } + + return cb; + } + + /** + * Get an array of the underlying ByteBuffer's contained in the specified + * array of SharedBuffers. + * + * @param aSharedBuffer the array of shared buffers + * + * @return underlying ByteBuffer[] + */ + protected ByteBuffer[] getBuffers(SharedBuffer[] aSharedBuffer) + { + ArrayList listBuf = new ArrayList(); + for (int i = 0, c = aSharedBuffer.length; i < c; i++) + { + SharedBuffer sBuf = aSharedBuffer[i]; + if (sBuf == null) + { + break; + } + + listBuf.add(sBuf.get()); + } + + return listBuf.toArray(new ByteBuffer[listBuf.size()]); + } + + /** + * Enable Writes on Selection Service thread if there are back-logged responses + * that couldn't be written completely by the service thread. + * + * @throws IOException + */ + protected void checkWrites() throws IOException + { + if (!f_delegatedWrites.isEmpty()) + { + m_flowControl.resumeWrites(); + } + } + + // ----- inner class: BinaryHeader -------------------------------------- + + /** + * Binary Message Header. + */ + protected static class BinaryHeader + { + /** + * Read the header message. + * + * @param readBuffer Read Buffer + * + * @throws IOException + */ + public BinaryHeader(DisposableReadBuffer readBuffer) throws IOException + { + BufferInput bufInput = readBuffer.getBufferInput(); + m_readBuffer = readBuffer; + + // magic should be 0x80 + int magic = bufInput.readUnsignedByte(); + if (magic != 0x80) + { + throw new IOException("invalid magic byte - " + magic); + } + + m_nOpCode = bufInput.readUnsignedByte(); + m_sKeyLength = bufInput.readShort(); + m_nExtraLength = bufInput.readUnsignedByte(); + m_nDataType = bufInput.readUnsignedByte(); // unused + m_sReserved = bufInput.readShort(); // unused + m_nBodyLength = bufInput.readInt(); + m_nOpaque = bufInput.readInt(); + m_lVersion = bufInput.readLong(); + } + + /** + * Get the extras length from the header. + * + * @return extras length + */ + public int getExtrasLength() + { + return m_nExtraLength; + } + + /** + * Get the body length. + * + * @return body length + */ + public int getBodyLength() + { + return m_nBodyLength; + } + + /** + * Get Key offset. + * + * @return key offset + */ + public int getKeyOffset() + { + return m_nExtraLength; + } + + /** + * Get key length. + * + * @return key length + */ + public int getKeyLength() + { + return m_sKeyLength; + } + + /** + * Get value offset. + * + * @return value offset + */ + public int getValueOffset() + { + return m_nExtraLength + m_sKeyLength; + } + + /** + * Get value length. + * + * @return value length + */ + public int getValueLength() + { + return m_nBodyLength - m_sKeyLength - m_nExtraLength; + } + + /** + * Get opaque value. + * + * @return opaque value + */ + public int getOpaqueValue() + { + return m_nOpaque; + } + + /** + * Get version. + * + * @return version + */ + public long getVersion() + { + return m_lVersion; + } + + // ----- data members ----------------------------------------------- + + /** + * Request Op Code + */ + protected final int m_nOpCode; + + /** + * Key length + */ + protected final short m_sKeyLength; + + /** + * Extra Length + */ + protected final int m_nExtraLength; + + /** + * Data type + */ + protected final int m_nDataType; + + /** + * Reserved field + */ + protected final short m_sReserved; + + /** + * Body length + */ + protected final int m_nBodyLength; + + /** + * Opaque value + */ + protected final int m_nOpaque; + + /** + * Version + */ + protected final long m_lVersion; + + /** + * Underlying read buffer + */ + protected final DisposableReadBuffer m_readBuffer; + } + + // ----- inner class: BinaryRequest ------------------------------------- + + /** + * Binary Request. + */ + protected static class BinaryRequest implements Request + { + /** + * Construct a BinaryRequest. + * + * @param header Request Header + * @param readBuffer Underlying read buffer + * @param bufMgr BufferManager + * @param conn Underlying connection + */ + public BinaryRequest(BinaryHeader header, DisposableReadBuffer readBuffer, BufferManager bufMgr, + BinaryConnection conn) + { + m_header = header; + m_bufferManager = bufMgr; + m_conn = conn; + m_bufPayLoad = readBuffer; + m_lId = conn.m_cRequests++; + m_response = new BinaryResponse(m_bufferManager, m_conn, this); + } + + /** + * {@inheritDoc} + */ + @Override + public int getOpCode() + { + return header().m_nOpCode; + } + + /** + * Get the message header + * + * @return header + */ + public BinaryHeader header() + { + return m_header; + } + + /** + * {@inheritDoc} + */ + @Override + public DataInput getExtras() + { + BinaryHeader hdr = m_header; + ReadBuffer readBuffer = m_bufPayLoad; + int nLen = hdr.getExtrasLength(); + return (nLen > 0) ? readBuffer.getReadBuffer(0, nLen).getBufferInput() : null; + } + + /** + * {@inheritDoc} + */ + @Override + public String getKey() + { + BinaryHeader hdr = m_header; + ReadBuffer readBuffer = m_bufPayLoad; + return MemcachedHelper.getString( + readBuffer.getReadBuffer(hdr.getKeyOffset(), hdr.getKeyLength()).toByteArray()); + } + + /** + * {@inheritDoc} + */ + @Override + public byte[] getValue() + { + BinaryHeader hdr = m_header; + ReadBuffer readBuffer = m_bufPayLoad; + return readBuffer.getReadBuffer(hdr.getValueOffset(), hdr.getValueLength()).toByteArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public long getVersion() + { + return header().getVersion(); + } + + /** + * {@inheritDoc} + */ + @Override + public BinaryResponse getResponse() + { + return m_response; + } + + /** + * {@inheritDoc} + */ + @Override + public Object getAssociatedKey() + { + return m_conn.getId(); + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + header().m_readBuffer.dispose(); + m_bufPayLoad.dispose(); + } + + // ----- data members ----------------------------------------------- + + /** + * Binary Header + */ + protected BinaryHeader m_header; + + /** + * Binary Response + */ + protected BinaryResponse m_response; + + /** + * Read Buffer + */ + protected DisposableReadBuffer m_bufPayLoad; + + /** + * Buffer Manager + */ + protected BufferManager m_bufferManager; + + /** + * Binary Connection + */ + protected BinaryConnection m_conn; + + /** + * Request Id + */ + protected volatile long m_lId; + } + + // ----- inner class: BinaryResponse ------------------------------------ + + /** + * Binary Response. + */ + protected static class BinaryResponse implements Response, Disposable + { + /** + * Construct a Binary Response. + * + * @param bufMgr BufferManager + * @param conn Underlying Connection + * @param request Associated request + */ + public BinaryResponse(BufferManager bufMgr, BinaryConnection conn, BinaryRequest request) + { + m_bufferManager = bufMgr; + m_conn = conn; + m_request = request; + } + + /** + * {@inheritDoc} + */ + @Override + public BinaryResponse setResponseCode(int nResponseCode) + { + m_nResponseCode = nResponseCode; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public int getResponseCode() + { + return m_nResponseCode; + } + + /** + * {@inheritDoc} + */ + @Override + public BinaryResponse setVersion(long lVersion) + { + m_lVersion = lVersion; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public BinaryResponse setKey(String sKey) + { + m_sKey = sKey; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public BinaryResponse setValue(byte[] value) + { + m_value = value; + return this; + } + + /** + * {@inheritDoc} + */ + @Override + public BinaryResponse setExtras(ByteBuffer extras) + { + m_extras = extras; + return this; + } + + /** + * Create the response message and send it back to the client. + * + * @param fDispose flag indicating iff the response needs to be discarded. + */ + public void flush(boolean fDispose) + { + boolean fClose = false; + try + { + BinaryRequest request = m_request; + m_fDisposeOnly = fDispose; + m_aBuffers = fDispose ? null : getBuffers(request.getOpCode() == 0x10); + BinaryResponse response = this; + BinaryConnection conn = m_conn; + ResponseQueue queue = conn.f_queueResponses; + boolean fFlush = queue.isFlushable(response, true); + do + { + if (fFlush) + { + if (response.m_fDisposeOnly) + { + response.dispose(); + } + else + { + m_conn.writeResponse(response); + } + response = queue.removeAndGetNext(response); + } + else + { + // Its imp. to recheck if the response is flushable after we have + // marked it deferred because we could become the head if the previous + // response is concurrently processed. + queue.markDeferred(response); + } + } + while (fFlush = queue.isFlushable(response, /*fOwner*/false)); + conn.checkWrites(); + } + catch (ClosedChannelException cce) { } /*ignore*/ + catch (IOException ioe) + { + fClose = true; + } + catch (Throwable thr) + { + fClose = true; + CacheFactory.log("Exception while writing response: "+Base.printStackTrace(thr), CacheFactory.LOG_ERR); + } + finally + { + if (fClose) + { + try + { + m_conn.getChannel().close(); + } + catch (IOException ioe) { } /*ignore*/ + } + } + } + + /** + * Write the response on the socket channel. + * + * @return true iff response completely written on the socket channel + * + * @throws IOException + */ + public boolean write() + throws IOException + { + SocketChannel channel = m_conn.getChannel(); + ByteBuffer[] abuffers = m_aBuffers; + + int offset = m_nOffset; + int cBuffers = m_cBuffers; + int nOpCode = m_request.getOpCode(); + if (nOpCode == 0x17) + { + // QuitQ request + channel.close(); + return true; + } + + while (cBuffers > 0) + { + if (channel.write(abuffers, offset, cBuffers) > 0) + { + for (int i = offset; i < abuffers.length && !abuffers[i].hasRemaining(); i++) + { + offset++; + cBuffers--; + } + } + else + { + m_nOffset = offset; + m_cBuffers = cBuffers; + return false; + } + } + if (nOpCode == 0x07) + { + // Quit request + channel.close(); + } + dispose(); + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + m_request.dispose(); + } + + /** + * Serialize the response into ByteBuffers. + * + * @param fAppendEmptyPkt Flag to indicate if as additional empty response needs to added + * + * @return ByteBuffer[] + * + * @throws IOException + */ + protected ByteBuffer[] getBuffers(boolean fAppendEmptyPkt) throws IOException + { + BufferSequenceWriteBufferPool wbPool = new BufferSequenceWriteBufferPool(m_bufferManager); + BufferOutput bufOutput = new MultiBufferWriteBuffer(wbPool).getBufferOutput(); + + bufOutput.write((byte) 0x81); // magic + bufOutput.write((byte) m_request.getOpCode()); // opcode + + short keyLength = m_sKey == null ? 0 : (short) m_sKey.length(); + bufOutput.writeShort(keyLength); + + int extrasLength = m_extras == null ? 0 : m_extras.remaining(); + bufOutput.writeByte((byte) extrasLength); // extra length = flags + expiry + + bufOutput.writeByte((byte) 0); // data type unused + bufOutput.writeShort(m_nResponseCode); // status code + + int dataLength = (m_value != null) ? m_value.length : 0; + bufOutput.writeInt(dataLength + keyLength + extrasLength); // data length + bufOutput.writeInt(m_request.header().getOpaqueValue()); // opaque + bufOutput.writeLong(m_lVersion); + + BufferSequence headerSeq = wbPool.toBufferSequence(); + + ByteBuffer[] aBufEmpty = null; + if (fAppendEmptyPkt) + { + BinaryResponse emptyResponse = new BinaryResponse(m_bufferManager, m_conn, m_request); + aBufEmpty = emptyResponse.getBuffers(/*fAppendEmptyPkt*/ false); + } + + m_cBuffers = headerSeq.getBufferCount() + (m_extras != null ? 1 : 0) + (m_sKey != null ? 1 : 0) + + (m_value != null ? 1 : 0) + + (fAppendEmptyPkt ? aBufEmpty.length : 0); + + ByteBuffer[] buffers = new ByteBuffer[m_cBuffers]; + ByteBuffer[] aHdr = headerSeq.getBuffers(); + int i = 0; + for (; i < aHdr.length; i++) + { + buffers[i] = aHdr[i]; + } + if (m_extras != null) + { + buffers[i++] = m_extras; + } + if (m_sKey != null) + { + buffers[i++] = ByteBuffer.wrap(m_sKey.getBytes("utf-8")); + } + if (m_value != null) + { + buffers[i++] = ByteBuffer.wrap(m_value); + } + if (fAppendEmptyPkt) + { + for (int j = 0; j < aBufEmpty.length; j++) + { + buffers[i++] = aBufEmpty[j]; + } + } + return buffers; + } + + // ----- data members ----------------------------------------------- + + /** + * The BufferManager. + */ + protected BufferManager m_bufferManager; + + /** + * The BinaryConnection. + */ + protected BinaryConnection m_conn; + + /** + * The BinaryRequest. + */ + protected BinaryRequest m_request; + + /** + * The Response code. + */ + protected int m_nResponseCode; + + /** + * The Version. + */ + protected long m_lVersion; + + /** + * The Key. + */ + protected String m_sKey; + + /** + * The Value. + */ + protected byte[] m_value; + + /** + * The Extras. + */ + protected ByteBuffer m_extras; + + /** + * The array of ByteBuffers to store Response message in. + */ + protected ByteBuffer[] m_aBuffers; + + /** + * The offset in the m_aBuffers[] from where data have to be written. + */ + protected int m_nOffset; + + /** + * The length of the m_aBuffers[] from where data have to be written. + */ + protected int m_cBuffers; + + /** + * Flag indicating if the response is ready to be sent to the client. + * It couldn't be sent earlier because there were pending responses before + * this one in the queue. The memcached client expects the responses in the + * same order as the requests. + */ + protected volatile boolean m_fDeferred = false; + + /** + * Flag indicating if the response needs to be disposed and not sent to the client. + */ + protected boolean m_fDisposeOnly; + + /** + * Next response to be sent to the client. Responses are sent in order + * of the requests received on the connection. + */ + protected volatile BinaryResponse m_next; + } + + // ----- data members ----------------------------------------------- + + /** + * The ConnectionFlowControl. + */ + protected ConnectionFlowControl m_flowControl; + + /** + * The BufferManager. + */ + protected final BufferManager m_bufferManager; + + /** + * Connected SocketChannel. + */ + protected final SocketChannel m_channel; + + /** + * Connection id. + */ + protected final int m_nConnId; + + /** + * flag to indicate if the next message to read is a header or body. + */ + protected boolean m_fHeader = true; + + /** + * Request header. + */ + protected BinaryHeader m_requestHeader; + + /** + * Responses that couldn't be completely written out and were handed to the I/O thread for writing. + */ + protected final ConcurrentLinkedQueue f_delegatedWrites = new ConcurrentLinkedQueue(); + + /** + * Queue of responses. It defines the order in which responses needs to be sent to the client. + */ + protected final ResponseQueue f_queueResponses = new ResponseQueue(); + + /** + * Message length in bytes. + */ + protected long m_cbRequired = HEADER_LEN; + + /** + * Count of available bytes. + */ + protected long m_cbReadable; + + /** + * Offset into the shared buffer from where we need to start reading messages. + */ + protected int m_ofReadable; + + /** + * Count of bytes that can be written to the shared buffer. + */ + protected long m_cbWritable; + + /** + * Offset into the shared buffer from where we need to start writing messages. + */ + protected int m_ofWritable; + + /** + * Number of writable buffers available. + */ + protected int m_cBufferWritable; + + /** + * Shared buffer array. + */ + protected SharedBuffer[] m_aSharedBuffer = new SharedBuffer[2]; + + /** + * Request count for this connection. + */ + protected long m_cRequests; + + // ----- constants ------------------------------------------------------ + + /** + * Binary request message header length. + */ + protected static final int HEADER_LEN = 24; + + /** + * Default buffer size to be allocated from the buffer manager. + */ + protected static final int BUF_SIZE = 16 * 1024; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/Connection.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/Connection.java new file mode 100644 index 0000000000000..ce09f1da8a96d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/Connection.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.server; + +import com.tangosol.coherence.memcached.Request; + +import java.io.IOException; + +import java.nio.channels.SocketChannel; + +import java.util.List; + +/** + * Memcached Connection. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public interface Connection + { + /** + * Get the underlying socket channel. + * + * @return socket channel + */ + public SocketChannel getChannel(); + + /** + * Parse the data from the underlying channel and return a list of memcached requests. + * + * @return the List of memcached requests or null if the request is not complete + * + * @throws IOException + */ + public List read() + throws IOException; + + /** + * Write the responses on the underlying channel. + * + * @return SelectionService's operation-set bit for write operations if there + * are pending data to be written or 0 to de-register for write operations. + * + * @throws IOException + */ + public int write() + throws IOException; + + /** + * Set the FlowControl. + * + * @param flowControl the Connection FlowControl + */ + public void setFlowControl(ConnectionFlowControl flowControl); + + /** + * Return the Connection Id. + * + * @return the connection id + */ + public int getId(); + + // ----- inner interface: ConnectionFlowControl ------------------------- + + /** + * ConnectionFlowControl interface to control reading/writing requests/responses + * from the connection. This is used to do flow control of traffic. + */ + public interface ConnectionFlowControl + { + /** + * Enable SelectionService thread to start doing write operations. Only needed + * if the responses couldn't be sent back by the service thread. + */ + public void resumeWrites(); + + /** + * Pause reading new requests from the connection. + */ + public void pauseReads(); + + /** + * Resume reading requests from the connection. + */ + public void resumeReads(); + + /** + * Check if reads have been paused. + * + * @return true iff reads are paused. + */ + public boolean isReadPaused(); + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/ConnectionFactory.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/ConnectionFactory.java new file mode 100644 index 0000000000000..5bcc87fe6d9e9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/ConnectionFactory.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.server; + +import com.oracle.coherence.common.io.BufferManager; + +import java.nio.channels.SocketChannel; + +/** + * Connection Factory to create connections that can handle memcached binary + * or ASCII protocol. Currently only the binary protocol is supported. + *

+ * ConnectionFactory is not thread-safe but it used by a single SelectionService + * thread which services the ServerSocketConnection and accepts new client sockets. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class ConnectionFactory + { + // ----- constructors --------------------------------------------------- + + /** + * Constructor. + * + * @param bufMgr the BufferManager + * @param fBinary flag indicating if Binary connections need to be created + */ + public ConnectionFactory(BufferManager bufMgr, boolean fBinary) + { + m_bufferManager = bufMgr; + m_fBinary = fBinary; + } + + // ----- ConnectionFactory methods -------------------------------------- + + /** + * Create a Connection. + * + * @param channel SocketChannel that is wrapped by the Connection. + * + * @return Connection + */ + public Connection createConnection(SocketChannel channel) + { + return m_fBinary ? new BinaryConnection(m_bufferManager, channel, m_nConnId++) : null; + } + + // ----- data members --------------------------------------------------- + + /** + * The BufferManager. + */ + protected BufferManager m_bufferManager; + + /** + * Flag to indicate if binary connection is needed. + */ + protected boolean m_fBinary; + + /** + * Connection id counter. + */ + protected int m_nConnId; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/DataHolder.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/DataHolder.java new file mode 100644 index 0000000000000..912c2391236b1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/DataHolder.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.server; + +import com.tangosol.io.ExternalizableLite; + +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * DataHolder is a holder for the value and memcached decorations - flag and version. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class DataHolder + implements ExternalizableLite, PortableObject + { + + // ----- constructors --------------------------------------------------- + + /** + * Default Constructor + */ + public DataHolder() + { + } + + /** + * Construct a DataHolder. + * + * @param abValue the value + * @param nFlag the flag + * @param lVersion the version + */ + public DataHolder(byte[] abValue, int nFlag, long lVersion) + { + m_abValue = abValue; + m_nFlag = nFlag; + m_lVersion = lVersion; + } + + // ----- accessors ------------------------------------------------------ + + /** + * Return the value + * + * @return value + */ + public byte[] getValue() + { + return m_abValue; + } + + /** + * Return the flag. + * + * @return flag + */ + public int getFlag() + { + return m_nFlag; + } + + /** + * Return the version. + * + * @return version + */ + public long getVersion() + { + return m_lVersion; + } + + // ----- ExternalizableLite methods ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) + throws IOException + { + int cb = in.readInt(); + if (cb > 0) + { + m_abValue = new byte[cb]; + in.readFully(m_abValue); + } + + m_nFlag = in.readInt(); + m_lVersion = in.readLong(); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) + throws IOException + { + if (m_abValue == null) + { + out.writeInt(0); + } + else + { + out.writeInt(m_abValue.length); + out.write(m_abValue); + } + + out.writeInt(m_nFlag); + out.writeLong(m_lVersion); + } + + // ----- PortableObject methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) + throws IOException + { + m_abValue = in.readByteArray(0); + m_nFlag = in.readInt(1); + m_lVersion = in.readLong(2); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter out) + throws IOException + { + out.writeByteArray(0, m_abValue); + out.writeInt(1, m_nFlag); + out.writeLong(2, m_lVersion); + } + + // ----- data members --------------------------------------------------- + + /** + * The value. + */ + protected byte[] m_abValue; + + /** + * The flag. + */ + protected int m_nFlag; + + /** + * The version. + */ + protected long m_lVersion; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/DisposableReadBuffer.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/DisposableReadBuffer.java new file mode 100644 index 0000000000000..882aa10160981 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/DisposableReadBuffer.java @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.server; + +import com.oracle.coherence.common.base.Disposable; + +import com.oracle.coherence.common.internal.net.socketbus.SharedBuffer.Segment; + +import com.tangosol.io.MultiBufferReadBuffer; +import com.tangosol.io.ReadBuffer; + +import com.tangosol.io.nio.ByteBufferReadBuffer; + +import com.tangosol.util.Binary; +import com.tangosol.util.ByteSequence; + +import java.io.DataOutput; +import java.io.IOException; +import java.io.OutputStream; + +import java.nio.ByteBuffer; + +/** + * DisposableReadBuffer provides a ReadBuffer abstraction on top of shared + * {@link Segment Segment}. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class DisposableReadBuffer + implements ReadBuffer, Disposable + { + + // ----- constructors --------------------------------------------------- + + /** + * Construct a DisposableReadBuffer. + * + * @param aSegment underlying segments + */ + public DisposableReadBuffer(Segment[] aSegment) + { + m_aSegment = aSegment; + m_delegate = aSegment.length == 1 + ? new ByteBufferReadBuffer(aSegment[0].get()) + : new MultiBufferReadBuffer(createReadBuffer(aSegment)); + } + + // ----- ReadBuffer methods --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int length() + { + return m_delegate.length(); + } + + /** + * {@inheritDoc} + */ + @Override + public byte byteAt(int of) + { + return m_delegate.byteAt(of); + } + + /** + * {@inheritDoc} + */ + @Override + public void copyBytes(int ofBegin, int ofEnd, byte[] abDest, int ofDest) + { + m_delegate.copyBytes(ofBegin, ofEnd, abDest, ofDest); + } + + /** + * {@inheritDoc} + */ + @Override + public BufferInput getBufferInput() + { + return m_delegate.getBufferInput(); + } + + /** + * {@inheritDoc} + */ + @Override + public ReadBuffer getReadBuffer(int of, int cb) + { + return m_delegate.getReadBuffer(of, cb); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeTo(OutputStream out) + throws IOException + { + m_delegate.writeTo(out); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeTo(OutputStream out, int of, int cb) + throws IOException + { + m_delegate.writeTo(out, of, cb); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeTo(DataOutput out) + throws IOException + { + m_delegate.writeTo(out); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeTo(DataOutput out, int of, int cb) + throws IOException + { + m_delegate.writeTo(out, of, cb); + } + + @Override + public void writeTo(ByteBuffer buf) + { + m_delegate.writeTo(buf); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeTo(ByteBuffer buf, int of, int cb) + throws IOException + { + m_delegate.writeTo(buf, of, cb); + } + + /** + * {@inheritDoc} + */ + @Override + public byte[] toByteArray() + { + return m_delegate.toByteArray(); + } + + /** + * {@inheritDoc} + */ + @Override + public byte[] toByteArray(int of, int cb) + { + return m_delegate.toByteArray(of, cb); + } + + /** + * {@inheritDoc} + */ + @Override + public Binary toBinary() + { + return m_delegate.toBinary(); + } + + /** + * {@inheritDoc} + */ + @Override + public Binary toBinary(int of, int cb) + { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer toByteBuffer() + { + return m_delegate.toByteBuffer(); + } + + /** + * {@inheritDoc} + */ + @Override + public ByteBuffer toByteBuffer(int of, int cb) + { + return m_delegate.toByteBuffer(of, cb); + } + + /** + * {@inheritDoc} + */ + @Override + public ByteSequence subSequence(int ofStart, int ofEnd) + { + return m_delegate.subSequence(ofStart, ofEnd); + } + + /** + * {@inheritDoc} + */ + public Object clone() + { + throw new UnsupportedOperationException(); + } + + // ----- Disposable methods --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void dispose() + { + Segment[] aSegment = m_aSegment; + for (int i = 0, c = aSegment.length; i < c; i++) + { + aSegment[i].dispose(); + } + } + + // ----- internal methods ----------------------------------------------- + + /** + * Create read buffers from the segments. + * + * @param aSegment Segment array + * + * @return ReadBuffers + */ + protected static ReadBuffer[] createReadBuffer(Segment[] aSegment) + { + int cSegments = aSegment.length; + ReadBuffer[] aBuf = new ReadBuffer[cSegments]; + for (int i = 0; i < cSegments; i++) + { + aBuf[i] = new ByteBufferReadBuffer(aSegment[i].get()); + } + + return aBuf; + } + + // ----- data members --------------------------------------------------- + + /** + * Underlying Segments + */ + protected Segment[] m_aSegment; + + /** + * Delegate ReadBuffer + */ + protected ReadBuffer m_delegate; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/MemcachedHelper.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/MemcachedHelper.java new file mode 100644 index 0000000000000..30982c33d38bc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/MemcachedHelper.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.server; + +import com.tangosol.io.ReadBuffer; +import com.tangosol.io.WriteBuffer; +import com.tangosol.io.ReadBuffer.BufferInput; +import com.tangosol.io.WriteBuffer.BufferOutput; + +import com.tangosol.net.BackingMapManagerContext; + +import com.tangosol.util.Base; +import com.tangosol.util.Binary; +import com.tangosol.util.BinaryEntry; +import com.tangosol.util.BinaryWriteBuffer; +import com.tangosol.util.ExternalizableHelper; +import com.tangosol.util.InvocableMap.Entry; + +import java.io.IOException; + +import java.util.Date; + +/** + * Memcached Helper class. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public abstract class MemcachedHelper + { + /** + * Utility method to calculate expiry value. As per the spec: + *

+ * The actual value sent may either be Unix time (number of seconds since January 1, 1970, + * as a 32-bit value), or a number of seconds starting from current time. In the + * latter case, this number of seconds may not exceed 60*60*24*30 + * (number of seconds in 30 days); if the number sent by a client is larger than + * that, the server will consider it to be real Unix time value rather + * than an offset from current time. + * + * @param nExpiry expiry (relative or absolute in secs.) + * + * @return expiry in millis + */ + public static int calculateExpiry(int nExpiry) + { + return nExpiry <= MAX_RELATIVE_EXPIRY_TIME + ? nExpiry * 1000 + : Math.max(0, (int) (new Date(nExpiry).getTime() - System.currentTimeMillis())); + } + + /** + * Utility method to decorate a binary value with memcached specific decoration. + * + * @param bin the binary value to decorate + * @param nFlag the flag to be added to the decoration + * @param lVersion the version to be added to the decoration + * + * @return decorated binary + */ + public static Binary decorateBinary(Binary bin, int nFlag, long lVersion) + { + WriteBuffer bufDeco = new BinaryWriteBuffer(12, 12); + try + { + BufferOutput out = bufDeco.getBufferOutput(); + out.writeInt(nFlag); + out.writeLong(lVersion); + return ExternalizableHelper.decorate(bin, ExternalizableHelper.DECO_MEMCACHED, bufDeco.toBinary()); + } + catch (IOException e) + { + throw Base.ensureRuntimeException(e); + } + } + + /** + * Utility method to extract value and decorations from a decorated binary. + * + * @param bin the decorated binary + * @param mgrCtx the backing map mgr context + * @param fBinaryPassThru flag indicating if binary-pass-thru is configured + * + * @return the DataHolder + */ + public static DataHolder convertToDataHolder(Binary bin, BackingMapManagerContext mgrCtx, boolean fBinaryPassThru) + { + byte[] abValue = null; + int nFlag = 0; + long lVersion = 0L; + try + { + if (ExternalizableHelper.isDecorated(bin, ExternalizableHelper.DECO_MEMCACHED)) + { + ReadBuffer buffer = bin; + ReadBuffer bufDeco = ExternalizableHelper.getDecoration(buffer, ExternalizableHelper.DECO_MEMCACHED); + BufferInput bufInput = bufDeco.getBufferInput(); + Binary binValue = (Binary) ExternalizableHelper.getUndecorated(buffer); + + nFlag = bufInput.readInt(); + lVersion = bufInput.readLong(); + abValue = fBinaryPassThru + ? binValue.toByteArray() + : (byte[]) mgrCtx.getValueFromInternalConverter().convert(binValue); + } + else + { + // Coherence client added to cache directly + abValue = bin.toByteArray(); + } + } + catch (IOException ioe) + { + throw Base.ensureRuntimeException(ioe); + } + + return new DataHolder(abValue, nFlag, lVersion); + } + + /** + * Return a utf-8 encoded string representation of the specified byte array. + * + * @param abValue the byte[] to convert to String + * + * @return a utf-8 encoded String + */ + public static String getString(byte[] abValue) + { + try + { + return new String(abValue, UTF8); + } + catch (IOException ioe) + { + throw Base.ensureRuntimeException(ioe); + } + } + + /** + * Convert InvocableMap.Entry to BinaryEntry. + * + * @param entry InvocableMap.Entry + * + * @return BinaryEntry + * + * @throws RuntimeException if entry cannot be cast to BinaryEntry + */ + public static BinaryEntry getBinaryEntry(Entry entry) + { + try + { + return (BinaryEntry) entry; + } + catch (ClassCastException cce) + { + throw new RuntimeException( + "The MemcachedAcceptor is only supported by the DistributedCache"); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * Maximum relative time period for calculating expiry times. + */ + protected static final int MAX_RELATIVE_EXPIRY_TIME = 60 * 60 * 24 * 30; // 1 month + + /** + * UTF-8 encoding. + */ + protected static final String UTF8 = "utf-8"; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/MemcachedServer.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/MemcachedServer.java new file mode 100644 index 0000000000000..9ef3d5ffd523b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/MemcachedServer.java @@ -0,0 +1,558 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.server; + +import com.oracle.coherence.common.base.Continuation; +import com.oracle.coherence.common.io.BufferManager; +import com.oracle.coherence.common.io.BufferManagers; + +import com.oracle.coherence.common.net.SafeSelectionHandler; +import com.oracle.coherence.common.net.SelectionService; +import com.oracle.coherence.common.net.SelectionServices; +import com.oracle.coherence.common.net.SocketProvider; + +import com.tangosol.coherence.memcached.DefaultRequestHandler; +import com.tangosol.coherence.memcached.Request; +import com.tangosol.coherence.memcached.RequestHandler; + +import com.tangosol.net.CacheFactory; +import com.tangosol.net.Service; +import com.tangosol.net.security.IdentityAsserter; + +import com.tangosol.util.Base; + +import java.io.IOException; + +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import java.util.List; + +import java.util.concurrent.Executor; + +/** + * MemcachedServer handles the lower level SocketChannel interactions for the + * MemcachedAcceptor. + *

+ * The MemcachedServer utilizes the "FMW-commons" SelectionService to perform + * channel I/O. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class MemcachedServer + { + /** + * Set the socket provider. + * + * @param provider SocketProvider + */ + public void setSocketProvider(SocketProvider provider) + { + m_provider = provider; + } + + /** + * Set the cache name configured for the memcached acceptor. + * + * @param sCacheName the cache name + */ + public void setCacheName(String sCacheName) + { + m_sCacheName = sCacheName; + } + + /** + * Set if binary-pass-thru is configured. + * + * @param fBinaryPassThru BinaryPassThru flag + */ + public void setBinaryPassthru(boolean fBinaryPassThru) + { + m_fBinaryPassThru = fBinaryPassThru; + } + + /** + * Set the memcached acceptor server address. + * + * @param sAddr the Acceptor Address + */ + public void setLocalAddress(String sAddr) + { + m_sAddr = sAddr; + } + + /** + * Set the memcached acceptor port. + * + * @param nPort the Acceptor Port + */ + public void setLocalPort(int nPort) + { + m_nPort = nPort; + } + + /** + * Set the Executor. + * + * @param executor the TaskExecutor + */ + public void setExecutor(Executor executor) + { + m_executor = executor; + } + + /** + * Set the BufferManager. + * + * @param manager the BufferManager + */ + public void setBufferManager(BufferManager manager) + { + m_bufferManager = manager; + } + + /** + * Set the SASL authentication method. + * + * @param sAuthMethod the Auth method + */ + public void setAuthMethod(String sAuthMethod) + { + m_sAuthMethod = sAuthMethod; + } + + /** + * Set the Proxy Service that is embedding this memcached adapter. + * + * @param service the parent proxy service + */ + public void setParentService(Service service) + { + m_parentService = service; + } + + /** + * Set the configured Identity Asserter. + * + * @param asserter IdentityAsserter + */ + public void setIdentityAsserter(IdentityAsserter asserter) + { + m_identityAsserter = asserter; + } + + /** + * Start the server. + * + * @throws IOException + */ + public void start() + throws IOException + { + ServerSocketChannel srvrChannel = m_srvrChannel = m_provider.openServerSocketChannel(); + srvrChannel.configureBlocking(false); + srvrChannel.socket().bind(m_provider.resolveAddress(m_sAddr + ":" + m_nPort)); + + SelectionService selectionService = m_selectionService = SelectionServices.getDefaultService(); + selectionService.register(srvrChannel, new AcceptHandler( + srvrChannel, new ConnectionFactory(m_bufferManager, true))); + } + + /** + * Stop the server. + * + * @throws IOException + */ + public void stop() + throws IOException + { + if (m_srvrChannel != null) + { + m_srvrChannel.close(); + } + } + + // ----- inner class: AcceptHandler ------------------------------------- + + /** + * AcceptHandler accepts new memcached client connections. + */ + protected class AcceptHandler + extends SafeSelectionHandler + { + /** + * Construct an AcceptHandler for accepting new client socket connections. + * + * @param srvrChannel the ServerSocketChannel + * @param connFactory ConnectionFactory for creating connection objects on top of the connected sockets. + */ + protected AcceptHandler(ServerSocketChannel srvrChannel, ConnectionFactory connFactory) + { + super(srvrChannel); + m_connFactory = connFactory; + } + + /** + * {@inheritDoc} + */ + @Override + protected int onReadySafe(int nOps) + throws IOException + { + SocketChannel chan = null; + try + { + chan = getChannel().accept(); + if (chan != null) + { + chan.configureBlocking(false); + Connection conn = m_connFactory.createConnection(chan); + MessageHandler msgHandler = new MessageHandler(conn, m_executor); + // create a new RequestHandler per client connection. + RequestHandler reqHandler = new DefaultRequestHandler(m_sCacheName, m_parentService, m_sAuthMethod, + m_fBinaryPassThru, m_identityAsserter, m_executor, msgHandler); + msgHandler.setRequestHandler(reqHandler); + conn.setFlowControl(msgHandler); + m_selectionService.register(chan, msgHandler); + } + } + catch (final IOException e) + { + if (chan == null) + { + // ServerSocketChannel gone bad. + // We need to restart the service by running this task + // in the proxy daemon pool. + m_executor.execute(new Runnable() + { + @Override + public void run() + { + // this will restart the service. + throw Base.ensureRuntimeException(e); + } + }); + + // de-register the server socket channel from the SelectionService + throw new RuntimeException(e); + } + else + // error with new channel; just close it + { + try + { + chan.close(); + } + catch (IOException e1) {} + } + } + return OP_ACCEPT; + } + + /** + * The ConnectionFactory. + */ + protected ConnectionFactory m_connFactory; + } + + // ----- inner class: MessageHandler ------------------------------------ + + /** + * MessageHandler handles messages on the connected socket channel. + */ + protected class MessageHandler + extends SafeSelectionHandler + implements Connection.ConnectionFlowControl + { + /** + * Construct a message handler. + * + * @param conn Memcached Connection + * @param handler Request handler + * @param executor Task executor + */ + protected MessageHandler(Connection conn, Executor executor) + { + super(conn.getChannel()); + m_conn = conn; + m_executor = executor; + } + + /** + * Set the RequestHandler. + * + * @param handler RequestHandler + */ + public void setRequestHandler(RequestHandler handler) + { + m_handler = handler; + } + + /** + * {@inheritDoc} + */ + @Override + protected int onReadySafe(int nOps) + throws IOException + { + int nFlag = m_nOpRead; + if ((nOps & nFlag) != 0) + { + nFlag = handleRead(); + } + if ((nOps & OP_WRITE) != 0) + { + nFlag |= handleWrite(); + } + return nFlag; + } + + /** + * {@inheritDoc} + */ + @Override + public void resumeWrites() + { + SocketChannel channel = getChannel(); + try + { + m_selectionService.register(channel, this); + } + catch (IOException ioe) + { + CacheFactory.log("Failed to resume writes. Closing channel.", CacheFactory.LOG_ERR); + closeChannel(channel); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void pauseReads() + { + m_nOpRead = 0; + } + + /** + * {@inheritDoc} + */ + @Override + public void resumeReads() + { + SocketChannel channel = getChannel(); + try + { + m_nOpRead = OP_READ; + m_selectionService.register(channel, this); + } + catch (IOException ioe) + { + CacheFactory.log("Failed to resume read. Closing channel.", CacheFactory.LOG_ERR); + closeChannel(channel); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isReadPaused() + { + return m_nOpRead == 0; + } + + /** + * Handle socket channel reads. + * + * @return SelectionService's operation-set bit for read operations. + * + * @throws IOException + */ + protected int handleRead() + throws IOException + { + List requestList = m_conn.read(); + for (Request request : requestList) + { + onRequest(request); + } + RequestHandler handler = m_handler; + handler.flush(); + if (handler.checkBacklog(null)) + { + BacklogEndedContinuation backlogContinuation = new BacklogEndedContinuation(); + if (handler.checkBacklog(backlogContinuation)) + { + backlogContinuation.pause(); + } + } + return m_nOpRead; + } + + /** + * Handle socket channel writes. + * + * @return SelectionService's operation-set bit for write operations if there + * are pending data to be written or 0 to de-register for write operations. + * + * @throws IOException + */ + protected int handleWrite() + throws IOException + { + return m_conn.write(); + } + + /** + * Submit the request to the task executor. + * + * @param request Request to process + */ + protected void onRequest(Request request) + { + new Task(request, m_handler).run(); + } + + /** + * Close socket channel. + * + * @param channel socket channel to close + */ + protected void closeChannel(SocketChannel channel) + { + try + { + channel.close(); + } + catch (IOException ex) { } /*ignore*/ + } + + /** + * Continuation that is called when the underlying cache service backlog ends. + */ + protected class BacklogEndedContinuation + implements Continuation + { + + /** + * {@inheritDoc} + */ + @Override + public void proceed(Void r) + { + m_fResumed = true; + resumeReads(); + } + + /** + * Pause reading from the memcached connection. + */ + public void pause() + { + pauseReads(); + if (m_fResumed) + { + resumeReads(); + } + } + + // ----- data members -------------------------------------------- + + /** + * Flag to indicate if continuation has been resumed. + */ + protected volatile boolean m_fResumed = false; + } + + // ----- data members ----------------------------------------------- + + /** + * Memcached connection + */ + protected Connection m_conn; + + /** + * Request Handler + */ + protected RequestHandler m_handler; + + /** + * Task Executor + */ + protected Executor m_executor; + + /** + * SelectionService's read operation flag + */ + protected volatile int m_nOpRead = OP_READ; + } + + // ----- data members --------------------------------------------------- + + /** + * Selection Service for handling socket channels + */ + protected SelectionService m_selectionService; + + /** + * Server socket channel + */ + protected ServerSocketChannel m_srvrChannel; + + /** + * Parent Proxy Service + */ + protected Service m_parentService; + + /** + * Named cache name + */ + protected String m_sCacheName; + + /** + * Flag to indicate if binary-pass-thru is configured. + */ + protected boolean m_fBinaryPassThru; + + /** + * Configured SASL authentication method. + */ + protected String m_sAuthMethod; + + /** + * Server Ip-address + */ + protected String m_sAddr; + + /** + * Server port + */ + protected int m_nPort; + + /** + * Socket Provider + */ + protected SocketProvider m_provider; + + /** + * TaskExecutor + */ + protected Executor m_executor; + + /** + * Identity Asserter + */ + protected IdentityAsserter m_identityAsserter; + + /** + * Buffer Manager + */ + protected BufferManager m_bufferManager = BufferManagers.getHeapManager(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/ResponseQueue.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/ResponseQueue.java new file mode 100644 index 0000000000000..0671a6bb83aa3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/ResponseQueue.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.server; + +import com.tangosol.coherence.memcached.server.BinaryConnection.BinaryResponse; + +/** + * ResponseQueue is a specialized single producer multi consumer queue used by + * the memcached adapter. All the responses that need to be sent to the client + * are added to this queue and are sent in the same order. The requests could finish + * in a different order since they are executed as async EPs. But this queue's FIFO order + * ensures that the responses are sent back in the order of the received requests + * irrespective of the request completion. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class ResponseQueue + { + /** + * Add a pending BinaryResponse to the queue. It will be sent to the client + * in the same order when its associated request is completed. The add always + * happens single threaded on the SelectionService thread serving the client + * connection. + * + * @param response BinaryResponse. + */ + public void add(BinaryResponse response) + { + BinaryResponse oldTail = m_tail; + if (oldTail != null) + { + oldTail.m_next = response; + } + m_tail = response; + BinaryResponse head = m_head; + if (head == null) + { + m_head = response; + if (oldTail != null) + { + oldTail.m_next = null; + } + } + else if (head == oldTail) + { + synchronized (this) // synch to avoid concurrent update of head by consumers + { + if (m_head == null) // consumers didn't see the newly added response + { + m_head = response; + oldTail.m_next = null; + } + } + } + } + + /** + * Check if the given response can be sent back to the client. + * + * @param response Response that needs to be checked for flushing to client. + * @param fOwner Did the current thread complete the associated request for the given response + * + * @return true if the response can be flushed to the client. + */ + public boolean isFlushable(BinaryResponse response, boolean fOwner) + { + if (response == null) + { + return false; + } + else if (fOwner) + { + // send response back to client if it is at the head of the queue + return m_head == response; + } + else + { + synchronized (response) + { + // send response back to client if it is at the head of the queue and is marked deferred. + if (m_head == response && response.m_fDeferred) + { + response.m_fDeferred = false; + return true; + } + return false; + } + } + } + + /** + * Mark the given response as deferred since it cannot be sent to the client by the owning thread. + * + * @param response Response to be marked as deferred + */ + public void markDeferred(BinaryResponse response) + { + synchronized (response) + { + response.m_fDeferred = true; + } + } + + /** + * Remove the response from the head and return the next response. + * + * @param response Response to be removed if it is the head. + * + * @return next response in the queue + */ + public BinaryResponse removeAndGetNext(BinaryResponse response) + { + BinaryResponse nextResponse = response.m_next; + if (nextResponse == null) // last response in the q. + { + synchronized (this) // synch to avoid concurrent update by producer + { + nextResponse = response.m_next; // ensure head points to right response that may have been added concurrently. + response.m_next = null; + m_head = nextResponse; + } + } + else + { + response.m_next = null; + m_head = nextResponse; + } + return nextResponse; + } + + /** + * Size of the queue - pending responses. + * + * @return size of the queue + */ + public int size() + { + BinaryResponse head = m_head; + BinaryResponse tail = m_tail; + if (head != null && tail != null) + { + return (int) (m_tail.m_request.m_lId - head.m_request.m_lId + 1); + } + return 0; + } + + // ----- data members --------------------------------------------------- + + /** + * Head of the queue. + */ + protected volatile BinaryResponse m_head; + + /** + * Tail of the queue. + */ + protected volatile BinaryResponse m_tail; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/Task.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/Task.java new file mode 100644 index 0000000000000..12793732ef97c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/memcached/server/Task.java @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.memcached.server; + +import com.tangosol.coherence.memcached.Request; +import com.tangosol.coherence.memcached.RequestHandler; +import com.tangosol.coherence.memcached.Response; +import com.tangosol.coherence.memcached.Response.ResponseCode; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.net.cache.KeyAssociation; + +import com.tangosol.util.Base; + +import java.io.IOException; + +import java.security.PrivilegedAction; + +import javax.security.auth.Subject; + +/** + * Task executes Memcached requests using a proxy worker thread. KeyAssociation + * maintains execution order of the requests on a per connection basis. + *

+ * This class implements the + * binary protocol parsing for binary memcached messages. + * + * @author bb 2013.05.01 + * + * @since Coherence 12.1.3 + */ +public class Task + implements Runnable, KeyAssociation + { + + // ----- constructors --------------------------------------------------- + + /** + * Constructor. + * + * @param request Request + * @param handler RequestHandler + */ + public Task(Request request, RequestHandler handler) + { + f_request = request; + f_handler = handler; + } + + // ----- KeyAssociation methods ----------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Object getAssociatedKey() + { + return f_request.getAssociatedKey(); + } + + // ----- Runnable methods ----------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void run() + { + final Request request = f_request; + final Response response = request.getResponse(); + Subject subject = f_handler.getSubject(request); + if (subject == null) + { + handleRequest(request, response); + } + else + { + Subject.doAs(subject, new PrivilegedAction() + { + public Void run() + { + handleRequest(request, response); + return null; + } + }); + } + } + + // ----- internal methods ----------------------------------------------- + + /** + * Execute the request. + * + * @param request the Request to process + * @param response the Response for the request + */ + protected void handleRequest(Request request, Response response) + { + boolean fFlush = false; + try + { + switch (request.getOpCode()) + { + case 0x00: // GET Request + { + f_handler.onGet(request, response); + break; + } + case 0x01: // SET Request + { + f_handler.onSet(request, response); + break; + } + case 0x02: // ADD Request + { + f_handler.onAdd(request, response); + break; + } + case 0x03: // REPLACE Request + { + f_handler.onReplace(request, response); + break; + } + case 0x04: // DELETE Request + { + f_handler.onDelete(request, response); + break; + } + case 0x05: // INCREMENT Request + { + f_handler.onIncrement(request, response); + break; + } + case 0x06: // DECREMENT Request + { + f_handler.onDecrement(request, response); + break; + } + case 0x07: // Quit Request + { + fFlush = true; + response.setResponseCode(ResponseCode.OK.getCode()); + break; + } + case 0x08: // FLUSH Request + { + f_handler.onFlush(request, response); + break; + } + case 0x09: // GETQ Request + { + f_handler.onGet(request, response); + break; + } + case 0x0a: // NO-OP Request + { + fFlush = true; + response.setResponseCode(ResponseCode.OK.getCode()); + break; + } + case 0x0b: // VERSION Request + { + fFlush = true; + version(request, response); + break; + } + case 0x0c: // GETK Request + { + response.setKey(request.getKey()); + f_handler.onGet(request, response); + break; + } + case 0x0d: // GETKQ Request + { + response.setKey(request.getKey()); + f_handler.onGet(request, response); + break; + } + case 0x0e: // APPEND Request + { + f_handler.onAppend(request, response); + break; + } + case 0x0f: // PREPEND Request + { + f_handler.onPrepend(request, response); + break; + } + case 0x10: // STAT Request + { + fFlush = true; + stat(request, response); + break; + } + case 0x11: // SETQ Request + { + f_handler.onSet(request, response); + break; + } + case 0x12: // ADDQ Request + { + f_handler.onAdd(request, response); + break; + } + case 0x13: // REPLACEQ Request + { + f_handler.onReplace(request, response); + break; + } + case 0x14: // DELETEQ Request + { + f_handler.onDelete(request, response); + break; + } + case 0x15: // INCREMENTQ Request + { + f_handler.onIncrement(request, response); + break; + } + case 0x16: // DECREMENTQ Request + { + f_handler.onDecrement(request, response); + break; + } + case 0x17: // QuitQ Request + { + fFlush = true; + response.setResponseCode(ResponseCode.OK.getCode()); + break; + } + case 0x18: // FLUSHQ Request + { + f_handler.onFlush(request, response); + break; + } + case 0x19: // APPENDQ Request + { + f_handler.onAppend(request, response); + break; + } + case 0x1a: // PREPENDQ Request + { + f_handler.onPrepend(request, response); + break; + } + case 0x1c: // TOUCH Request + { + f_handler.onTouch(request, response); + break; + } + case 0x1d: // GAT Request + { + f_handler.onGAT(request, response); + break; + } + case 0x1e: // GATQ Request + { + f_handler.onGAT(request, response); + break; + } + case 0x20: // SASL LIST Mechanisms Request + { + fFlush = true; + f_handler.onSASLList(request, response); + break; + } + case 0x21: // SASL Auth Request + { + fFlush = true; + f_handler.onSASLAuth(request, response); + break; + } + case 0x22: // SASL Auth STEP Request + { + fFlush = true; + f_handler.onSASLAuthStep(request, response); + break; + } + default: + { + CacheFactory.log("Memcached adapter received unknown request: " + + request.getOpCode(), CacheFactory.LOG_ERR); + response.setResponseCode(Response.ResponseCode.NOT_SUPPORTED.getCode()); + } + } + } + catch (Throwable thr) + { + fFlush = true; + CacheFactory.log("Exception in handling memcached request: " + + Base.printStackTrace(thr), CacheFactory.LOG_ERR); + response.setResponseCode(Response.ResponseCode.INTERNAL_ERROR.getCode()); + } + finally + { + if (fFlush) + { + flush(response, /*fDisponseOnly*/ false); + } + } + } + + /** + * Handle stat request. + * + * @param request the request + * + * @throws IOException + */ + protected void stat(Request request, Response response) + throws IOException + { + String sPid = CacheFactory.getCluster().getLocalMember().getProcessName(); + response.setKey("pid").setValue(sPid.getBytes("utf-8")); + } + + /** + * Handle version request. + * + * @param request the request + * @param response the response + * + * @throws IOException + */ + protected void version(Request request, Response response) + throws IOException + { + response.setValue(CacheFactory.VERSION.getBytes("utf-8")); + } + + /** + * Flush the response. It may or may not be sent to the client immediately + * depending upon if there are pending responses ahead of this response. + * + * @param response the response to send to the client + * @param fDisposeOnly flag to indicate that the response should not be sent back + * to client and only removed from the pending response queue. + */ + public static void flush(Response response, boolean fDisposeOnly) + { + if (response instanceof BinaryConnection.BinaryResponse) + { + ((BinaryConnection.BinaryResponse) response).flush(fDisposeOnly); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The Memcached request. + */ + protected final Request f_request; + + /** + * The RequestHandler. + */ + protected final RequestHandler f_handler; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/BatchView.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/BatchView.java new file mode 100755 index 0000000000000..afe96b4d894c4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/BatchView.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + + +/** +* Macro column to include a Batch identifier into the filename. The batch identifier +* is incremented with each execution of the Report Group. The batch identifier +* is helpful when needing to associate data from related reports. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class BatchView + extends ColumnView + { + /** + * BatchView is never visible in the report. BatchLocator is used to include + * the batch number in the report + * + * @return false + */ + public boolean isVisible() + { + return false; + } + + /** + * Return a 10 digit batch number padded with zeros(0) + * + * @param oObject this is ignored + * @return a string representing the batch number. + */ + public String getOutputString(Object oObject) + { + long lBatch = ((JMXQueryHandler) m_handler).getBatch(); + String sRet = "0000000000" + String.valueOf(lBatch); + return sRet.substring(sRet.length() - 10); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/Collection.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/Collection.java new file mode 100644 index 0000000000000..c4d20b6b8151b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/Collection.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import com.tangosol.util.InvocableMap; +import com.tangosol.util.aggregator.AbstractAggregator; +import com.tangosol.util.ValueExtractor; + + +/** +* Aggregator to return a single value without aggregation. This is used when +* doing key group-by clauses. +* +* @author ew 2008.07.07 +* @since Coherence 3.4 +*/ +public class Collection + extends AbstractAggregator + { + /** + * Public constructor to assign the value extractor for the processing. + * + * @param extractor the ValueExtractor of the data to be processed + */ + public Collection(ValueExtractor extractor) + { + super(extractor); + } + + // ----- StreamingAggregator methods ------------------------------------ + + @Override + public InvocableMap.StreamingAggregator supply() + { + return new Collection(getValueExtractor()); + } + + @Override + public int characteristics() + { + return PARALLEL | PRESENT_ONLY; + } + + // ----- AbstractAggregator methods ------------------------------------- + + /** + * @inheritDoc + */ + protected void init(boolean fFinal) + { + // No Implementation + } + + /** + * @inheritDoc + */ + protected void process(Object o, boolean fFinal) + { + m_oValue = o; + } + + /** + * @inheritDoc + */ + protected Object finalizeResult(boolean fFinal) + { + return m_oValue; + } + + /** + * The last value passed. + */ + protected Object m_oValue; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ColumnView.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ColumnView.java new file mode 100755 index 0000000000000..11ff3b3f3f793 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ColumnView.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import com.tangosol.run.xml.XmlElement; + +import java.text.DateFormat; + +import java.util.Date; + + +/** +* Provides column definition for each data element. Each ColumnView must be +* "backed" by a corresponding ColumnLocator. The ColumnLocator and the ColumnView +* are "linked" by the QueryHandler through the column identifier getId(). +* +* @author ew 2008.01.28 +* +* @since Coherence 3.4 +*/ +public class ColumnView + implements ReportColumnView, Constants + { + /** + * Sets the configuration of the report column. + * + * @param xmlConfig the column xml from the report configuration see + * coherence-report-config.xsd + */ + public void configure(XmlElement xmlConfig) + { + String sId = xmlConfig.getSafeAttribute("id").getString(); + String sName = xmlConfig.getSafeElement(Reporter.TAG_COLUMNNAME).getString(); + if (sId == null || sId.length() == 0) + { + sId = sName; + } + + String sHeader = xmlConfig.getSafeElement(Reporter.TAG_COLUMNHEAD).getString(null); + sHeader = sHeader == null ? sName == null ? "" : sName : sHeader; + + m_sId = sId; + m_xmlConfig = xmlConfig; + m_sType = xmlConfig.getSafeElement(Reporter.TAG_DATATYPE).getString("java.lang.String"); + m_sDesc = xmlConfig.getSafeElement(Reporter.TAG_DESC).getString(sHeader); + + setHeader(sHeader); + setVisible(!xmlConfig.getSafeElement(Reporter.TAG_HIDDEN).getBoolean(false)); + } + + /** + * Configure the ColumnView. + * + * @param xmlConfig the XML configuration + * @param handler the QueryHandler + */ + public void configure(XmlElement xmlConfig, QueryHandler handler) + { + setQueryHandler(handler); + configure(xmlConfig); + } + + /** + * Update the QueryHandler for the ColumnView. + * + * @param handler the QueryHandler + */ + public void setQueryHandler(QueryHandler handler) + { + m_handler = handler; + } + + /** + * Determine if the ColumnView uses and Aggregate or a Extractor + * + * @return true if the ColumnView uses an Aggregate. + */ + public boolean isAggregate() + { + return m_handler.isAggregate(m_sId); + } + + /** + * Set the visibility of the column + * + * @param fVisible true, the column is displayed, false the column is omitted. + */ + protected void setVisible(boolean fVisible) + { + m_fVisible = fVisible; + } + + /** + * Set the column output header + * + * @param sHeader value to be displayed on the first row of the file + */ + protected void setHeader(String sHeader) + { + m_sHeader = sHeader; + } + + /** + * Get the column view configuration XML. + */ + public XmlElement getConfig() + { + return m_xmlConfig; + } + + /** + * Get the type of this column as specified by the configuration XML, + * or "java.lang.String" if none is specified. + * + * @return the canonical name of this column view value's class + */ + public String getType() + { + return m_sType; + } + + /** + * Get the description of this column as specified by the configuration XML. + * + * @return this column's description + */ + public String getDescription() + { + return m_sDesc; + } + + /** + * {@inheritDoc} + */ + public String getOutputString(Object oObject) + { + Object oValue = m_handler.getValue(oObject, m_sId); + if (oValue == null) + { + return ""; + } + + return oValue instanceof Date && m_dateFormat != null + ? m_dateFormat.format((Date) oValue).toString() + : oValue.toString(); + } + + /** + * {@inheritDoc} + */ + public boolean isRowDetail() + { + return m_handler.isDetail(m_sId); + } + + /** + * {@inheritDoc} + */ + public String getHeader() + { + return m_sHeader; + } + + /** + * {@inheritDoc} + */ + protected void setDateFormat(DateFormat df) + { + m_dateFormat = df; + } + + /** + * {@inheritDoc} + */ + public boolean isVisible() + { + return m_fVisible; + } + + /** + * {@inheritDoc} + */ + public String getId() + { + return m_sId; + } + + // ----- data members ---------------------------------------------------- + + /** + * The column view identifier. There must also be a corresponding + * column locator with the same identifier. + */ + protected String m_sId; + + /** + * The column value type. + */ + protected String m_sType; + + /** + * The column description. + */ + protected String m_sDesc; + + /** + * The column header. + */ + protected String m_sHeader; + + /** + * The DateFormat for displaying date. + */ + protected DateFormat m_dateFormat; + + /** + * The flag determining column visiblity in report. + */ + protected boolean m_fVisible; + + /** + * The configuration XML + */ + protected XmlElement m_xmlConfig; + + /** + * The query handler associated with the column view + */ + protected QueryHandler m_handler; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/Constants.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/Constants.java new file mode 100755 index 0000000000000..20c79f1087095 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/Constants.java @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +/** +* Constants used to parse the Report Configuraiton and the Report Batch XML files. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public interface Constants + { + /** + * The Default value for the report if an non-critical error occurs. + */ + static final Object DEFAULT_VALUE = null; + + /** + * The domain partition key attribute. + */ + public static final String DOMAIN_PARTITION = "domainPartition"; + + /** + * The calculation is not valid. + */ + public static final int CALC_ERR = -1; + + /** + * The values in the attribute will be summed. + */ + public static final int CALC_SUM = 0; + + /** + * The values in the attribute will averaged. + */ + public static final int CALC_AVG = 1; + + /** + * The maximum value for the attibutes will be returned. + */ + public static final int CALC_MAX = 2; + + /** + * The minimum value for the attibutes will be returned. + */ + public static final int CALC_MIN = 3; + + /** + * The column type is invalid. + */ + public static final int COL_ERR = -1; + + /** + * The column type is a JMX Attribute. + */ + public static final int COL_ATTRIB = 0; + + /** + * The column type is a calculation of a JMX Attribute. + */ + public static final int COL_CALC = 1; + + /** + * The column type is a JMX method. + */ + public static final int COL_METHOD = 2; + + /** + * The column type is a JMX key part. + */ + public static final int COL_KEY = 3; + + /** + * The column type is a Global report value. + */ + public static final int COL_GLOBAL = 4; + + /** + * The report type is invalid. + */ + public static final int REPORT_ERR = -1; + + /** + * The XML tag Header Flag + */ + public static final String TAG_HEADERS = "hide-headers"; + + /** + * The XML tag for the output file name. + */ + public static final String TAG_FILENAME = "file-name"; + + /** + * The XML tag for the pattern and filter elements. + */ + public static final String TAG_QUERY = "query"; + + /** + * The XML tag for the report description. + */ + public static final String TAG_DESC= "description"; + + /** + * The XML tag for the JMX Query. + */ + public static final String TAG_PATTERN = "pattern"; + + /** + * The XML tag for the row information. + */ + public static final String TAG_ROW = "row"; + + /** + * The XML tag for the Attribute Name. + */ + public static final String TAG_COLUMNNAME = "name"; + + /** + * The XML value for the correlated column type. + */ + public static final String VALUE_CORRELATED = "correlated"; + + /** + * The XML tag for the Attribute Name. + */ + public static final String TAG_PARAMS = "params"; + + /** + * The XML tag for the column header. + */ + public static final String TAG_COLUMNHEAD = "header"; + + /** + * The XML tag for the column header. + */ + public static final String TAG_HIDDEN = "hidden"; + + /** + * The XML tag for the query filter + */ + public static final String TAG_FILTER = "filter"; + + /** + * The XML tag for the query filter + */ + public static final String TAG_FILTERS = "filters"; + + /** + * The XML tag for the query filter + */ + public static final String TAG_FILTERTYPE = "type"; + + /** + * The XML tag for the query filter + */ + public static final String TAG_FILTERCLS = "filter-class"; + + /** + * The XML tag for the query filter + */ + public static final String TAG_REPORT = "report"; + + /** + * The XML tag for the query filter + */ + public static final String TAG_FILTERREF= "filter-ref"; + + /** + * The XML tag for the column type. + */ + public static final String TAG_COLUMNTYPE = "type"; + + /** + * The XML tag for the column value's type. + */ + public static final String TAG_DATATYPE = "data-type"; + + /** + * The XML tag for the calculation type. + */ + public static final String TAG_COLUMNFUNC = "function-name"; + + /** + * The XML tag for the column/value delimiter. + */ + public static final String TAG_DELIM = "delim"; + + /** + * The XML tag for a custom column class + */ + public static final String TAG_CLASS = "column-class"; + + /** + * The XML tag for the column definition. + */ + public static final String TAG_COLUMN = "column"; + + /** + * The XML tag for the column definition. + */ + public static final String TAG_COLUMNREF = "column-ref"; + + /** + * The XML value for a JMX Attribute column + */ + public static final String TAG_SUBQUERY = "subquery"; + + /** + * The XML value for a sum calculation. + */ + public static final String VALUE_SUM = "sum"; + + /** + * The XML value for a Count calculation. + */ + public static final String VALUE_COUNT = "count"; + + /** + * The XML value for a average calculation. + */ + public static final String VALUE_AVG = "avg"; + + /** + * The XML value for a minimum calculation. + */ + public static final String VALUE_MIN = "min"; + + /** + * The XML value for a maximum calculation. + */ + public static final String VALUE_MAX = "max"; + + /** + * The XML value for a maximum calculation. + */ + public static final String VALUE_DELTA = "delta"; + + /** + * The XML value for a divide calculation. + */ + public static final String VALUE_DIVIDE = "divide"; + + /** + * The XML value for a divide calculation. + */ + public static final String VALUE_ADD = "add"; + + /** + * The XML value for a divide calculation. + */ + public static final String VALUE_SUB = "subtract"; + + /** + * The XML value for a divide calculation. + */ + public static final String VALUE_MULTI = "multiply"; + + /** + * The XML value for a JMX Attribute column + */ + public static final String VALUE_ATTRIB = "attribute"; + + /** + * The XML value for a JMX Attribute column + */ + public static final String VALUE_SUBQUERY = "subquery"; + + /** + * The XML value for a JMX method column. + */ + public static final String VALUE_METHOD = "method"; + + /** + * The XML value for a custom column. + */ + public static final String VALUE_CUSTOM = "custom"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_CONSTANT = "constant"; + + /** + * The XML value for a system property value + */ + public static final String VALUE_PROPERTY = "property"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_GLOBAL = "global"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_FUNC = "function"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_DISTINCT = "distinct"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_OR = "or"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_AND = "and"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_EQUALS = "equals"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_GREATER = "greater"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_LESS = "less"; + + /** + * The XML value for a reporter global value column. + */ + public static final String VALUE_NOT = "not"; + + /** + * The XML value for a JMX key part column. + */ + public static final String VALUE_KEY = "key"; + + /** + * The Reporter calculation column. + */ + public static final String VALUE_COLCALC = "function"; + + /** + * The XML value for a the global number of times the report has been executed. + */ + public static final String VALUE_BATCH = "{batch-counter}"; + + /** + * The XML value for a the global time a report started. + */ + public static final String VALUE_TIME = "{report-time}"; + + /** + * The default column type. + */ + public static final String DEFAULT_COLTYPE = "attribute"; + + /** + * The default column calculation type. + */ + public static final String DEFAULT_CALC = "sum"; + + /** + * The file "macro" start character. + */ + public static final String MACRO_START = "\\{"; + + /** + * The file "macro" stop character. + */ + public static final String MACRO_STOP = "\\}"; + + /** + * The XML value for a tab delimited locator. + */ + public static final String VALUE_TAB = "{tab}"; + + /** + * The XML value for a comma delimited locator. + */ + public static final String VALUE_SPACE = "{space}"; + + /** + * The file name "macro" to insert the execution batch number into the filename. + */ + public static final String MACRO_BATCH = "batch"; + + /** + * The file name "macro" to insert the execution node Id into the filename. + */ + public static final String MACRO_NODE = "node"; + + /** + * The file name "macro" to insert the execution start date into the filename. + */ + public static final String MACRO_DATE = "date"; + + /** + * The XML value to indicate if this is a Non-Multitenant Environment. + */ + public static final String MACRO_NONMT = "non-MT"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/DataSource.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/DataSource.java new file mode 100755 index 0000000000000..3360a41426f17 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/DataSource.java @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import com.tangosol.net.management.MBeanHelper; + +import com.tangosol.util.InvocableMap; +import com.tangosol.util.ImmutableArrayList; +import com.tangosol.util.ValueExtractor; +import com.tangosol.util.SafeHashMap; + +import com.tangosol.util.aggregator.CompositeAggregator; +import com.tangosol.util.aggregator.GroupAggregator; + +import com.tangosol.util.extractor.MultiExtractor; +import com.tangosol.util.extractor.IdentityExtractor; + +import com.tangosol.util.processor.ExtractorProcessor; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + + +/** +* Class to abstract the contract of the Locators to the MBean. +* +* @author ew 2008.03.19 +* @since Coherence 3.4 +*/ +public class DataSource + { + /** + * Construct a new DataSource + */ + public DataSource() + { + m_listVE = new LinkedList(); + m_listAgg = new LinkedList(); + m_listGroup = new LinkedList(); + m_listScalar = new LinkedList(); + } + + /** + * Add a extractor to the DataSource. + * + * @param extractor the ValueExtractor to execute + * + * @return the position in the result set of the extractor. This value + * is used to access the result via getValue + */ + public int addExtractor(ValueExtractor extractor) + { + int cSize = m_listVE.size(); + m_listVE.add(new Collection(extractor)); + return cSize; + } + + /** + * Add a scalar transformation to the DataSource. + * + * @param extractor the ValueExtractor to execute + * + * @return the position in the result set of the extractor. This value + * is used to access the result via {@link #getValue} + */ + public int addScalar(ValueExtractor extractor) + { + List listScalar = m_listScalar; + int cSize = listScalar.size(); + + listScalar.add(extractor); + + return cSize; + } + + /** + * Add an aggregator to the DataSource. + * + * @param aggregator the aggregator to execute + * + * @return the position in the result set of the aggregator. This value + * is used to access the result via {@link #getAggValue} + */ + public int addAggregator(InvocableMap.EntryAggregator aggregator) + { + List list = m_fGroupBy ? m_listVE : m_listAgg; + int cSize = list.size(); + + list.add(aggregator); + + return cSize; + } + + /** + * Add an extractor to the group by. + * + * @param extractor the extractor to group by + * + * @return the position in the result set of the aggregator. This value + * is used to access the result via {@link #getAggValue} + */ + public int addGroupBy(ValueExtractor extractor) + { + List listGroup = m_listGroup; + int cGroups = listGroup.size(); + + listGroup.add(extractor); + + return cGroups; + } + + /** + * Execute the aggregators and the extractors + * + * @param mBeanQuery the mBeanQuery to execute against + * @param setMBeans the set of keys to execute against, a subset of keys in the map + */ + public void execute(MBeanQuery mBeanQuery, Set setMBeans) + { + if (m_listGroup.size() == 0) + { + m_listGroup.add(new IdentityExtractor()); + } + + m_ialAgg = getAggregates(mBeanQuery, setMBeans, m_listAgg); + m_mapGroup = getDetail(mBeanQuery, setMBeans, m_listGroup); + m_mapExtractionResults = getAggregates(mBeanQuery, setMBeans, m_listVE, m_listGroup); + m_mapScalar = getScalar(m_mapExtractionResults, m_listScalar); + } + + /** + * Apply Scalar functions to the aggregate result set + * + * @param mapResults the Result aggregate Map + * @param listVE the list of Scalar ValueExtractors + * + * @return a Map containing the results + */ + protected Map getScalar(Map mapResults, List listVE) + { + int cVE = listVE.size(); + int cResults = mapResults.size(); + + if (cVE > 0 && cResults > 0) + { + ValueExtractor[] aVE = (ValueExtractor[]) + listVE.toArray(new ValueExtractor[cVE]); + + MultiExtractor extractor = new MultiExtractor(aVE); + Map mapScalars = new SafeHashMap(); + + for (Iterator iter = mapResults.keySet().iterator(); iter.hasNext(); ) + { + Object oKey = iter.next(); + mapScalars.put(oKey, extractor.extract(oKey)); + } + + return mapScalars; + } + + return null; + } + + /** + * Execute the extractors designated "group-by" on the given mBeans. + * + * @param mBeanQuery the MBeanQuery to execute against + * @param setMBeans the keys to be extracted + * @param listGBE the list of ValueExtractors designated "group-by" to be executed + * + * @return a Map of mBean to result of extraction on that mBean + */ + protected Map getDetail(MBeanQuery mBeanQuery, Set setMBeans, List listGBE) + { + int cSize = listGBE.size(); + if (cSize > 0) + { + ValueExtractor[] aVE = (ValueExtractor[]) + listGBE.toArray(new ValueExtractor[cSize]); + + return mBeanQuery.invokeAll(setMBeans, new ExtractorProcessor(new MultiExtractor(aVE))); + } + + return null; + } + + /** + * Execute the aggregate extractors against the set of beans. + * + * @param mBeanQuery the MBeanQuery to execute against + * @param setMBeans the keys to be aggregate + * @param listAgg the list of aggregators + * + * @return the result of the aggregation + */ + protected ImmutableArrayList getAggregates(MBeanQuery mBeanQuery, Set setMBeans, List listAgg) + { + int cSize = listAgg.size(); + if (cSize > 0) + { + InvocableMap.EntryAggregator[] aVE = (InvocableMap.EntryAggregator[]) + listAgg.toArray(new InvocableMap.EntryAggregator[cSize]); + + CompositeAggregator aggComp = CompositeAggregator.createInstance(aVE); + + return (ImmutableArrayList) mBeanQuery.aggregate(setMBeans, aggComp); + } + + return null; + } + + /** + * Execute the aggregates. + * + * @param mBeanQuery the MBeanQuery to execute against + * @param setMBeans the keys to be aggregate + * @param listAgg the list of aggregators + * @param listBy the list of value extractors + * + * @return the result of the aggregation + */ + protected Map getAggregates(MBeanQuery mBeanQuery, Set setMBeans, List listAgg, List listBy) + { + int cAgg = listAgg.size(); + int cBy = listBy.size(); + + if (cAgg > 0 && cBy > 0) + { + InvocableMap.EntryAggregator[] aEA = (InvocableMap.EntryAggregator[]) + listAgg.toArray(new InvocableMap.EntryAggregator[cAgg]); + + ValueExtractor[] aVE = (ValueExtractor[]) listBy.toArray(new ValueExtractor[cBy]); + CompositeAggregator CA = CompositeAggregator.createInstance(aEA); + MultiExtractor ME = new MultiExtractor(aVE); + + GroupAggregator ga = GroupAggregator.createInstance(ME, CA); + + return (Map) mBeanQuery.aggregate(setMBeans, ga); + } + + return Collections.emptyMap(); + } + + /** + * The get an aggregate value. + * + * @param oKey the key for the aggregate + * @param nPos the position of the aggregate (returned from addAggregate) + * + * @return the aggregated value + */ + public Object getAggValue(Object oKey, int nPos) + { + return m_fGroupBy ? getValue(oKey, nPos) : m_ialAgg.get(nPos); + } + + /** + * Convert an query key into a group by key. + * + * @param oKey an ObjectName key + * + * @return a group by key based on the select key + */ + protected ImmutableArrayList convertObjectName(ObjectName oKey) + { + int nSize = m_listGroup.size(); + Object[] ao = new Object[nSize]; + for (int i = 0; i < nSize; i++) + { + ao[i] = ((ImmutableArrayList) m_mapGroup.get(oKey)).get(i); + } + + return new ImmutableArrayList(ao); + } + + /** + * The get an extracted value. + * + * @param oKey the key to extract + * @param nPos the position of the Extractor (returned from addExtractor + * + * @return the extracted value + */ + public Object getValue(Object oKey, int nPos) + { + Object oValKey = oKey instanceof ObjectName + ? convertObjectName((ObjectName) oKey) + : oKey; + + return ((ImmutableArrayList) m_mapExtractionResults.get(oValKey)).get(nPos); + } + + /** + * The get an extracted value. + * + * @param oKey the key to extract + * @param nPos the position of the Extractor (returned from addExtractor) + * + * @return the extracted value + */ + public Object getScalarValue(Object oKey, int nPos) + { + Object oValKey = oKey instanceof ObjectName + ? convertObjectName((ObjectName) oKey) + : oKey; + + return ((ImmutableArrayList) m_mapScalar.get(oValKey)).get(nPos); + } + + /** + * The get an extracted value. + * + * @param oKey the key to extract + * @param nPos the position of the Extractor. (returned from addExtractor + * + * @return the extracted Aggregate value + */ + public Object getGroupValue(Object oKey, int nPos) + { + return getAggValue(oKey, nPos); + } + + /** + * Set the query to use a group by clause on the query. + * + * @param fGroupBy true to apply a group by + */ + public void setGroupBy(boolean fGroupBy) + { + m_fGroupBy = fGroupBy; + } + + /** + * Determine if the query uses a group by clause. + * + * @return true iff the query uses a group by + */ + public boolean isGroupBy() + { + return m_fGroupBy; + } + + /** + * Get the results keyset for the group by query. + * + * @return a set of ImmutableArrayList objects which are the result keys + */ + public Set getGroupKeys() + { + Map setResults = new TreeMap(new Comparator() + { + public int compare(Object o, Object o1) + { + ImmutableArrayList ial = (ImmutableArrayList) o; + ImmutableArrayList ial1 = (ImmutableArrayList) o1; + int cThis = ial.size(); + int cThat = ial1.size(); + + for (int i = 0, c = Math.min(cThis, cThat); i < c; i++) + { + Object oThis = ial.get(i); + Object oThat = ial1.get(i); + int nResult; + + if (oThis == null || oThat == null) + { + nResult = oThis == null ? (oThat == null ? 0 : -1) : 1; + } + else if (oThis instanceof ObjectName && oThat instanceof ObjectName) + { + String s = ((ObjectName) oThis).getKeyPropertyListString(); + String s1 = ((ObjectName) oThat).getKeyPropertyListString(); + nResult = s.compareTo(s1); + } + else + { + nResult = ((Comparable) oThis).compareTo(oThat); + } + + if (nResult != 0) + { + return nResult; + } + } + + return cThis - cThat; + } + }); + + for (Iterator iter = m_mapExtractionResults.keySet().iterator(); iter.hasNext();) + { + setResults.put(iter.next(), null); + } + + return setResults.keySet(); + } + + /** + * Reset all attributes for next execution. + */ + public void postProcess() + { + m_listVE.clear(); + m_listAgg.clear(); + m_listGroup.clear(); + m_listScalar.clear(); + if (m_mapExtractionResults != null) + { + m_mapExtractionResults.clear(); + } + if (m_mapGroup != null) + { + m_mapGroup.clear(); + } + if (m_mapIndex != null) + { + m_mapIndex.clear(); + } + if (m_mapScalar != null) + { + m_mapScalar.clear(); + } + } + + /** + * Return an appropriate {@link MBeanServer} to use. + * + * @return an appropriate MBeanServer to use + */ + public MBeanServer getMBeanServer() + { + MBeanServer mbs = m_mbs; + if (mbs == null) + { + mbs = m_mbs = MBeanHelper.findMBeanServer(); + } + return mbs; + } + + + // ----- data members --------------------------------------------------- + + /** + * The working list of detail ValueExtractors. + */ + protected List m_listVE; + + /** + * The working list of PostProcess Scalar Transformations. + */ + protected List m_listScalar; + + /** + * The working list of Aggregate ValueExtractors. + */ + protected List m_listAgg; + + /** + * The results of the detail extraction (content of rows and columns). + */ + protected Map m_mapExtractionResults; + + /** + * The map holding mBeans to the composite keys made by "group-by" extractors. + */ + protected Map m_mapGroup; + + /** + * The results from running the aggregate extractors against the keyset of beans. + */ + protected ImmutableArrayList m_ialAgg; + + /** + * The results from a scalar post process. + */ + protected Map m_mapScalar; + + /** + * The working list of ValueExtractors flagged as "group-by" that together are + * used as the composite key for each row. + */ + protected List m_listGroup; + + /** + * Flag that is true iff the query contains a "group-by" clause. + */ + protected boolean m_fGroupBy; + + /** + * Map keyed by the group-by keys (ImmutableArrayList) values query keys + * ObjectName. + */ + protected Map m_mapIndex; + + /** + * The {@link MBeanServer} this DataSource operates against. + */ + protected MBeanServer m_mbs; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/DateView.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/DateView.java new file mode 100755 index 0000000000000..027c92a41f331 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/DateView.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import java.text.SimpleDateFormat; + +import java.util.Date; + + +/** +* Class to include execution date as a macro in the Filename. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class DateView + extends ColumnView + { + /** + * Obtain the start date time for the report. + * + * @param oObject ignored + * @return the date the report is executed in YYYYMMDD format + */ + public String getOutputString(Object oObject) + { + long ldtStartTime = ((JMXQueryHandler) m_handler).getStartTime(); + return new SimpleDateFormat("yyyyMMddHH") + .format(new Date(ldtStartTime)); + } + + /** + * DateView is never visible in the report. It is only used as a "macro" in + * the output filename. + * + * @return false + */ + public boolean isVisible() + { + return false; + } + + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/FilterFactory.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/FilterFactory.java new file mode 100755 index 0000000000000..f1fc91349ab5c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/FilterFactory.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.ValueExtractor; +import com.tangosol.util.Base; +import com.tangosol.util.extractor.ComparisonValueExtractor; +import com.tangosol.util.filter.AndFilter; +import com.tangosol.util.filter.EqualsFilter; +import com.tangosol.util.filter.OrFilter; +import com.tangosol.util.filter.NotFilter; +import com.tangosol.util.filter.LessFilter; +import com.tangosol.util.filter.GreaterFilter; +import com.tangosol.util.Filter; + +import java.util.Iterator; +import java.util.List; +import java.util.Comparator; + + +/** +* Factory to create Filter instances from XML defintion. +* +* @author ew 2008.02.27 +* @since Coherence 3.4 +*/ +public class FilterFactory + implements Constants + { + /** + * Create a new FilterFactory for the specified query handler and XML + * definition. + * + * @param query The query context for the filter. + * + * @param xmlFilters The filter and subfilter XML definition. + */ + public FilterFactory(JMXQueryHandler query, XmlElement xmlFilters) + { + m_query = query; + m_xmlFilters = xmlFilters; + } + + /** + * Instantiates a Filter as defined by the XML + * + * @param xmlConfig The Filter configuation XML. + * + * @return a Filter instance + */ + public Filter getFilter(XmlElement xmlConfig) + { + String sType = xmlConfig.getSafeElement(TAG_FILTERTYPE).getString(); + + if (sType.equals(VALUE_AND) || sType.equals(VALUE_OR) + || sType.equals(VALUE_NOT)) + { + List listRef = xmlConfig.getSafeElement(TAG_PARAMS).getElementList(); + if (listRef.size() == 2) + { + Iterator i = listRef.iterator(); + XmlElement xmlRef = (XmlElement) i.next(); + String sId = xmlRef.getString(); + Filter filterRight = getFilter(sId); + + xmlRef = (XmlElement) i.next(); + sId = xmlRef.getString(); + + Filter filterLeft = getFilter(sId); + if (sType.equals(VALUE_AND)) + { + return new AndFilter(filterLeft, filterRight); + } + else + { + return new OrFilter(filterLeft, filterRight); + } + } + else + { + if (listRef.size() == 1) + { + Iterator i = listRef.iterator(); + XmlElement xmlRef = (XmlElement) i.next(); + String sId = xmlRef.getString(); + Filter filter = getFilter(sId); + return new NotFilter(filter); + } + else + { + Base.log("FilterFactory: Invalid Filter Definition:" + + xmlConfig.toString()); + return null; + } + } + } + else + { + List listRef = xmlConfig.getSafeElement(TAG_PARAMS).getElementList(); + if (listRef.size() == 2) + { + + Iterator iter = listRef.iterator(); + XmlElement xmlRef = (XmlElement) iter.next(); + String sId = xmlRef.getString(); + ValueExtractor ve1 = m_query.ensureExtractor(sId); + + xmlRef = (XmlElement) iter.next(); + sId = xmlRef.getString(); + ValueExtractor ve2 = m_query.ensureExtractor(sId); + + if (sType.equals(VALUE_GREATER)) + { + return new GreaterFilter(new ComparisonValueExtractor(ve1,ve2, + new NumericComparator()), + Integer.valueOf(0)); + } + else if (sType.equals(VALUE_LESS)) + { + return new LessFilter(new ComparisonValueExtractor(ve1,ve2, + new NumericComparator()), + Integer.valueOf(0)); + } + else if (sType.equals(VALUE_EQUALS)) + { + return new EqualsFilter(new ComparisonValueExtractor(ve1,ve2, + new NumericComparator()), + Integer.valueOf(0)); + } + Base.log("FilterFactory: Invalid Filter Definition:" + + xmlConfig.toString()); + return null; + } + else + { + Base.log("FilterFactory: Invalid Filter Definition:" + xmlConfig.toString()); + return null; + } + } + } + + /** + * Create a Filter as defined by the filter reference Identifier + * + * @param sId a string identifier for a filter. + * + * @return a Filter instance. + */ + public Filter getFilter(String sId) + { + return getFilter(getFilterXmlByRef(sId)); + } + + /** + * Determine the configuration XML for a filter based on the filter + * reference. + * + * @param sRef the reference identifier to locate + * + * @return the configuration XML for the filter reference + */ + public XmlElement getFilterXmlByRef(String sRef) + { + XmlElement xmlFilters = m_xmlFilters; + + if (xmlFilters != null) + { + List listXml = xmlFilters.getElementList(); + for (Iterator i = listXml.iterator(); i.hasNext();) + { + XmlElement xmlSub = (XmlElement) i.next(); + String sTemp = xmlSub.getSafeAttribute("id").getString(); + if (sTemp.equals(sRef) && xmlSub.getElementList() != null) + { + return xmlSub; + } + } + } + return null; + } + + /** + * Determine the report configuration xml for a given XmlElement. + * + * @param xml the xml configuration component to determine the report + * + * @return The report configuration XML + */ + protected static XmlElement getReportXml(XmlElement xml) + { + XmlElement xmlTemp = xml; + while (xmlTemp.getElement(Reporter.TAG_REPORT) == null) + { + xmlTemp = xmlTemp.getParent(); + if (xmlTemp == null) + { + return null; + } + } + return xmlTemp.getElement(Reporter.TAG_REPORT); + } + + /** + * A numeric comparator class providing type independent numeric comparison. + * + * @author ew 2008.02.27 + * @since Coherence 3.4 + */ + protected static class NumericComparator + implements Comparator + { + /** + * @inheritDoc + */ + public int compare(Object o1, Object o2) + { + if (o1 instanceof Number && o2 instanceof Number) + { + double d1 = ((Number)o1).doubleValue(); + double d2 = ((Number)o2).doubleValue(); + if (d1 == d2) + { + return 0; + } + if (d1 < d2) + { + return -1; + } + return 1; + } + if (o1 instanceof Comparable && o2 instanceof Comparable + && o1.getClass().getName().equals(o2.getClass().getName())) + { + return ((Comparable)o1).compareTo(o2); + } + throw new IllegalArgumentException("NumericComparator only " + + "accepts Numeric Objects"); + } + } + + + + // ----- data members --------------------------------------------------- + /** + * The XML definition for the Query and related sub filters. + */ + protected XmlElement m_xmlFilters; + + /** + * The query context for filter creation. The query creates ValueExtractors + * for comparison filters. + */ + protected JMXQueryHandler m_query; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/JMXQueryHandler.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/JMXQueryHandler.java new file mode 100755 index 0000000000000..17dc6e384c426 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/JMXQueryHandler.java @@ -0,0 +1,786 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import com.tangosol.coherence.reporter.locator.ColumnLocator; +import com.tangosol.coherence.reporter.locator.CorrelatedLocator; +import com.tangosol.coherence.reporter.locator.NodeLocator; + +import com.tangosol.run.xml.SimpleElement; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlValue; + +import com.tangosol.util.Base; +import com.tangosol.util.ClassHelper; +import com.tangosol.util.Filter; +import com.tangosol.util.ValueExtractor; + +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + + +/** +* Handles creation of the JMX data source and retrieval of the JMX information. +* +* @author ew 2008.02.29 +* @since Coherence 3.4 +*/ +public class JMXQueryHandler + implements QueryHandler, Constants + { + /** + * @inheritDoc + */ + public void setContext(XmlElement xmlQuery, XmlElement xmlReportCfg) + { + DataSource source = m_source; + if (source == null) + { + source = m_source = new DataSource(); + } + + if (m_xml == null) + { + m_xml = xmlReportCfg; + m_xmlColumns = xmlReportCfg.getElement(TAG_ROW); + m_listXmlCol = m_xmlColumns.getElementList(); + m_sQueryTemp = xmlQuery.getSafeElement(Reporter.TAG_PATTERN).getString(); + m_xmlFilter = xmlQuery.getSafeElement(Reporter.TAG_FILTERREF); + + boolean fMultiTenant = isMultiTenant(); + + // The list of columns not set as Sub-Query if possible. + List listQueryCol = m_listQueryCol = xmlQuery.getSafeElement(TAG_PARAMS).getElementList(); + boolean fQueryEmpty = false; + if (listQueryCol.size() == 0) + { + fQueryEmpty = true; + listQueryCol = m_listQueryCol = new ArrayList(); + } + + List listXmlCol = m_listXmlCol; + boolean fGroupBy = m_fGroupBy; + for (Iterator iterCol = listXmlCol.iterator(); iterCol.hasNext();) + { + XmlElement xmlColumn = Reporter.replaceHidden((XmlElement) iterCol.next(), !fMultiTenant); + boolean fHidden = xmlColumn.getSafeElement(TAG_HIDDEN).getBoolean(false); + boolean fSubQuery = xmlColumn.getSafeElement(TAG_SUBQUERY).getBoolean(false); + + if (fQueryEmpty && !fSubQuery) + { + String sColumnRef = xmlColumn.getSafeAttribute("id").getString(); + XmlElement xmlTemp = new SimpleElement(TAG_COLUMNREF, sColumnRef); + listQueryCol.add(xmlTemp); + } + + // group-by for hidden columns is not supported (see COH-14871) + fGroupBy |= !fHidden && xmlColumn.getSafeElement("group-by").getBoolean(false); + } + m_fGroupBy = fGroupBy; + } + + } + + /** + * @inheritDoc + */ + public XmlElement getContext() + { + return m_xml; + } + + /** + * @inheritDoc + */ + public void setPattern(String sPattern) + { + m_sQueryTemp = sPattern; + } + + /** + * @inheritDoc + */ + public void postProcess() + { + List listColumn = m_listColumns; + Set setKeys = m_setKeys; + + for (Iterator iterCol = listColumn.iterator(); iterCol.hasNext();) + { + ColumnLocator columnLocator = (ColumnLocator) iterCol.next(); + if (columnLocator != null) + { + columnLocator.reset(setKeys); + } + } + m_source.postProcess(); + } + + /** + * @inheritDoc + */ + public void execute() + { + m_ldtStartTime = System.currentTimeMillis(); + + DataSource source = m_source; + + source.setGroupBy(m_fGroupBy); + + initColumns(); + + MBeanQuery query = ensureBeanQuery(); + Set setKeys = getKeys(); + + source.execute(query, setKeys); + } + + /** + * Obtain the keys for the report. + * + * @return a set containing the keys from the query + */ + public Set getKeys() + { + MBeanQuery mapBeans = ensureBeanQuery(); + XmlElement xmlFilters = m_xml.getElement(TAG_FILTERS); + Set setKeys; + + if (xmlFilters == null) + { + setKeys = mapBeans.keySet(); + } + else + { + FilterFactory filterFactory = new FilterFactory(this, xmlFilters); + String sId = m_xmlFilter.getString(""); + if (sId.length() == 0) + { + setKeys = mapBeans.keySet(); + } + else + { + Filter filter = filterFactory.getFilter(sId); + + setKeys = filter == null ? mapBeans.keySet() : mapBeans.keySet(filter); + } + } + + m_setKeys = setKeys; + return setKeys; + } + + /** + * Obtain the keys for the group by report. + * + * @return a set containing the keys from the group by + */ + public Set getGroupKeys() + { + return m_source.getGroupKeys(); + } + + /** + * Obtain the value for the key and column from the ColumnLocator + * + * @param key the key for the object + * @param oSourceId the ColumnLocator identifier + * + * @return the value of the column + */ + public Object getValue(Object key, Object oSourceId) + { + Map mapSources = m_mapColumns; + ColumnLocator qc = (ColumnLocator) mapSources.get(oSourceId); + + return qc == null ? null : qc.getValue(key); + } + + /** + * Determine if the Column Identifier is an aggregate. + * + * @param column the column identifier + * + * @return true if the column is an aggregate + */ + public boolean isAggregate(Object column) + { + Map mapColumns = m_mapColumns; + ColumnLocator qc = (ColumnLocator) mapColumns.get(column); + + return qc != null && qc.isAggregate(); + } + + /** + * Determine if the Column Identifier has detail values. + * + * @param column the column identifier + * + * @return true if the column is an aggregate + */ + public boolean isDetail(Object column) + { + Map mapColumns = m_mapColumns; + ColumnLocator qc = (ColumnLocator) mapColumns.get(column); + + return qc != null && qc.isRowDetail(); + } + + /** + * Return whether true if any ObjectNames under the query expression that + * limits the report has a multi-tenant attribute. + * + * @return true if ObjectNames with a multi-tenant attibute exist + */ + public boolean isMultiTenant() + { + Boolean FMultiTenant = m_FMultiTenant; + if (FMultiTenant == null) + { + String sPattern = replaceMacros(m_sQueryTemp, null); + try + { + Optional optName = getMBeanServer() + .queryNames(new ObjectName(sPattern), null) + .stream() + .filter(name -> name.getKeyProperty(Constants.DOMAIN_PARTITION) != null) + .findAny(); + + FMultiTenant = m_FMultiTenant = Boolean.valueOf(optName.isPresent()); + } + catch (MalformedObjectNameException e) + { + e.printStackTrace(); + } + } + return FMultiTenant.booleanValue(); + } + + /** + * Initialize locator. + */ + protected void initColumns() + { + Map mapColumns = m_mapColumns; + List listColumn = m_listColumns; + + // Initialize locator. Column renames will override macros. + if (mapColumns.isEmpty()) + { + for (Iterator iterCol = m_listQueryCol.iterator(); iterCol.hasNext();) + { + XmlElement xmlRef = (XmlElement) iterCol.next(); + String sColRef = xmlRef.getString(); + ColumnLocator locator = ensureColumnLocator(xmlRef, sColRef); + if (locator != null) + { + listColumn.add(locator); + mapColumns.put(locator.getId(), locator); + if (locator instanceof CorrelatedLocator) + { + ((CorrelatedLocator) locator).setCorrellatedObject(this.m_oCorrelated); + } + } + } + } + else + { + for (Iterator iter = listColumn.iterator(); iter.hasNext();) + { + ColumnLocator cl = (ColumnLocator)iter.next(); + cl.configure(cl.getConfig()); + cl.setDataSource(m_source); // Set the new datasource + if (cl instanceof CorrelatedLocator) + { + ((CorrelatedLocator) cl).setCorrellatedObject(this.m_oCorrelated); + } + } + } + } + + /** + * Obtains a QueryColumn instance based on the XML configuration. + * + * @param xmlColumn the column definition XML + * + * @return a QueryColumn instance + */ + public ColumnLocator ensureColumnLocator(XmlElement xmlColumn) + { + if (xmlColumn == null) + { + return null; + } + + XmlValue xmlId = xmlColumn.getAttribute("id"); + Map mapColumns = ensureSourceMap(); + XmlElement xmlColDef = xmlColumn; + String sId; + + if (xmlId != null) + { + sId = xmlId.getString(); + xmlColDef = getColumnCfg(xmlColumn, sId); + } + + ColumnLocator columnLocator = (ColumnLocator) mapColumns.get(getColumnKey(xmlColumn)); + if (columnLocator == null) + { + try + { + columnLocator = newColumnLocator(xmlColDef); + columnLocator.configure(xmlColDef, this, m_source); + mapColumns.put(getColumnKey(xmlColDef), columnLocator); + } + catch (IllegalArgumentException e) + { + // Missing column-ref is already logged. See getColumnCfg() + return null; + } + catch (Exception e) // ClassNotFoundException, InstantiationException, IllegalAccessException + { + // Log the Error and Continue. + Base.log(e); + return null; + } + } + return columnLocator; + } + + /** + * Obtain the Map of existing locator for the Reporter keyed by column id. + * + * @return the Map of locator + */ + public Map ensureSourceMap() + { + Map mapColumns = m_mapColumns; + if (mapColumns == null) + { + m_mapColumns = mapColumns = new HashMap(); + } + return mapColumns; + } + + /** + * Instantiate a new report column based on the column configuration XML. + * + * @param xmlColumn the column definition XML + * + * @return the ColumnLocator to access the data + */ + public static ColumnLocator newColumnLocator(XmlElement xmlColumn) + { + String sTypeValue = xmlColumn.getSafeElement(TAG_COLUMNTYPE).getString(VALUE_ATTRIB); + String sClass; + + if (sTypeValue.equals(VALUE_CUSTOM)) + { + sClass = xmlColumn.getSafeElement(TAG_CLASS).getString(); + } + else + { + int nType = columnFromString(sTypeValue); + switch (nType) + { + case COL_GLOBAL: + sTypeValue += "," + xmlColumn.getSafeElement(TAG_COLUMNNAME).getString(); + break; + + case COL_CALC: + sTypeValue += "," + xmlColumn.getSafeElement(TAG_COLUMNFUNC).getString(); + break; + } + + sClass = (String) m_mapColumnClass.get(sTypeValue); + } + + if (sClass == null || sClass.length() == 0) + { + sClass = sTypeValue; + } + + try + { + return (ColumnLocator) ClassHelper.newInstance(Class.forName(sClass), null); + } + catch (Exception e) + { + throw Base.ensureRuntimeException(e, + "Failed to instantiate ColumnLocator " + sClass); + } + } + + /** + * Obtain the list of locator for the Reporter. + * + * @return the List of locator + */ + protected List ensureColumnList() + { + List listColumns = m_listColumns; + if (listColumns == null) + { + m_listColumns = listColumns = new LinkedList(); + } + return listColumns; + } + + /** + * Returns the XML column config given the column id. + * + * @param xmlColumn the XML definition which references the column id + * @param sId the string identifier to locate + * + * @return the requested column configuration XML, null if not found + */ + protected XmlElement getColumnCfg(XmlElement xmlColumn, String sId) + { + List listXml = m_listXmlCol; + + for (Iterator i = listXml.iterator(); i.hasNext();) + { + XmlElement xmlSub = (XmlElement) i.next(); + String sTemp = xmlSub.getSafeAttribute("id").getString(); + if (sTemp.equals(sId)) + { + return xmlSub; + } + } + + Base.log("Unable to locate column-ref \""+sId+"\""); + return null; + } + + /** + * Determine the key for the m_mapColumns given column XML. + * + * @param xmlColumn the column definition XML + * + * @return the id attribute of the column or the column configuration XML + */ + protected static Object getColumnKey(XmlElement xmlColumn) + { + XmlValue xmlTemp = xmlColumn.getAttribute("id"); + if (xmlTemp == null) + { + return xmlColumn; + } + return xmlTemp.getString(); + } + + /** + * Replace all "macro" strings with the value from the ColumnLocator. + * + * @param sTemplate the template contain the macros + * + * @return a string based on the Template with JMX values included + */ + public String replaceMacros(String sTemplate, Object oSource) + { + String sRet = sTemplate; + Set setMacros = Reporter.getMacros(sTemplate); + Map mapMacroExtractors = m_mapMacroExtractors; + + for (Iterator iter = setMacros.iterator(); iter.hasNext();) + { + String sId = (String) iter.next(); + ColumnLocator nl = (ColumnLocator) mapMacroExtractors.get(sId); + ValueExtractor ve = nl == null ? ensureExtractor(sId) : nl.getExtractor(); + + if (ve != null) + { + Object oValue = ve.extract(oSource); + if (oValue != null) + { + sRet = sRet.replaceAll(MACRO_START + sId + MACRO_STOP, + oValue.toString()); + } + } + } + return sRet; + } + + /** + * Create the ColumnLocator based on the XML configuration of the sId. + * + * @param xmlColRef a column-ref XmlElement + * @param sId the column sId to create a ColumnLocator for + * + * @return a ColumnLocator configured with the XML + */ + public ColumnLocator ensureColumnLocator(XmlElement xmlColRef, String sId) + { + return ensureColumnLocator(getColumnCfg(xmlColRef, sId)); + } + + /** + * Create/Obtain the MBeanQuery. + * + * @return the MBeanQuery + */ + protected MBeanQuery ensureBeanQuery() + { + MBeanQuery query = m_mbeanQuery; + if (query == null) + { + String sTemplate = m_sQueryTemp; + String sQuery = replaceMacros(sTemplate, null); + m_mbeanQuery = query = new MBeanQuery(sQuery, getMBeanServer()); + } + return query; + } + + /** + * Obtain the ValueExtractor from the column definition XML. + * + * @param xmlColumn the column definition XML + * + * @return the ValueExtractor for the column + */ + public ValueExtractor ensureExtractor(XmlElement xmlColumn) + { + String sId = xmlColumn.getAttribute("id").getString(); + ensureColumnLocator(xmlColumn); + return ensureExtractor(sId); + } + + /** + * Obtain the ValueExtractor from the Extractor Identifier. + * + * @param sExtractorId the column sId + * + * @return the ValueExtractor for the column + */ + public ValueExtractor ensureExtractor(String sExtractorId) + { + ColumnLocator qc = ensureColumnLocator(null, sExtractorId); + return qc == null ? null : qc.getExtractor(); + } + + + // ----- accessors and helpers ------------------------------------------- + + /** + * Set the correlated key target for a sub query. + * + * @param oTarget the key that should be used for accessing values in the + * outer query + */ + public void setCorrelated(Object oTarget) + { + m_oCorrelated = oTarget; + } + + /** + * Get the current running batch identifier. + * + * @return the batch identifier + */ + public long getBatch() + { + return m_lBatch; + } + + /** + * Set the batch identifier. + * + * @param lBatch the batch identifier + */ + protected void setBatch(long lBatch) + { + m_lBatch = lBatch; + } + + /** + * Get the time the report started execution. + * + * @return the long representing the time the report started + */ + public long getStartTime() + { + return m_ldtStartTime; + } + + /** + * Convert the column string into the internal representation. + * + * @param sType the string representation of the column type + * + * @return the internal representation of the column type + */ + protected static int columnFromString(String sType) + { + if (sType.equals(VALUE_GLOBAL)) + { + return COL_GLOBAL; + } + else if (sType.equals(VALUE_COLCALC)) + { + return COL_CALC; + } + else if (sType.equals(VALUE_METHOD)) + { + return COL_METHOD; + } + else if (sType.equals(VALUE_KEY)) + { + return COL_KEY; + } + else if (sType.equals("")) + { + return COL_ATTRIB; + } + else if (sType.equals(VALUE_ATTRIB)) + { + return COL_ATTRIB; + } + return COL_ERR; + } + + /** + * Return an appropriate {@link MBeanServer} to use. + * + * @return an appropriate MBeanServer to use + */ + protected MBeanServer getMBeanServer() + { + return m_source == null ? null : m_source.getMBeanServer(); + } + + // ----- data members ---------------------------------------------------- + + /** + * The batch identifier for the query. + */ + protected long m_lBatch; + + /** + * The start time of the query + */ + protected long m_ldtStartTime; + + /** + * A list of ColumnLocators for the report + */ + protected List m_listColumns = new LinkedList(); + + /** + * Either the ColumnViews included in the query or the columns specified in the .xml + */ + protected List m_listQueryCol; + + /** + * A map from getColumnKey() to ColumnLocator for the report + */ + protected Map m_mapColumns = new HashMap(); + + /** + * A list of Column XML Elements to be made into locators for the report + */ + protected List m_listXmlCol; + + /** + * The query/pattern template for the report + */ + protected String m_sQueryTemp; + + /** + * The report configuration XML + */ + protected XmlElement m_xml; + + /** + * The filter configuration XML + */ + protected XmlElement m_xmlFilter; + + /** + * The XmlElement holding the list of columns to be included in the Report + */ + protected XmlElement m_xmlColumns; + + /** + * The query MBean + */ + protected MBeanQuery m_mbeanQuery; + + /** + * The attribute and aggregate data source. + */ + protected DataSource m_source; + + /** + * The result set from the query. + */ + protected Set m_setKeys; + + /** + * The correlated target key for sub query. + */ + protected Object m_oCorrelated; + + /** + * Idicates whether there are any Group-By columns. + */ + protected boolean m_fGroupBy; + + /** + * Whether MBeans with a multi-tenant attribute exist. + */ + protected Boolean m_FMultiTenant; + + /** + * A static Map between the String name of the macro and the implementation of it. + */ + public static Map m_mapMacroExtractors = new HashMap(); + + /** + * A static Map between the filter XML definition and the implementation class + * name. + */ + public static Map m_mapColumnClass = new HashMap(); + + static + { + String sBase = "com.tangosol.coherence.reporter.locator."; + m_mapMacroExtractors.put(MACRO_NODE, new NodeLocator()); + m_mapColumnClass.put(VALUE_ATTRIB, sBase + "AttributeLocator"); + m_mapColumnClass.put(VALUE_METHOD, sBase + "OperationLocator"); + m_mapColumnClass.put(VALUE_SUBQUERY, sBase + "SubQueryLocator"); + m_mapColumnClass.put(VALUE_CONSTANT, sBase + "ConstantLocator"); + m_mapColumnClass.put(VALUE_CORRELATED, sBase + "CorrelatedLocator"); + m_mapColumnClass.put(VALUE_PROPERTY, sBase + "PropertyLocator"); + m_mapColumnClass.put(VALUE_GLOBAL + "," + VALUE_BATCH, sBase + "BatchLocator"); + m_mapColumnClass.put(VALUE_GLOBAL + "," + MACRO_START + MACRO_NODE + MACRO_STOP, sBase + "NodeLocator"); ///// Do you want this macro to exist in the columns + m_mapColumnClass.put(VALUE_GLOBAL + "," + VALUE_TIME, sBase + "DateTimeLocator"); + m_mapColumnClass.put(VALUE_KEY, sBase + "KeyLocator"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_SUM, sBase + "SumLocator"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_COUNT, sBase + "CountLocator"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_AVG, sBase + "AverageLocator"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_MIN, sBase + "MinLocator"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_MAX, sBase + "MaxLocator"); + + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_DELTA, sBase + "DeltaLocator"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_DIVIDE, sBase + "DivideLocator"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_ADD, sBase + "AddLocator"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_SUB, sBase + "SubtractLocator"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_MULTI, sBase + "MultiplyLocator"); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/MBeanQuery.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/MBeanQuery.java new file mode 100755 index 0000000000000..037f2241de412 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/MBeanQuery.java @@ -0,0 +1,440 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import com.tangosol.net.management.MBeanHelper; + +import com.tangosol.util.Base; +import com.tangosol.util.Filter; +import com.tangosol.util.InvocableMap; +import com.tangosol.util.ValueExtractor; +import com.tangosol.util.ValueUpdater; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.Comparator; +import java.util.TreeSet; + +import javax.management.ObjectName; +import javax.management.MalformedObjectNameException; +import javax.management.MBeanServer; + +/** +* A "map" class wrapper for the MBeanServer. +* +* @author ew 2008.02.13 +* @since Coherence 3.4 +*/ +public class MBeanQuery + { + // ----- Constructors ---------------------------------------------------- + /** + * Construct a MBeanQuery with the specified pattern. + * + * @param sPattern the JMX Query pattern + */ + public MBeanQuery(String sPattern) + { + this(sPattern, MBeanHelper.findMBeanServer()); + } + + /** + * Construct a MBeanQuery with the specified pattern and {@link MBeanServer}. + * + * @param sPattern the JMX Query pattern + * @param server the {@link MBeanServer} to query against + */ + public MBeanQuery(String sPattern, MBeanServer server) + { + setPattern(sPattern); + f_mbs = server; + } + + /** + * Refresh the cached keyset with the filter. + * + * @param filter a Filter object to limit the results + */ + protected void refreshKeys(Filter filter) + { + if (m_setResults == null || m_setResults.size() == 0 || m_filter == null) + { + try + { + Set setResults = new TreeSet((o, o1) -> + { + ObjectName on = (ObjectName) ((Entry) o).getKey(); + ObjectName on1 = (ObjectName) ((Entry) o1).getKey(); + String s = on.getKeyPropertyListString(); + String s1 = on1.getKeyPropertyListString(); + return s.compareTo(s1); + }); + + Set setMBeans = f_mbs.queryNames(new ObjectName(m_sPattern), new QueryExpFilter(filter)); + for (Iterator iter = setMBeans.iterator(); iter.hasNext();) + { + setResults.add(new Entry(iter.next())); + } + m_setResults = setResults; + m_filter = filter; + } + catch (MalformedObjectNameException e) + { + throw Base.ensureRuntimeException(e); + } + } + } + + /** + * The JMX Query pattern. + * + * @param sPattern The query pattern. + */ + public void setPattern(String sPattern) + { + m_sPattern = sPattern; + } + + /** + * Returns the number of key-value mappings in this query. If the + * query contains more than Integer.MAX_VALUE elements, returns + * Integer.MAX_VALUE. + * + * @return the number of key-value mappings in this query. + */ + public int size() + { + return m_setResults == null ? 0 : m_setResults.size(); + } + + /** + * Removes all mappings from this map (optional operation). + */ + public void clear() + { + m_setResults.clear(); + } + + /** + * Determine if the query results are empty. + * + * @return true if results are empty. + */ + public boolean isEmpty() + { + return m_setResults.isEmpty(); + } + + /** + * Returns a set view of the keys contained in this map. The set is + * backed by the map, so changes to the map are reflected in the set, and + * vice-versa. If the map is modified while an iteration over the set is + * in progress, the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding mapping from + * the map, via the Iterator.remove, Set.remove, + * removeAll retainAll, and clear operations. + * It does not support the add or addAll operations. + * + * @return a set view of the keys contained in this query + */ + public Set entrySet() + { + return keySet(); + } + + /** + * Returns a set view of the keys contained in this map. The set is + * backed by the map, so changes to the map are reflected in the set, and + * vice-versa. If the map is modified while an iteration over the set is + * in progress, the results of the iteration are undefined. The set + * supports element removal, which removes the corresponding mapping from + * the map, via the Iterator.remove, Set.remove, + * removeAll retainAll, and clear operations. + * It does not support the add or addAll operations. + * + * @return a set view of the keys contained in this query + */ + public Set keySet() + { + refreshKeys(null); + return m_setResults; + } + + /** + * Return a set view of the keys contained in this map for entries that + * satisfy the criteria expressed by the filter. + *

+ * Unlike the {@link #keySet()} method, the set returned by this method + * may not be backed by the map, so changes to the set may not reflected + * in the map, and vice-versa. + *

+ * Note: When using the Coherence Enterprise Edition or Grid Edition, + * the Partitioned Cache implements the QueryMap interface using the + * Parallel Query feature. When using Coherence Standard Edition, the + * Parallel Query feature is not available, resulting in lower performance + * for most queries, and particularly when querying large data sets. + * + * @param filter the Filter object representing the criteria that + * the entries of this map should satisfy + * + * @return a set of keys for entries that satisfy the specified criteria + */ + public Set keySet(Filter filter) + { + refreshKeys(filter); + return m_setResults; + } + + /** + * Return a set view of the entries contained in this map that satisfy + * the criteria expressed by the filter. Each element in the returned set + * is a {@link java.util.Map.Entry}. + *

+ * Unlike the {@link #entrySet()} method, the set returned by this method + * may not be backed by the map, so changes to the set may not be reflected + * in the map, and vice-versa. + *

+ * Note: When using the Coherence Enterprise Edition or Grid + * Edition, the Partitioned Cache implements the QueryMap interface using + * the Parallel Query feature. When using Coherence Standard Edition, the + * Parallel Query feature is not available, resulting in lower performance + * for most queries, and particularly when querying large data sets. + * + * @param filter the Filter object representing the criteria that + * the entries of this map should satisfy + * + * @return a set of entries that satisfy the specified criteria + */ + public Set entrySet(Filter filter) + { + return keySet(filter); + } + + /** + * Invoke the passed EntryProcessor against the entries specified by the + * passed keys, returning the result of the invocation for each. + * + * @param collKeys the keys to process; these keys are not required to + * exist within the Query + * @param agent the EntryProcessor to use to process the specified keys + * + * @return a Map containing the results of invoking the EntryProcessor + * against each of the specified keys + */ + public Map invokeAll(Collection collKeys, InvocableMap.EntryProcessor agent) + { + Map mRet = new HashMap(); + for (Iterator i = collKeys.iterator(); i.hasNext(); ) + { + Entry oKey = (Entry)i.next(); + mRet.put(oKey.getKey(), agent.process(oKey)); + } + return mRet; + } + + /** + * Perform an aggregating operation against the entries specified by the + * passed keys. + * + * @param collKeys the Collection of keys that specify the entries within + * this Map to aggregate across + * @param agent the EntryAggregator that is used to aggregate across + * the specified entries of this Query + * + * @return the result of the aggregation + */ + public Object aggregate(Collection collKeys, + InvocableMap.EntryAggregator agent) + { + return agent.aggregate((Set) collKeys); + } + + /** + * Perform an aggregating operation against the set of entries that are + * selected by the given Filter. + * + * @param filter the Filter that is used to select entries within this + * Map to aggregate across + * @param agent the EntryAggregator that is used to aggregate across + * the selected entries of this Query + * + * @return the result of the aggregation + */ + public Object aggregate(Filter filter, InvocableMap.EntryAggregator agent) + { + return agent.aggregate(keySet(filter)); + } + + // ----- InvocableMap.Entry interface ----------------------------------- + + /** + * An InvocableMap.Entry contains additional information and exposes + * additional operations that the basic Map.Entry does not. It allows + * non-existent entries to be represented, thus allowing their optional + * creation. It allows existent entries to be removed from the Map. It + * supports a number of optimizations that can ultimately be mapped + * through to indexes and other data structures of the underlying Map. + */ + public static class Entry + implements InvocableMap.Entry, SortedMap.Entry + { + Entry(Object Key) + { + m_Key = Key; + } + + // ----- Map.Entry interface ------------------------------------ + + /** + * Return the key corresponding to this entry. The resultant key does + * not necessarily exist within the containing Map, which is to say + * that InvocableMap.this.containsKey(getKey) could return + * false. To test for the presence of this key within the Query, use + * {@link #isPresent}, and to create the entry for the key, use + * {@link #setValue}. + * + * @return the key corresponding to this entry; may be null if the + * underlying Map supports null keys + */ + public Object getKey() + { + return m_Key; + } + + /** + * Return the value corresponding to this entry. If the entry does + * not exist, then the value will be null. To differentiate between + * a null value and a non-existent entry, use {@link #isPresent}. + *

+ * Note: any modifications to the value retrieved using this + * method are not guaranteed to persist unless followed by a + * {@link #setValue} or {@link #update} call. + * + * @return the value corresponding to this entry; may be null if the + * value is null or if the Entry does not exist in the Map + */ + public Object getValue() + { + return m_Key; + } + + /** + * Unsupported Operation + * + * @param oValue ignored + * @return null + */ + public Object setValue(Object oValue) + { + throw(new UnsupportedOperationException()); + } + + /** + * Unsupported Operation + * + * @param oValue ignored + * @return null + */ + public void setValue(Object oValue, boolean fSynthetic) + { + throw(new UnsupportedOperationException()); + } + + /** + * Unsupported Operation + * + * @param oValue ignored + * @return null + */ + public void update(ValueUpdater updater, Object oValue) + { + throw(new UnsupportedOperationException()); + } + + /** + * Determine if this Entry exists in the Query. If the Entry is not + * present, it can not be added + * + * @return true iff this Entry is existent in the containing Query + */ + public boolean isPresent() + { + return true; + } + + /** + * {@inheritDoc} + */ + public boolean isSynthetic() + { + return false; + } + + /** + * Unsupported Operation + * + * @param fSynthetic ignored + * @return null + */ + public void remove(boolean fSynthetic) + { + throw(new UnsupportedOperationException()); + } + + + /** + * Extract a value out of the Entry's value. Calling this method is + * semantically equivalent to + * extractor.extract(entry.getValue()), but this method may + * be significantly less expensive. For example, the resultant value may + * be obtained from a forward index, avoiding a potential object + * de-serialization. + * + * @param extractor a ValueExtractor to apply to the Entry's value + * + * @return the extracted value + */ + public Object extract(ValueExtractor extractor) + { + return extractor.extract(m_Key); + } + + /** + * The key and value for this entry. + */ + protected Object m_Key; + } + + // ----- data members ---------------------------------------------------- + + + /** + * The query pattern wrapped by the "map". + */ + protected String m_sPattern; + + /** + * The results from the MBean pattern and the filter. + */ + protected Set m_setResults; + + /** + * The filter for the MBean query. + */ + protected Filter m_filter; + + /** + * The {@link MBeanServer} this MBeanQuery operates against. + */ + protected MBeanServer f_mbs; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/NodeView.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/NodeView.java new file mode 100755 index 0000000000000..b091227dbf481 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/NodeView.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.net.Cluster; + +/** +* Column to include the execution node as a macro within the report +* description or output filename. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class NodeView + extends ColumnView + { + /** + * @inheritDoc + */ + public String getOutputString(Object oObject) + { + if (m_sNodeId == null || m_sNodeId.length() == 0) + { + Cluster cluster = CacheFactory.ensureCluster(); + + String sNodeFmt = "00000" + Integer.toString(cluster.getLocalMember().getId()); + m_sNodeId = sNodeFmt.substring(sNodeFmt.length()-5, sNodeFmt.length()); + } + return m_sNodeId; + } + + /** + * @inheritDoc + */ + public boolean isVisible() + { + return false; + } + + // ----- data members ---------------------------------------------------- + /** + * Formatted Node string + */ + protected String m_sNodeId; + } + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/QueryExpFilter.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/QueryExpFilter.java new file mode 100755 index 0000000000000..88da6ee2e5c90 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/QueryExpFilter.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import com.tangosol.util.Filter; + +import javax.management.ObjectName; +import javax.management.QueryEval; +import javax.management.QueryExp; + +/** +* Wrapper class to map Coherence Filters into JMX QueryExp Interface. +* +* @author ew 2008.02.19 +* @since Coherence 3.4 +*/ +public class QueryExpFilter + extends QueryEval + implements QueryExp + + { + /** + * Construct a QueryExp Wrapper for the Filter + * + * @param filter the Filter to be wrapped. + */ + public QueryExpFilter(Filter filter) + { + m_filter = filter; + } + + /** + * @inheritDoc + */ + public boolean apply(ObjectName objectName) + { + return m_filter == null || m_filter.evaluate(objectName); + } + + /** + * The wrapped Filter. + */ + protected Filter m_filter; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/QueryHandler.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/QueryHandler.java new file mode 100755 index 0000000000000..011b78b94631c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/QueryHandler.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import com.tangosol.run.xml.XmlElement; + +import java.util.Set; + + +/** +* Implementation independent QueryHandler interface. +* +* @author ew 2008.03.22 +* @since Coherence 3.4 +*/ +public interface QueryHandler + { + /** + * Set the XML Context and the query pattern for the Query + * + * @param xmlQuery the Query XML + * @param xmlContext the context for the Query. + */ + public void setContext(XmlElement xmlQuery, XmlElement xmlContext); + + /** + * Get the XML Context for the Query + * + * @return the Context XML + */ + public XmlElement getContext(); + + /** + * Execute the Query + */ + public void execute(); + + /** + * Get the keys from the query. + * + * @return a set of keys + */ + public Set getKeys(); + + /** + * Get the value at for a particular key and column combination + * + * @param key a key returned from getKeys + * @param column a column defined by the Query. + * + * @return the value from the query. + */ + public Object getValue(Object key, Object column); + + /** + * Determine if a particular column is an aggregate value or a detailed value + * + * @param column the column identifier + * + * @return true if the column is an aggregate + */ + public boolean isAggregate(Object column); + + /** + * Determine if a particular column requires multiple rows to display. + * Constant values are not aggregates but are also not detail. + * + * @param column the column identifier + * @return true if the column is an aggregate + */ + public boolean isDetail(Object column); + + /** + * Return whether true if any ObjectNames under the query expression that + * limits the report has a multi-tenant attribute. + * + * @return true if ObjectNames with a multi-tenant attibute exist + */ + public boolean isMultiTenant(); + + /** + * Replace macros in the template with values from key results + * + * @param sTemplate a string template that contains macros. A macro is a + * curly brace {}. + * @param oKey a key value from the query. + * + * @return a string where the macros are replaced with values extracted from + * the query + */ + public String replaceMacros(String sTemplate, Object oKey); + + /** + * Set a correlated target key for the query. This correlated key will be + * used to extract outer query values to be used within the inner query. + * + * @param oTarget the outer query target key + */ + public void setCorrelated(Object oTarget); + + /** + * Execute the post process of the query handler. + */ + public void postProcess(); + + /** + * Get the key set for a group by query. + * + * @return a set of keys to access the results + */ + public Set getGroupKeys(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ReportBatch.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ReportBatch.java new file mode 100755 index 0000000000000..5de99754b24e6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ReportBatch.java @@ -0,0 +1,1230 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.reporter; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.run.xml.XmlDocument; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Base; +import com.tangosol.util.TaskDaemon; + +import java.io.File; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.oracle.coherence.common.base.Blocking; +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + + +/** +* Management class to continually run the reporting process. +* +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class ReportBatch + extends Base + implements Runnable, ReportControl + { + //----- constructor ---------------------------------------------------- + + /** + * Default constructor. + */ + public ReportBatch() + { + } + + /** + * Continually execute a group of reports from the configuration file. + *

+    * Example:
+    *   java com.tangosol.coherence.reporter.ReportBatch config-file
+    * 
+ * Note: to run a group of reports programmaticly, one could to do the following: + * + * new ReportBatch().start(); + * + */ + public static void main(String[] asArg) + { + if (asArg.length == 0) + { + showUsage(); + return; + } + + String sFile = asArg[0]; + + System.setProperty("coherence.management.report.configuration", sFile); + System.setProperty("coherence.management.report.autostart", "true"); + System.setProperty("coherence.management.report.distributed", "false"); + + while (true) + { + CacheFactory.ensureCluster(); + try + { + Blocking.sleep(5000); + } + catch (InterruptedException e) + { + Thread.interrupted(); + break; + } + } + } + + //----- ReportBatch methods ---------------------------------------------- + + /** + * Run the report batch. + */ + public void run() + { + long ldtStart = System.currentTimeMillis(); + ReportBatch model = this; + Map map = m_mapReporters; + Reporter reporter; + + // refresh settings if they have changed + model.setCurrentBatch(model.getCurrentBatch() + 1); + + long nBatchId = model.getCurrentBatch(); + String[] aReport = model.getReports(); + XmlElement[] aParam = model.getParams(); + String sPath = m_sOutputDir; + long cReports = aReport.length; + + if (model.setState(/*sExpectState*/ STATE_WAITING, /*sNewState*/ STATE_RUNNING) || + model.setState(/*sExpectState*/ STATE_STARTED, /*sNewState*/ STATE_RUNNING)) + { + model.setLastExecutionMillis(System.currentTimeMillis()); + for (int i = 0; i < cReports; i++) + { + Integer nKey = Integer.valueOf(i); + String sDefFile = aReport[i]; + XmlElement xmlParam = aParam[i]; + + reporter = (Reporter) map.get(nKey); + if (reporter == null) + { + reporter = new Reporter(); + map.put(nKey, reporter); + } + + reporter.setDateFormat(m_dateFormat); + model.setLastReport(sDefFile); + + reporter.run(sDefFile, sPath, nBatchId, xmlParam, + ReportBatch.class.getClassLoader()); + } + + updateStats(ldtStart); + model.setState(/*sExpectState*/ STATE_RUNNING, /*sNewState*/ STATE_WAITING); + } + } + + /** + * Output usage instructions. + */ + public static void showUsage() + { + out(); + out("java com.tangosol.coherence.reporter.ReportBatch "); + out(); + out("command option descriptions:"); + out("\t the file containing the report configuration XML"); + out(); + } + + // ----- ReportControl methods ------------------------------------------- + + /** + * Obtain the daemon for the Reporter task. + * + * @return the daemon the Reporter is executing + */ + public TaskDaemon getDaemon() + { + return m_daemon; + } + + /** + * Set the daemon for the Reporter task. + * + * @param daemon the daemon the Reporter is executing + */ + public void setDaemon(TaskDaemon daemon) + { + m_daemon = daemon; + } + + /** + * Check to see if the execution thread is running. + * + * @return the thread running the reporter + */ + public boolean isRunning() + { + return m_fRun; + } + + /** + * Set the last report executed. + * + * @param sLastReport the last Report Executed + */ + public void setLastReport(String sLastReport) + { + m_sLastReport = sLastReport; + } + + /** + * Set the list of reports in the execution list. + * + * @param asReports the report execution list + */ + public void setReports(String[] asReports) + { + m_asReports = asReports; + } + + /** + * Set the last time a report was executed. + * + * @param ldtTime the last time a reported executed as a long + */ + public void setLastExecutionMillis(long ldtTime) + { + m_ldtLastExecutionMillis = ldtTime; + } + + /** + * Set the last time a report was executed. + * + * @return the last time a reported executed as a long + */ + public long getLastExecutionMillis() + { + return m_ldtLastExecutionMillis; + } + + /** + * Set the state of the reporter. + * + * @param sState the state of the reporter + */ + public void setState(String sState) + { + m_refState.set(sState); + } + + /** + * Compare and Set the state of the reporter. + * + * @param sExpectState the expected state of the reporter + * @param sNewState the new state to set + * + * @return true if the state is set successfully + */ + public boolean setState(String sExpectState, String sNewState) + { + return m_refState.compareAndSet(sExpectState, sNewState); + } + + /** + * Get the batch configuration XML that conforms to batch-config.xml. + * + * @return the batch configuration XML + */ + public XmlDocument getXml() + { + return m_xml; + } + + /** + * Set the batch configuration XML that conforms to batch-config.xml. + * + * @param xml the XML configuration for the Reporter + */ + public void setXml(XmlDocument xml) + { + m_xml = xml; + } + + /** + * Convert the batch configuration XML to an array for the MBean. + * + * @param xmlReports the batch configuration report list + * + * @return the array of report configuration file names + */ + private String[] makeReportArray(XmlElement xmlReports) + { + List listReports = xmlReports.getElementList(); + String[] asReports = new String[listReports.size()]; + XmlElement[] axmlParam = new XmlElement[listReports.size()]; + int c = 0; + for (Iterator i = listReports.iterator(); i.hasNext();) + { + XmlElement o = (XmlElement)i.next(); + asReports[c] = o.getSafeElement(TAG_LOCATION).getString(); + axmlParam[c] = o.getElement(TAG_PARAMS); + c++; + } + m_aParams = axmlParam; + return asReports; + } + + //----- ReportControl Interface ---------------------------------------- + + /** + * {@inheritDoc} + */ + public long getCurrentBatch() + { + return m_nCurrentBatch; + } + + /** + * {@inheritDoc} + */ + public void setCurrentBatch(long nNewBatch) + { + m_nCurrentBatch = nNewBatch; + } + + /** + * {@inheritDoc} + */ + public long getIntervalSeconds() + { + return m_nInterval / 1000; + } + + /** + * {@inheritDoc} + */ + public String getOutputPath() + { + return m_sOutputDir == null ? "" : new File(m_sOutputDir).getAbsolutePath(); + } + + /** + * {@inheritDoc} + */ + public void setOutputPath(String sPath) + { + m_sOutputDir = sPath; + } + + /** + * {@inheritDoc} + */ + public void setIntervalSeconds(long nInterval) + { + m_nInterval = nInterval * 1000; + } + + /** + * {@inheritDoc} + */ + public String getConfigFile() + { + return m_sConfigFile; + } + + /** + * {@inheritDoc} + */ + public String getState() + { + return m_refState.get(); + } + + /** + * {@inheritDoc} + */ + public boolean isAutoStart() + { + return getDependencies().isAutoStart(); + } + + /** + * {@inheritDoc} + */ + public void stop() + { + if (!getState().equals(STATE_ERROR)) + { + synchronized (this) + { + if (isRunning()) + { + m_fRun = false; + setState(STATE_STOPPING); + getDaemon().stop(); + setState(STATE_STOPPED); + setDaemon(null); + Base.log("Management Reporting - Stopped"); + } + } + } + } + + /** + * {@inheritDoc} + */ + public void start() + { + if (getState().equals(STATE_ERROR)) + { + Base.log("Management Reporting - " + + "An unrecoverable error has occurred. Reporter not started."); + } + else + { + synchronized (this) + { + TaskDaemon daemon = getDaemon(); + if (m_daemon == null && m_sConfigFile != null) + { + ReportBatch oReport = this; + daemon = new TaskDaemon("Reporter"); + daemon.schedulePeriodicTask(oReport, System.currentTimeMillis() + + getIntervalSeconds() * 1000, getIntervalSeconds() * 1000); + Base.log("Management Reporting - Started"); + daemon.start(); + + setState(STATE_STARTED); + m_fRun = true; + } + + setDaemon(daemon); + } + } + } + + /** + * {@inheritDoc} + */ + public String[] getReports() + { + return m_asReports; + } + + /** + * {@inheritDoc} + */ + public XmlElement[] getParams() + { + return m_aParams; + } + + /** + * Set the array of XML elements for the initialization parameters. + * + * @param aXml array of xml elements + */ + public void setParams(XmlElement[] aXml) + { + m_aParams = aXml; + } + + /** + * {@inheritDoc} + */ + public void setConfigFile(String sInputFilename) + { + try + { + synchronized (this) + { + m_mapReporters = new HashMap(); + m_sConfigFile = sInputFilename; + XmlDocument xml = XmlHelper.loadFileOrResource( + sInputFilename, "Reporter configuration", + ReportBatch.class.getClassLoader()); + XmlHelper.replaceSystemProperties(xml, "system-property"); + + setXml(xml); + setOutputPath(xml.getSafeElement(TAG_DIR).getString("")); + setIntervalSeconds(Base.parseTime(xml.getSafeElement(TAG_FREQ).getString(DEFAULT_FREQ))/1000); + m_asReports = makeReportArray(xml.getSafeElement(TAG_LIST)); + } + } + catch (Exception e) // FileNotFoundException + { + setState(STATE_ERROR); + Base.log("Failed to start Reporter " + e); + m_asReports = new String[0]; + } + } + + /** + * {@inheritDoc} + */ + public void runReport(String sReportFile) + { + if (!getState().equals(STATE_ERROR)) + { + new Reporter().run(sReportFile, m_sOutputDir, m_nCurrentBatch, null, + ReportBatch.class.getClassLoader()); + } + } + + /** + * {@inheritDoc} + */ + public TabularData runTabularReport(String sReportFile) + { + boolean fURI = Reporter.isURI(sReportFile); + + // Reporters for URI based reports are cached. + TabularReportRunner runner = fURI ? f_mapReporter.get(sReportFile) + : new TabularReportRunner(sReportFile, fURI); + if (runner == null) + { + runner = new TabularReportRunner(sReportFile, fURI); + f_mapReporter.put(sReportFile, runner); + } + return runner.runTabularReport(); + } + + /** + * {@inheritDoc} + */ + public TabularData runTabularGroupReport(String sReportName, Map mapXmlReports) + { + TabularReportRunner runner = new TabularReportRunner(sReportName, mapXmlReports); + return runner.runTabularReport(); + } + + /** + * {@inheritDoc} + */ + public String getLastReport() + { + return m_sLastReport; + } + + /** + * {@inheritDoc} + */ + public Date getLastExecuteTime() + { + return new Date(getLastExecutionMillis()); + } + + /** + * {@inheritDoc} + */ + public long getRunLastMillis() + { + return m_lastRuntimeMillis; + } + + /** + * {@inheritDoc} + */ + public long getRunMaxMillis() + { + return this.m_maxRuntimeMillis; + } + + /** + * {@inheritDoc} + */ + public double getRunAverageMillis() + { + return (m_cExecutionCount == 0) ? 0.0 + : this.m_totalRuntimeMillis / m_cExecutionCount; + } + + /** + * {@inheritDoc} + */ + public void resetStatistics() + { + m_lastRuntimeMillis = 0; + m_maxRuntimeMillis = 0; + m_cExecutionCount = 0; + m_totalRuntimeMillis = 0; + } + + /** + * {@inheritDoc} + */ + public boolean isCentralized() + { + return getDependencies().isDistributed(); + } + + + // ----- helper methods -------------------------------------------------- + + protected void updateStats(long ldtStart) + { + long lRuntime = System.currentTimeMillis() - ldtStart; + m_cExecutionCount ++; + m_maxRuntimeMillis = (m_maxRuntimeMillis < lRuntime) + ? lRuntime + : m_maxRuntimeMillis; + m_totalRuntimeMillis += lRuntime; + m_lastRuntimeMillis = lRuntime; + } + + /** + * {@inheritDoc} + */ + public void setDependencies(Dependencies dps) + { + if (getDependencies() == null) + { + m_dependencies = dps = new DefaultDependencies(dps).validate(); + } + else + { + throw new IllegalStateException("Reporter dependencies cannot be reset"); + } + + setConfigFile(dps.getConfigFile()); + + String sTimezone = dps.getTimeZone(); + String sTimeStampFormat = dps.getDateFormat(); + + m_dateFormat = new SimpleDateFormat(sTimeStampFormat); + if (!sTimezone.isEmpty()) + { + m_dateFormat.setTimeZone(getTimeZone(sTimezone)); + } + } + + /** + * {@inheritDoc} + */ + public Dependencies getDependencies() + { + return m_dependencies; + } + + // ----- inner interface -------------------------------------------------- + + /** + * The interface used to provide reporter with its external dependencies. + */ + public interface Dependencies + { + /** + * Return the report configuration that contain the location for + * the Reporter batch. + * + * @return the report configuration file + */ + String getConfigFile(); + + /** + * Return the report switch for reporter. + * + * @return true to enable reporter + */ + boolean isAutoStart(); + + /** + * Return the distributed flag that specifies whether or not to run + * reporter on multiple management node. + * + * @return true to enable distributed reporter + */ + boolean isDistributed(); + + /** + * Return the time zone for the generated reports. + * + * @return time zone + */ + String getTimeZone(); + + /** + * Return the time stamp format for reporter. + * + * @return time output format + */ + String getDateFormat(); + } + + + // ----- inner classes -------------------------------------------------- + + /** + * Default {@link Dependencies} implementation. + */ + public static class DefaultDependencies + implements Dependencies + { + /** + * Construct a DefaultReportDependencies object. Uses default value for each dependency. + */ + public DefaultDependencies() + { + this(null); + } + + /** + * Construct a DefaultReportDependencies object copying the values + * from the specified ReporterDependencies object. + * + * @param deps the dependencies to copy + */ + public DefaultDependencies(Dependencies deps) + { + if (deps != null) + { + m_sConfigFile = deps.getConfigFile(); + m_autoStart = deps.isAutoStart(); + m_distributed = deps.isDistributed(); + m_sTimezone = deps.getTimeZone(); + m_sDateFormat = deps.getDateFormat(); + } + } + + /** + * {@inheritDoc} + */ + public String getConfigFile() + { + return m_sConfigFile; + } + + /** + * Set the report configuration file. + * + * @param sConfFile the report configuration file + * + * @return this object + */ + public DefaultDependencies setConfigFile(String sConfFile) + { + m_sConfigFile = sConfFile; + return this; + } + + /** + * {@inheritDoc} + */ + public boolean isAutoStart() + { + return m_autoStart; + } + + /** + * Set the reporter switch. + * + * @param fAutoStart the reporter switch, true to enable reporting. + * + * @return this object + */ + public DefaultDependencies setAutoStart(boolean fAutoStart) + { + m_autoStart = fAutoStart; + return this; + } + + /** + * {@inheritDoc} + */ + public boolean isDistributed() + { + return m_distributed; + } + + /** + * Set the distributed flag. + * + * @param fDistributed specify whether the reporter should run on multiple nodes. + * + * @return this object + */ + public DefaultDependencies setDistributed(boolean fDistributed) + { + m_distributed = fDistributed; + return this; + } + + /** + * {@inheritDoc} + */ + public String getTimeZone() + { + return m_sTimezone; + } + + /** + * Set the time zone. + * + * @param sTimeZone time zone for the reports. + * + * @return this object + */ + public DefaultDependencies setTimeZone(String sTimeZone) + { + m_sTimezone = sTimeZone; + return this; + } + + /** + * {@inheritDoc} + */ + public String getDateFormat() + { + return m_sDateFormat; + } + + /** + * Set the time format. + * + * @param sTimeFormat time stamp format for the reports. + * + * @return this object + */ + public DefaultDependencies setDateFormat(String sTimeFormat) + { + m_sDateFormat = sTimeFormat; + return this; + } + + /** + * Validate the report configuration. + * + * @throws IllegalArgumentException if the configuration file are not valid + * + * @return this object + */ + public DefaultDependencies validate() + { + Base.checkNotNull(m_sConfigFile, "configuration"); + return this; + } + + // ----- data members of DefaultDependencies -------------------------------- + + /** + * The report configuration file. + */ + protected String m_sConfigFile = "reports/report-group.xml"; + + /** + * The reporter switch, true to enable reporting. + */ + protected boolean m_autoStart = false; + + /** + * The distributed flag that specifies whether or not to run reporter + * on multiple management node.. + */ + protected boolean m_distributed = false; + + /** + * The time zone for reports. + */ + protected String m_sTimezone = ""; + + /** + * The time stamp format for reports. + */ + protected String m_sDateFormat = "EEE MMM dd HH:mm:ss zzz yyyy"; + } + + // ----- inner classes -------------------------------------------------- + + /** + * TablularReportRunner runs the report and returns the data in a tabular data format. + */ + public class TabularReportRunner + { + /** + * Construct a {@code TabularReportRunner} using the specified parameters. + * + * @param sReportOrGroup the URI or contents of either a report group or individual + * report file. + * @param fURI flag indicating if the report file is a URI or + * XML content. + */ + public TabularReportRunner(String sReportOrGroup, boolean fURI) + { + f_fURI = fURI; + XmlDocument xmlDocument = fURI + ? XmlHelper.loadFileOrResource(sReportOrGroup, "Reporter configuration", + ReportBatch.class.getClassLoader()) + : XmlHelper.loadXml(sReportOrGroup); + + // could be a report group or a single report + f_fReportGrp = xmlDocument.getName().equals("report-group"); + + if (f_fReportGrp) + { + f_sReportGroup = sReportOrGroup; + f_sReport = null; + List xmlReports = xmlDocument.getSafeElement(TAG_LIST).getElementList(); + f_mapReports = new LinkedHashMap(xmlReports.size()); + for (Iterator iter = xmlReports.iterator(); iter.hasNext();) + { + // Individual reports can only be URI. Hence the value is null. + f_mapReports.put(((XmlElement) iter.next()).getSafeElement(TAG_LOCATION).getString(), null); + } + } + else + { + f_sReport = sReportOrGroup; + f_sReportGroup = null; + } + } + + /** + * Construct a {@code TabularReportRunner} using the specified parameters. + * + * @param sReportGroup the URI of the report group. + * @param mapXmlReports map of Individual report names and their XML content. + */ + public TabularReportRunner(String sReportGroup, Map mapXmlReports) + { + f_sReportGroup = sReportGroup; + f_fURI = false; + f_fReportGrp = true; + f_sReport = null; + // Individual reports can only be XML content. + f_mapReports = new LinkedHashMap(mapXmlReports); + } + + /** + * Run the report. + * + * @return the data in TabularData format + */ + public TabularData runTabularReport() + { + if (!getState().equals(STATE_ERROR)) + { + if (f_fReportGrp) + { + int cReports = f_mapReports.size(); + OpenType[] aOpenTypes = new OpenType[cReports]; + String[] asReportDesc = new String[cReports]; + String[] asReportNames = new String[cReports]; + Map mapTabulars = new HashMap(); + int i = 0; + for (Map.Entry entry : f_mapReports.entrySet()) + { + String sReport = entry.getKey(); + String sContent = entry.getValue(); + + // If the individual reports are URIs, then entry's value will be null and the key is the URI + // otherwise entry's value is the report's xml content. + int index = sReport.lastIndexOf('/'); + String sTabularType = index < 0 ? sReport : sReport.substring(index); + TabularData tabData = runSingleReport(sContent == null ? sReport : sContent, sTabularType); + + asReportNames[i] = sReport; + mapTabulars.put(sReport, tabData); + if (tabData == null) + { + aOpenTypes[i] = SimpleType.STRING; + asReportDesc[i] = sTabularType; + } + else + { + TabularType tabType = tabData.getTabularType(); + aOpenTypes[i] = tabType; + asReportDesc[i] = tabType.getDescription(); + } + i++; + } + + try + { + String sReport = f_sReportGroup; + CompositeType rowType = new CompositeType(sReport, sReport, asReportNames, asReportDesc, aOpenTypes); + TabularType tabType = new TabularType(sReport, sReport, rowType, asReportNames); + TabularDataSupport tabDataSupport = new TabularDataSupport(tabType); + tabDataSupport.put(new CompositeDataSupport(rowType, mapTabulars)); + return tabDataSupport; + } + catch (OpenDataException e) + { + throw Base.ensureRuntimeException(e); + } + } + else + { + return runSingleReport(f_sReport, f_fURI ? f_sReport : DEFAULT_TABULAR_TYPE_NAME); + } + } + return null; + } + + /** + * Run an individual report. + * + * @param sReport the URI or the content of the report + * @param sTabularType the typeName of the {@code TabularType} + * + * @return report data in TabularData format + */ + protected TabularData runSingleReport(String sReport, String sTabularType) + { + // reporter can handle both URI and xml content. + Reporter reporter = getReporter(sReport); + return reporter.run(sReport, m_sOutputDir, sTabularType, m_nCurrentBatch, null, + ReportBatch.class.getClassLoader(), false, true); + } + + /** + * Get the Reporter. + * + * @param sReportFile Report file + * + * @return Reporter associated with the given report file. + */ + protected Reporter getReporter(String sReportFile) + { + Reporter reporter = f_fURI ? f_mapReporter.get(sReportFile) : new Reporter(); + if (reporter == null) + { + reporter = new Reporter(); + f_mapReporter.put(sReportFile, reporter); + } + return reporter; + } + + // ----- data members ------------------------------------------------ + + /** + * Report URI or content. Reporter MBeans can be invoked by passing the report + * content(for example when used in JVisualVM) or the report URI. + */ + protected final String f_sReport; + + /** + * Report Group URI or content. Reporter MBeans can be invoked by passing the report + * group content(for example when used in JVisualVM) or the report group URI. + */ + protected final String f_sReportGroup; + + /** + * Flag indicating if the report name is a URI. + */ + protected final boolean f_fURI; + + /** + * Flag indicating if the report is a group report. + */ + protected final boolean f_fReportGrp; + + /** + * Map of Individual report names and their XML content. + */ + protected Map f_mapReports; + + /** + * Map of Individual reports and their associated Reporter. + */ + protected final Map f_mapReporter = new HashMap(); + } + + // ----- data members ---------------------------------------------------- + + /** + * The state of the execution thread. + */ + private AtomicReference m_refState = new AtomicReference<>(STATE_STOPPED); + + /** + * The file name of the last report executed. + */ + private String m_sLastReport; + + /** + * The output path of the data files. + */ + private String m_sOutputDir; + + /** + * The batch configuration filename. + */ + private String m_sConfigFile; + + /** + * The current execution batch. + */ + private long m_nCurrentBatch; + + /** + * Flag to determine if the process should be running (false stops execution + * thread. + */ + private boolean m_fRun; + + /** + * Array of report configuration file names in the batch. + */ + private String[] m_asReports; + + /** + * Number of milliseconds to wait between batch executions. + */ + private long m_nInterval; + + /** + * The batch configuration XML. + */ + private XmlDocument m_xml; + + /** + * The report execution daemon. + */ + protected TaskDaemon m_daemon; + + /** + * The start date and time of the last batch execution. + */ + private long m_ldtLastExecutionMillis; + + /** + * The parameters for each report. + */ + private XmlElement[] m_aParams; + + /** + * The map of Reporter instances to running in the batch. + */ + protected Map m_mapReporters = new HashMap(); + + /** + * The last batch execution time in milliseconds. + */ + protected long m_lastRuntimeMillis; + + /** + * The maximum runtime in milliseconds. + */ + protected long m_maxRuntimeMillis; + + /** + * The total number of executions. + */ + protected long m_cExecutionCount; + + /** + * The total runtime in milliseconds. + */ + protected long m_totalRuntimeMillis; + + /** + * Report dependencies. + */ + private Dependencies m_dependencies; + + /** + * Date format. + */ + protected DateFormat m_dateFormat; + + /** + * Map of Reports and their associated TablularReportRunner. Reporters for URI + * based reports are cached. + */ + protected Map f_mapReporter = new HashMap(); + + // ----- Constants ------------------------------------------------------ + + /** + * The execution thread has been started. + */ + public static final String STATE_STARTED = "Started"; + + /** + * The controlling thread is attempting to stop the execution thread. + */ + public static final String STATE_STOPPING = "Stopping"; + + /** + * The execution thread is stopped. + */ + public static final String STATE_STOPPED = "Stopped"; + + /** + * The execution thread is waiting for the frequency time before running. + */ + public static final String STATE_WAITING = "Sleeping"; + + /** + * The execution thread is running a report. + */ + public static final String STATE_RUNNING = "Running"; + + /** + * The Reporter Batch has received an Error and can not continue. + */ + public static final String STATE_ERROR = "Error"; + + /** + * The frequency which the report batch will execute. + */ + public static final String TAG_FREQ = "frequency"; + + /** + * The tag in the xml which contains the location of the report configuration. + */ + public static final String TAG_LOCATION = "location"; + + /** + * The tag in the xml which contains the report configuration pararameters. + */ + public static final String TAG_PARAMS = "init-params"; + + /** + * The tag in the xml which contains the output path. + */ + public static final String TAG_DIR = "output-directory"; + + /** + * The tag in the xml which contains the report-list. + */ + public static final String TAG_LIST = "report-list"; + + /** + * The value of the default frequency if frequency is not specified. + */ + public static final String DEFAULT_FREQ = "60s"; + + /** + * The constants to be used as the typeName of the {@code TabularType} in non URL based + * reports i.e. where report XML's are passed as the content of the Reporter invocation. + */ + public static final String DEFAULT_TABULAR_TYPE_NAME = "coherence-report.xml"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ReportColumnView.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ReportColumnView.java new file mode 100755 index 0000000000000..e19151df4fb76 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ReportColumnView.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + + +import com.tangosol.run.xml.XmlElement; + +import java.util.Set; + +/** +* This interface is used by the Reporter to get display information on a column. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public interface ReportColumnView + { + /** + * Configure the Report Column view. + * + * @param xmlConfig the configuration XML + */ + public void configure(XmlElement xmlConfig); + + /** + * Set the QueryHandler context for the view. The QueryHandler is the + * "source" of the view data. + * + * @param handler the configuration XML + */ + public void setQueryHandler(QueryHandler handler); + + /** + * Obtain the formatted data. + * + * @param oKey the key identifier + * + * @return the value to be included in the report + */ + public String getOutputString(Object oKey); + + /** + * Obtain the column header. + * + * @return the column header. + */ + public String getHeader(); + + /** + * Determine if the column should be included in the output file. + * + * @return true if the column is included; false otherwise + */ + public boolean isVisible(); + + /** + * Determine if the value of the column is a single [detail] value. + * Aggregates and constants often return false. + * + * @return true if multiple values exist; false otherwise + */ + public boolean isRowDetail(); + + /** + * Obtain the string identifier for the ColumnView. + * + * @return the column identifier + */ + public String getId(); + } + diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ReportControl.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ReportControl.java new file mode 100755 index 0000000000000..90bc8ea4a4774 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/ReportControl.java @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.reporter; + + +import com.tangosol.run.xml.XmlDocument; +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.TaskDaemon; + +import java.util.Date; +import java.util.Map; + +import javax.management.openmbean.TabularData; + + +/** +* A custom MBean to manage the reporter execution. +* +* The following properties may be used for configuration of the monitoring node: +* +* +* +* +* +* +* +* +* +* +* +* +* +* +*
coherence.management.report.groupSpecifies the report group XML file to execute. The +* report group file must conform to coherence-report-group.xsd +*
coherence.management.report.autostartSpecifies if the service is to auto start. Valid values: +* "true" and "false"
coherence.management.report.centralizedIf true the Reporter MBean is registered as a singleton. +* If false, the Reporter MBean is registered with a global nodeId. Valid values: +* "true" and "false"
+* +* @author ew 2008.02.28 +* @since Coherence 3.4 +*/ +public interface ReportControl + { + // ----- ReportControl methods ------------------------------------------- + + /** + * Specify the ReportControl instance with specified Dependencies. + * + * @param dps The reporter dependencies to utilize + */ + public void setDependencies(ReportBatch.Dependencies dps); + + /** + * Getter for reporter property Dependencies. + * + * @return dependencies object + */ + public ReportBatch.Dependencies getDependencies(); + + /** + * Obtain the daemon for the Reporter task. + * + * @return the daemon the Reporter is executing + */ + + public TaskDaemon getDaemon(); + + /** + * Set the daemon for the Reporter task. + * + * @param daemon the daemon the Reporter is executing + */ + public void setDaemon(TaskDaemon daemon); + + /** + * Check to see if the execution thread is running. + * + * @return the thread running the reporter + */ + public boolean isRunning(); + + /** + * Set the last report executed. + * + * @param sLastReport the last Report Executed + */ + public void setLastReport(String sLastReport); + + /** + * Set the list of reports in the execution list. + * + * @param asReports the report execution list + */ + public void setReports(String[] asReports); + + /** + * Set the last time a report was executed. + * + * @param ldtExeTime the last time a reported executed as a long + */ + public void setLastExecutionMillis(long ldtExeTime); + + /** + * Set the last time a report was executed. + * + * @return the last time a reported executed as a long + */ + public long getLastExecutionMillis(); + + /** + * Set the state of the reporter. + * + * @param sState the state of the reporter + */ + public void setState(String sState); + + /** + * Get the batch configuration XML that conforms to batch-config.xml. + * + * @return the batch configuration XML + */ + public XmlDocument getXml(); + + /** + * Set the batch configuration XML that conforms to batch-config.xml. + * + */ + public void setXml(XmlDocument xml); + + /** + * Get the bacth identifier for the Reporter. + * + * @return the current batch for execution + */ + public long getCurrentBatch(); + + /** + * set the bacth identifier for the Reporter. + * + * @param nNewBatch the new batch identifier + */ + public void setCurrentBatch(long nNewBatch); + + /** + * Get the frequency of execution in Seconds. + * + * @return the frequency which the reporter executes the configured reports + */ + public long getIntervalSeconds(); + + /** + * Get the output path for the data files. + * + * @return the output path for the data files. + */ + public String getOutputPath(); + + /** + * Set the output path for the data files. + * + * @param sPath the new batch identifier + */ + public void setOutputPath(String sPath); + + /** + * Set the frequency of execution in Milliseconds. + * + * @param nInterval the frequency which the reporter executes the configured reports + */ + public void setIntervalSeconds(long nInterval); + + /** + * Get the batch configuration file name. The file corresponds to + * coherence-report-config.xsd. + * + * @return the frequency which the reporter executes the configured reports + */ + public String getConfigFile(); + + /** + * Get the state of the reporter. Valid values are : Started, Stopped, Stopping, + * Running and Waiting. + * + * @return the state of the reporter + */ + public String getState(); + + /** + * Determine if the Reporter auto starts. + * + * @return true if autostart + */ + public boolean isAutoStart(); + + /** + * Stop the reporter. The reporter will continue to execute the current + * batch of reports prior to termination. + */ + public void stop(); + + /** + * Start the reporter. If the reporter is already running, this method does + * not start another one. + */ + public void start(); + + /** + * Get a list of reports executing by the batch reporter. + * + * @return a string array of the reports in the execution batch + */ + public String[] getReports(); + + /** + * Get the array of XML elements for the initialization parameters. + */ + public XmlElement[] getParams(); + + /** + * Set the Parameter XML. + */ + public void setParams(XmlElement[] aXml); + + /** + * Set the batch configuration file name. When setting this value on a remote + * server, the remote server must have access to the path and file. + * The file must conform to coherence-report-config.xsd. + * + * @param sInputFilename The path and file name of the batch configuration + */ + public void setConfigFile(String sInputFilename); + + /** + * Execute a single report on time. + * + * @param sReport a report configuration path or XML content + */ + public void runReport(String sReport); + + /** + * Execute the specified report file or report XML defined in the {@code sReport} + * argument. If the report XML file or XML content defines a single report, the returned + * TabularData will have a CompositeData for each row of values from the report. + * It will also include a rowId attribute for indexing. + * For example: + *
+    * TabularData(sReportFile) =
+    *             TabularData[ CompositeData { attribute1 -> value1,
+    *                                          attribute2 -> value2,
+    *                                          rowId      -> 1},
+    *                          CompositeData { attribute1 -> value1,
+    *                                          attribute2 -> value2,
+    *                                          rowId      -> 2} ]
+    * 
+ * If the specified file defines a report group, the returned + * TabularData will have a single CompositeData with report-names as keys + * and TabularDatas of the reports as values. For example: + *
+    * TabularData[ CompositeData { report1 -> TabularData(report1),
+    *                              report2 -> TabularData(report2) } ]
+    * 
+ *

+ * If the XML content in the {@code sReport} defines a report group, then it + * must contain the URI of individual reports in the group. It cannot have XML + * content for the individual reports. + * + * @param sReport a report or report-group configuration path and filename + * or a String containing the report XML + * + * @return a tabularData with the above specified representation. + */ + public TabularData runTabularReport(String sReport); + + /** + * Execute the specified group report. The group's member report names and + * their xml content are passed in the map. + *

+ * The returned TabularData will have a single CompositeData with report-name as keys + * and TabularDatas of the reports as values. For example: + *

+     * TabularData[ CompositeData { report1 -> TabularData(report1),
+     *                              report2 -> TabularData(report2) } ]
+     * 
+ * + * @param sReportName report-group name + * @param mapXmlReports Map of Individual report names and their XML content. + * + * @return a tabularData with the above specified representation. + */ + public TabularData runTabularGroupReport(String sReportName, MapmapXmlReports); + + /** + * Get the last report file executed. + * + * @return a string array of the reports in the execution batch + */ + public String getLastReport(); + + /** + * Get the date and time the reporter executed the batch. + * + * @return the date and time the reporter last executed + */ + public Date getLastExecuteTime(); + + /** + * Get the last execution duration in milliseconds since the last statistics + * reset. + * + * @return the date and time the reporter last executed + */ + public long getRunLastMillis(); + + /** + * Get the Maximum execution duration in milliseconds since the last + * statistics reset. + * + * @return the date and time the reporter last executed + */ + public long getRunMaxMillis(); + + /** + * Get the average execution duration in milliseconds since the last + * statistics reset. + * + * @return the date and time the reporter last executed + */ + public double getRunAverageMillis(); + + /** + * Reset the execution statistics. + */ + public void resetStatistics(); + + /** + * Determine if the Reporter is running in a centralized or Distributed mode. + */ + public boolean isCentralized(); + + // ----- Constants ------------------------------------------------------ + + /** + * The execution thread has been started. + */ + public static final String STATE_STARTED = "Started"; + + /** + * The controlling thread is attempting to stop the execution thread. + */ + public static final String STATE_STOPPING = "Stopping"; + + /** + * The execution thread is stopped. + */ + public static final String STATE_STOPPED = "Stopped"; + + /** + * The execution thread is waiting for the frequency time before running. + */ + public static final String STATE_WAITING = "Sleeping"; + + /** + * The execution thread is running a report. + */ + public static final String STATE_RUNNING = "Running"; + + /** + * The tag in the xml which contains the frequency time. + */ + public static final String TAG_FREQ = "frequency"; + + /** + * The tag in the xml which contains the frequency time. + */ + public static final String TAG_DIR = "output-directory"; + + /** + * The tag in the xml which contains the report-list. + */ + public static final String TAG_LIST = "report-list"; + + /** + * The value of the default frequency if frequency is not specified. + */ + public static final String DEFAULT_FREQ = "60s"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/Reporter.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/Reporter.java new file mode 100755 index 0000000000000..fd95b7a622d88 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/Reporter.java @@ -0,0 +1,1201 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.net.management.MBeanHelper; + +import com.tangosol.run.xml.SimpleElement; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; +import com.tangosol.run.xml.XmlValue; + +import com.tangosol.util.Base; + +import java.net.URI; +import java.net.URISyntaxException; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import java.text.DateFormat; + +import javax.management.ObjectName; + +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +/** +* The Reporter can create a CSV file or a TabularData object using a report +* configuration descriptor compliant with coherence-report-config.xsd or +* coherence-report-group-config.xsd. +* +* @author ew 2008.02.28 +* @since Coherence 3.4 +*/ +public class Reporter + extends Base + implements Constants + { + /** + * Run the specified report. + *
+    * Usage:
+    *   java com.tangosol.coherence.reporter.Reporter config-path [output-directory] [batchId]
+    * 
+ */ + public static void main(String[] asArg) + { + int cArgs = asArg.length; + if (cArgs == 0) + { + showUsage(); + return; + } + + String sConfigFile = asArg[0]; + String sOutputPath = cArgs > 1 ? asArg[1] : ""; + long lBatchId = cArgs > 2 ? Long.parseLong(asArg[2]) : 1; + + CacheFactory.ensureCluster(); + new Reporter().run(sConfigFile, sOutputPath, lBatchId, null, null); + } + + /** + * Generate the CSV file based on the the specified configuration. + * + * @param sReport the URI or the content of the report + * @param sPathTemplate the output path template + * @param lBatch a unique identifier for the report execution + * @param xmlParams the initialization parameters + */ + public void run(String sReport, String sPathTemplate, long lBatch, XmlElement xmlParams) + { + run(sReport, sPathTemplate, null, lBatch, xmlParams, null, true, false); + } + + /** + * Generate the CSV file based on the the specified configuration. + * + * @param sReport the URI or the content of the report + * @param sPathTemplate the output path template + * @param lBatch a unique identifier for the report execution + * @param xmlParams the initialization parameters + * @param loader the class loader that should be used to load + * report definition + */ + public void run(String sReport, String sPathTemplate, long lBatch, XmlElement xmlParams, ClassLoader loader) + { + run(sReport, sPathTemplate, null, lBatch, xmlParams, loader, true, false); + } + + /** + * Generate the CSV file and/or TabularData based on the the specified + * configuration. + * + * @param sReport the URI or the content of the report + * @param sPathTemplate the output path template + * @param sTabularType the typeName of the {@code TabularType} + * @param lBatch a unique identifier for the report execution + * @param xmlParams the initialization parameters + * @param loader the class loader that should be used to load + * report definition + * @param fReportFile true iff results should be written to a file + * @param fTabular true iff we generate and return the values as a tabular data + * + * @return a TabularData of the result if fTabular is set to true, null otherwise + */ + public TabularData run(String sReport, String sPathTemplate, String sTabularType, long lBatch, XmlElement xmlParams, + ClassLoader loader, boolean fReportFile, boolean fTabular) + { + setBatch(lBatch); + + TabularData tabData = null; + XmlElement xmlConfig = m_xml; + if (xmlConfig == null) + { + boolean fURI = isURI(sReport); + + XmlElement xmlReports = fURI + ? XmlHelper.loadFileOrResource(sReport, "Reporter configuration", loader) + : XmlHelper.loadXml(sReport); + + xmlReports = replaceParams(xmlReports, xmlParams); + xmlConfig = xmlReports.getElement(TAG_REPORT); + if (xmlConfig == null) + { + return null; + } + + setConfig(xmlConfig); + } + + XmlElement xmlQuery = xmlConfig.getElement(TAG_QUERY); + QueryHandler handler = ensureQueryHandler(xmlConfig, xmlQuery, lBatch); + + // Execute the query. + handler.execute(); + + Set setBeans = handler.getKeys(); + + if (setBeans.size() > 0) + { + if (!m_fInitialized) + { + initDisplayColumns(handler.isMultiTenant()); + m_fInitialized = true; + } + + if (fReportFile) + { + writeReportFile(sPathTemplate, handler); + } + + if (fTabular) + { + tabData = tabular(handler, sTabularType); + } + } + + // apply deltas and clean up + handler.postProcess(); + + return tabData; + } + + /** + * Write the contents of the provided handler into a file in the given path template. + * + * @param sPathTemplate the output path template + * @param handler the QueryHandler with the execution results + */ + protected void writeReportFile(String sPathTemplate, QueryHandler handler) + { + char cDelim = getDelim(); + String sDesc = getDescTemplate(); + File fileOut = getFile(sPathTemplate); + PrintStream streamOut = getPrintStream(fileOut); + List listDisplay = m_listDisplay; + + // Write out report description and headers for new output files + if (m_fHeaders && fileOut.length() == 0) + { + sDesc = replaceMacros(sDesc, null); + if (sDesc.length() > 0) + { + writeDescription(streamOut, sDesc); + } + writeHeader(streamOut, listDisplay, cDelim); + } + + boolean fDetail = false; // true if any column's value is row dependent + for (Iterator iterCol = listDisplay.iterator(); iterCol.hasNext();) + { + ReportColumnView columnView = (ReportColumnView) iterCol.next(); + if (columnView != null) + { + fDetail |= columnView.isRowDetail() && columnView.isVisible(); + } + } + + if (fDetail) + { + // write the details; + for (Iterator iter = handler.getGroupKeys().iterator(); iter.hasNext(); ) + { + writeDetail(streamOut, listDisplay, iter.next(), cDelim); + } + } + else + { + // Write the aggregates and constants only + Iterator iter = handler.getGroupKeys().iterator(); + + // group-keys are row identifiers; any row will do for aggregates + Object oFirstKey = iter.hasNext() + ? iter.next() + : null; + + writeDetail(streamOut, listDisplay, oFirstKey, cDelim); + } + streamOut.close(); + } + + /** + * Return the contents of the provided QueryHandler as a TabularData indexed by + * the handler's keys. + * + * @param handler the QueryHandler to transform into a TabularData + * @param sTabularType the typeName of the {@code TabularType} + * + * @return the TabularData with values from the QueryHandler + */ + protected TabularData tabular(QueryHandler handler, String sTabularType) + { + // create the TabularType for this report + List listDisplay = m_listDisplay; + int cVisibleCol = 0; + for (Iterator iterCol = listDisplay.iterator(); iterCol.hasNext();) + { + ColumnView columnView = (ColumnView) iterCol.next(); + if (columnView != null && columnView.isVisible()) + { + cVisibleCol++; + } + } + + int cTotalCols = cVisibleCol + 1; // number of visible columns + rowID column + String[] aColumnNames = new String[cTotalCols]; + String[] aColumnDesc = new String[cTotalCols]; + OpenType[] aOpenTypes = new OpenType[cTotalCols]; + int iCol = 0; + + aColumnNames[iCol] = "rowID"; + aColumnDesc[iCol] = "Unique Row ID"; + aOpenTypes[iCol++] = SimpleType.INTEGER; + + for (Iterator iterCol = listDisplay.iterator(); iterCol.hasNext();) + { + ColumnView columnView = (ColumnView) iterCol.next(); + if (columnView != null && columnView.isVisible()) + { + aColumnNames[iCol] = columnView.getId(); + aColumnDesc[iCol] = columnView.getDescription(); + aOpenTypes[iCol] = getSimpleType(columnView.getType()); + + iCol++; + } + } + + String sDesc = getDescTemplate(); + sDesc = sDesc == null || sDesc.length() == 0 ? sTabularType : sDesc; + + TabularType tabType; + + try + { + CompositeType rowType = new CompositeType(sTabularType, sDesc, aColumnNames, aColumnDesc, aOpenTypes); + + tabType = new TabularType(sTabularType, sDesc, rowType, aColumnNames); + } + catch (OpenDataException e) + { + throw Base.ensureRuntimeException(e); + } + + // create the TabularData for this report + TabularDataSupport tabDataSupport = new TabularDataSupport(tabType); + CompositeType rowType = tabDataSupport.getTabularType().getRowType(); + + try + { + int iRow = 1; + for (Iterator iterKey = handler.getGroupKeys().iterator(); iterKey.hasNext(); ) + { + Object oKey = iterKey.next(); + Map mapRowData = new HashMap(); + + for (Iterator iterCol = rowType.keySet().iterator(); iterCol.hasNext();) + { + String sColID = (String) iterCol.next(); + Object oValue = handler.getValue(oKey, sColID); + if (rowType.getType(sColID) == SimpleType.STRING) + { + // For arrays, simply return its stringified representation. + oValue = oValue == null ? null + : oValue.getClass().isArray() + ? Arrays.deepToString((Object[]) oValue) + : oValue.toString(); + } + mapRowData.put(sColID, oValue); + } + mapRowData.put("rowID", iRow); + + tabDataSupport.put(new CompositeDataSupport(rowType, mapRowData)); + iRow++; + } + } + catch (OpenDataException e) + { + throw Base.ensureRuntimeException(e); + } + + return tabDataSupport; + } + + /** + * Convert the given String identifier into the corresponding SimpleType. + * + * @param sType the name of the type + * + * @return the SimpleType for the type name, or SimpleType.STRING if an invalid name is given + */ + protected static SimpleType getSimpleType(String sType) + { + SimpleType type = MBeanHelper.SCALAR_SIMPLETYPES.get(sType); + return type == null ? SimpleType.STRING : type; + } + + /** + * Replace the system-properties and init-params in the XML. + * + * @param xmlReports the XML to modify + * @param xmlParams the init-params XML + * + * @return the report definition XML with system-property and init-param + * values + */ + public XmlElement replaceParams(XmlElement xmlReports, XmlElement xmlParams) + { + XmlHelper.replaceSystemProperties(xmlReports, "system-property"); + XmlElement xmlParseParams = new SimpleElement(); + if (xmlParams != null) + { + XmlHelper.transformInitParams(xmlParseParams, xmlParams); + } + replaceInitParams(xmlReports, xmlParseParams); + return xmlReports; + } + + /** + * Ensure ReportColumnViews. + */ + protected void initDisplayColumns(boolean fMultiTenant) + { + List listXmlCol = m_listXmlCol; + Map mapColumns = m_mapColumns; + List listDisplay = m_listDisplay; + + // Initialize ColumnViews. Column renames will override macros. + if (mapColumns.isEmpty()) + { + for (Iterator iterCol = listXmlCol.iterator(); iterCol.hasNext();) + { + XmlElement xmlColumn = (XmlElement) iterCol.next(); + ReportColumnView columnView = ensureDisplayColumn(replaceHidden(xmlColumn, !fMultiTenant)); + + listDisplay.add(columnView); + mapColumns.put(columnView.getId(), columnView); + } + + if (mapColumns.get(MACRO_BATCH) == null) + { + BatchView view = new BatchView(); + view.setQueryHandler(m_queryHandler); + mapColumns.put(MACRO_BATCH, view); + } + + if (mapColumns.get(MACRO_DATE) == null) + { + DateView view = new DateView(); + view.setQueryHandler(m_queryHandler); + mapColumns.put(MACRO_DATE, view); + } + + if (mapColumns.get(MACRO_NODE) == null) + { + mapColumns.put(MACRO_NODE, new NodeView()); + } + } + } + + /** + * Obtains a ReportDisplay instance based on the XML configuration. + * + * @param xmlColumn the column definition XML + * + * @return a ReportDisplay instance + */ + public ReportColumnView ensureDisplayColumn(XmlElement xmlColumn) + { + XmlValue xmlId = xmlColumn.getAttribute("id"); + XmlElement xmlColDef = xmlColumn; + String sId; + + if (xmlId != null) + { + sId = xmlId.getString(); + xmlColDef = getColumnById(xmlColumn, sId); + } + + ColumnView columnView = new ColumnView(); + columnView.setQueryHandler(m_queryHandler); + columnView.configure(xmlColDef); + columnView.setDateFormat(m_dateFormat); + return columnView; + } + + /** + * Returns mapColumns key for the given column XML. + * + * @param xmlColumn the XML definition which references the column id + * @param sId the string identifier to locate + * + * @return the id attribute of the column or the column configuration XML + */ + protected XmlElement getColumnById(XmlElement xmlColumn, String sId) + { + XmlElement xmlRow = getRowXml(xmlColumn); + List listXml = xmlRow.getElementList(); + for (Iterator i = listXml.iterator(); i.hasNext();) + { + XmlElement xmlSub = (XmlElement) i.next(); + String sTemp = xmlSub.getSafeAttribute("id").getString(); + if (sTemp.equals(sId)) + { + return xmlSub; + } + } + return null; + } + + /** + * Obtain the row xml for a given column. + * + * @param xml a column xml or column reference + * + * @return the XmlElement for the row + */ + protected static XmlElement getRowXml(XmlElement xml) + { + XmlElement xmlTemp = xml; + while (xmlTemp.getElement(TAG_ROW) == null) + { + xmlTemp = xmlTemp.getParent(); + if (xmlTemp == null) + { + return null; + } + } + return xmlTemp.getElement(TAG_ROW); + } + + /** + * Writes an array of strings to the output file. + * + * @param wps the output PrintStream + * @param sDesc the Description to be written + */ + protected void writeDescription(PrintStream wps, String sDesc) + { + wps.println(sDesc); + // without this line sometimes the following row is omitted. + wps.flush(); + } + + /** + * Writes the data from the list of columnViews to the file. + * + * @param wps the output PrintStream + * @param listColumn list of columns to be displayed + * @param oKey the row identifier + * @param cDelim the column delimiter + */ + protected void writeDetail(PrintStream wps, List listColumn, Object oKey, char cDelim) + { + int c = 0; + for (Iterator iCol = listColumn.iterator(); iCol.hasNext(); ) + { + ReportColumnView columnView = (ReportColumnView) iCol.next(); + if (columnView != null && columnView.isVisible()) + { + if (c != 0) + { + wps.print(cDelim); + } + c++; + + wps.print(columnView.getOutputString(oKey)); + } + } + wps.println(); + wps.flush(); + } + + /** + * Writes an array of strings to the output file. + * + * @param wps the output PrintStream + * @param listColumn list of columnViews to be displayed + * @param cDelim the column delimiter + */ + protected void writeHeader(PrintStream wps, List listColumn, char cDelim) + { + String sData; + int c = 0; + for (Iterator iCol = listColumn.iterator(); iCol.hasNext(); ) + { + ReportColumnView columnView = (ReportColumnView) iCol.next(); + if (columnView != null) + { + if (columnView.isVisible()) + { + if (c != 0) + { + wps.print(cDelim); + } + c++; + Object oResult = columnView.getHeader(); + sData = oResult == null ? "" : oResult.toString(); + wps.print(sData); + } + } + } + wps.println(); + // without this line sometimes the header is not printed correctly. + wps.flush(); + } + + /** + * Return the first part of a '/' delimited string. + * + * @param sKey a '/' delimited path string for composite data + * + * @return the first part of the path + */ + public String currentKey(String sKey) + { + if (sKey.length() == 0) + { + return ""; + } + + String[] arKey = Base.parseDelimitedString(sKey, '/'); + + return arKey[0]; + } + + /** + * Extract the column delimiter from the report configuration. + * + * @return the column delimiter character + */ + protected char getDelim() + { + char cDelim = m_cDelim; + if (cDelim == Character.UNASSIGNED) + { + String sDelim = m_xml.getSafeElement(TAG_DELIM).getString(VALUE_TAB); + + m_cDelim = cDelim = sDelim.equals(VALUE_TAB) + ? '\t' + : sDelim.equals(VALUE_SPACE) ? ' ' : sDelim.charAt(0); + } + + return cDelim; + } + + /** + * Obtain the report description template. + * + * @return the report description template + */ + public String getDescTemplate() + { + return m_sDescTemplate; + } + + /** + * Obtain the Map of existing locator for the Reporter keyed by column id. + * + * @return the Map of locator + */ + public Map ensureColumnMap() + { + Map mapColumns = m_mapColumns; + if (mapColumns == null) + { + m_mapColumns = mapColumns = new HashMap(); + } + return mapColumns; + } + + /** + * Obtain the report batch. + * + * @return the report batch number + */ + public long getBatch() + { + return m_lBatch; + } + + /** + * Set the report batch. + * + * @param lBatch the report batch number + */ + public void setBatch(long lBatch) + { + m_lBatch = lBatch; + } + + /** + * Obtain the list of locator for the Reporter. + * + * @return the List of locator + */ + protected List ensureDisplayList() + { + if (m_listDisplay == null) + { + m_listDisplay = new LinkedList(); + } + return m_listDisplay; + } + + /** + * Determine the output file name and create the file. + * + * @param sPathTempl the output path template + * + * @return the output file for the report + */ + protected File getFile(String sPathTempl) + { + try + { + String sFileTempl = m_sFileTempl; + String sFileName = replaceMacros( + getFileTemplate(sFileTempl, sPathTempl), null); + + if (sFileName.length() == 0) + { + log("Report writer:No output file specified. Report terminated"); + return null; + } + + File fOut = new File(sFileName); + if (!fOut.exists()) + { + fOut.createNewFile(); + } + + return fOut; + } + catch (IOException e) // FileNoteFoundException + { + throw ensureRuntimeException(e, "Invalid or unable to create output file."); + } + } + + /** + * Determine the output file name, create the file and write the column headers. + * + * @param fOut the output File + * + * @return the output file for the report + */ + protected PrintStream getPrintStream(File fOut) + { + try + { + OutputStream os = new FileOutputStream(fOut, true); + return new PrintStream(new BufferedOutputStream(os)); + } + catch (FileNotFoundException e) + { + // This error will never occur. The file will be created. + throw ensureRuntimeException(e); + } + } + + /** + * Get the output file template. + * + * @param sTemplate the filename template + * @param sPath the path for the file + * + * @return the output file name + */ + protected String getFileTemplate(String sTemplate, String sPath) + { + String sFileName = sTemplate; + + if (sFileName.length() == 0) + { + log("Report writer:No output file specified. Report terminated"); + return null; + } + + try + { + sFileName = new File(sPath).getCanonicalPath() + File.separatorChar + sFileName; + } + catch (IOException e) + { + // leave the problem for caller + sFileName = sPath + sFileName; + } + + return sFileName; + } + + /** + * Set the {@link DateFormat} that will be used when formatting date columns. + * + * @param df the {@link DateFormat} + */ + public void setDateFormat(DateFormat df) + { + m_dateFormat = df; + } + + /** + * Output usage instructions. + */ + public static void showUsage() + { + out(); + out("java com.tangosol.coherence.reporter.Reporter [[ []]"); + out(); + out("command option descriptions:"); + out("\t the file containing the report configuration XML"); + out(); + out("\t (optional) the path where the output files are created."); + out("\t(default is the current directory)"); + out(); + out("\t (optional) a long indentifier for the report execution."); + out("\tThe batchId is useful when differientiating data from different"); + out("\texecutions contained in a single file"); + out("\t (default is 1)"); + out(); + } + + + /** + * Return true if the {@code sConfigFileName} is a URI containing a report config file + * rather than a String containing the XML for the report. + * + * @param sConfigFileName the config file or XML + * + * @return true if the config file name is a URI + */ + protected static boolean isURI(String sConfigFileName) + { + try + { + new URI(sConfigFileName); + return true; + } + catch (URISyntaxException e) + { + return false; + } + } + + /** + * Replace the column macro in the hidden attribute. + * + * @param xml the XmlElement to replace the macro + * @param fValue whether the attribute should be hidden + * + * @return the XmlElement after the macro is replaced + */ + protected static XmlElement replaceHidden(XmlElement xml, boolean fValue) + { + Set setMacros = Reporter.getMacros(xml.toString()); + + for (Iterator iter = setMacros.iterator(); iter.hasNext();) + { + String sId = (String) iter.next(); + if (sId.equals(MACRO_NONMT)) + { + xml.getElement(TAG_HIDDEN).setString(String.valueOf(fValue)); + } + } + return xml; + } + + /** + * Replace string macros in the template with the associated values from + * the source object (source). + * + * @param sTemplate the template contain the macros + * @param source the MBean source for the replacement values + * + * @return a string based on the Template with JMX values included + */ + protected String replaceMacros(String sTemplate, Object source) + { + String sRet = sTemplate; + Set setMacros = Reporter.getMacros(sTemplate); + + for (Iterator i = setMacros.iterator(); i.hasNext();) + { + String sId = (String) i.next(); + ReportColumnView columnView = (ReportColumnView) m_mapColumns.get(sId); + Object oValue = columnView.getOutputString(source); + if (columnView != null && oValue != null) + { + sRet = sRet.replaceAll(MACRO_START + sId + MACRO_STOP, + oValue.toString()); + } + } + return sRet; + } + + /** + * Determine if the string contains any macros, without sanity checking the macro string. + * + * @param sTemplate String containing column reference identifiers. + * these identifiers must be with in curly braces {} + * + * @return the set of strings contained with {} + */ + public static Set getMacros(String sTemplate) + { + Set listRet = new HashSet(); + StringTokenizer st = new StringTokenizer(sTemplate, "{}", true); + while (st.hasMoreTokens()) + { + String sToken = st.nextToken(); + if (sToken.equals("{")) + { + String sAttrib = st.nextToken(); + listRet.add(sAttrib); + sToken = st.nextToken(); + if (!sToken.equals("}")) + { + // Error - un-matched {} + Base.log("Error processing string \"" + sTemplate + "\". Unmatched brace {." ); + return null; + } + } + } + return listRet; + } + + /** + * Parse a string and get the defaults from the included macros. + * + * @param sTemplate a string containing {macros defaults} + * + * @return a set keyed by the macro name with the value of the default + */ + public static Set getMacroDefaults(String sTemplate) + { + Map mapResults = new HashMap(); + int ofStart = sTemplate.indexOf('{'); + int ofEnd = -1; + + while (ofStart >= 0) + { + ofEnd = sTemplate.indexOf('}', ofStart); + if (ofEnd < 0) + { + CacheFactory.log("Invalid attribute format: " + + sTemplate, CacheFactory.LOG_ERR); + break; + } + + String sAttrName = sTemplate.substring(ofStart + 1, ofEnd); // "name value" + String sDefault = null; + int ofDefault = sAttrName.indexOf(' '); + if (ofDefault > 0) + { + sDefault = sAttrName.substring(ofDefault + 1).trim(); + sAttrName = sAttrName.substring(0, ofDefault); + } + mapResults.put(sAttrName, sDefault); + ofStart = sTemplate.indexOf('{', ofEnd); + } + return mapResults.entrySet(); + } + + /** + * Convert the column string into the internal representation. + * + * @param sType the string representation of the column type + * + * @return the internal representation of the column type + */ + protected static int columnFromString(String sType) + { + if (sType.equals(VALUE_GLOBAL)) + { + return COL_GLOBAL; + } + else if (sType.equals(VALUE_COLCALC)) + { + return COL_CALC; + } + else if (sType.equals(VALUE_METHOD)) + { + return COL_METHOD; + } + else if (sType.equals(VALUE_KEY)) + { + return COL_KEY; + } + else if (sType.equals("")) + { + return COL_ATTRIB; + } + else if (sType.equals(VALUE_ATTRIB)) + { + return COL_ATTRIB; + } + return COL_ERR; + } + + /** + * Configure the Reporter. + * + * @param xmlReportCfg the report configuration XML + */ + public void setConfig(XmlElement xmlReportCfg) + { + if (m_xml == null) + { + m_xml = xmlReportCfg; + + // Configuration Declarations + XmlElement xmlRow = xmlReportCfg.getElement(TAG_ROW); + m_sDescTemplate = xmlReportCfg.getSafeElement(Reporter.TAG_DESC).getString(); + m_fHeaders = !xmlReportCfg.getSafeElement(Reporter.TAG_HEADERS).getBoolean(false); + m_listXmlCol = xmlRow.getElementList(); + m_sFileTempl = xmlReportCfg.getSafeElement(TAG_FILENAME).getString(); + } + } + + /** + * Replace the Initialization "macros" with the initialization parameters. + * + * @param xml the XML containing the macros + * @param xmlParams the initialization parameter + */ + public static void replaceInitParams(XmlElement xml, XmlElement xmlParams) + { + String sTemplate = xml.getString(); + if (sTemplate != null && sTemplate.length() > 0) + { + String sRet = sTemplate; + Set setMacros = Reporter.getMacroDefaults(sTemplate); + + // set the element's value from the specified system property + for (Iterator i = setMacros.iterator(); i.hasNext();) + { + Map.Entry entry = (Map.Entry) i.next(); + String sMacro = (String) entry.getKey(); + Object oDefault = entry.getValue(); + String sDefault = (oDefault == null) ? null: oDefault.toString(); + String sValue = (xmlParams == null) ? null : + xmlParams.getSafeElement(sMacro).getString(); + String sId = (sDefault == null) ? sMacro : sMacro + " " + sDefault; + if (sValue != null && sValue.length() > 0) + { + sRet = sRet.replaceAll(MACRO_START + sId + MACRO_STOP, sValue); + } + else + { + if (sDefault != null) + { + sRet = sRet.replaceAll(MACRO_START + sId + MACRO_STOP, sDefault); + } + } + } + if (!sRet.equals(sTemplate)) + { + xml.setString(sRet); + } + } + + // iterate for each contained element + for (Iterator iter = xml.getElementList().iterator(); iter.hasNext();) + { + replaceInitParams((XmlElement) iter.next(), xmlParams); + } + } + + /** + * Get the XML used to configure the reporter. + * + * @return an XmlElement containing the parsed results of the configuration + * file + */ + public XmlElement getConfig() + { + return m_xml; + } + + /** + * Create/return the QueryHandler for the Report. + * + * @param xmlConfig the report configuration + * @param xmlQuery the query to be executed + * @param lBatch the batch identifier for the query + * + * @return the QueryHandler for the Reporter + */ + public QueryHandler ensureQueryHandler(XmlElement xmlConfig, XmlElement xmlQuery, long lBatch) + { + QueryHandler qh = m_queryHandler; + if (qh == null) + { + qh = new JMXQueryHandler(); + qh.setContext(xmlQuery, xmlConfig); + m_queryHandler = qh; + } + + ((JMXQueryHandler)qh).setBatch(lBatch); + + return qh; + } + + + //----- static mthods --------------------------------------------------- + + /** + * Determine the key for the m_mapColumns given column XML. + * + * @param xmlColumn the column definition XML + * + * @return the id attribute of the column or the column configuration XML + */ + protected static Object getColumnKey(XmlElement xmlColumn) + { + XmlValue xmlTemp = xmlColumn.getAttribute("id"); + if (xmlTemp == null) + { + return xmlColumn; + } + return xmlTemp.getString(); + } + + /** + * Compare two String or Number objects. + * + * @param o1 first Object + * @param o2 second Object + * + * @return 0 when o1 = o2, -1 when o1 < o2, 1 when o1 > o2 + */ + public static int compare(Object o1, Object o2) + { + return o1 instanceof Comparable && o2 instanceof Comparable ? + ((Comparable) o1).compareTo(o2) : + String.valueOf(o1).compareTo(String.valueOf(o2)); + } + + + //----- data members ---------------------------------------------------- + + /** + * A list of all Display Objects, including those not visible. + */ + protected List m_listDisplay = new LinkedList(); + + /** + * DateFormat used to display Date columns. + */ + protected DateFormat m_dateFormat; + + /** + * A list of all XML Column Definition elements to convert into columnViews for the report. + */ + protected List m_listXmlCol; + + /** + * A Map from column ID to columnView for the Reporter. + */ + protected Map m_mapColumns = new HashMap(); + + /** + * Used as a Description Template for the report description. + */ + protected String m_sDescTemplate; + + /** + * The batch id for the report execution. + */ + public long m_lBatch; + + /** + * The configuration XML. + */ + protected XmlElement m_xml; + + /** + * The column delimiter. + */ + protected char m_cDelim; + + /** + * The headers flag - true to print headers, false to hide headers. + */ + protected boolean m_fHeaders; + + /** + * The filename template. + */ + protected String m_sFileTempl; + + /** + * The Query Handler for the report. + */ + protected QueryHandler m_queryHandler; + + /** + * true if the report has run once before + */ + protected boolean m_fInitialized; + + + //----- Constants -------------------------------------------------------- + + /** + * A static Map between the filter XML definition and the implementation class + * name. + */ + public static Map m_mapColumnClass = new HashMap(); + + static + { + String sBase = "com.tangosol.coherence.reporter."; + m_mapColumnClass.put(VALUE_ATTRIB, sBase + "AttributeColumn"); + m_mapColumnClass.put(VALUE_SUBQUERY, sBase + "SubQueryColumn"); + m_mapColumnClass.put(VALUE_CONSTANT, sBase + "ConstantColumn"); + m_mapColumnClass.put(VALUE_PROPERTY, sBase + "PropertyColumn"); + m_mapColumnClass.put(VALUE_GLOBAL + "," + VALUE_BATCH, sBase + "BatchColumn"); + m_mapColumnClass.put(VALUE_GLOBAL + "," + VALUE_TIME, sBase + "DateTimeColumn"); + m_mapColumnClass.put(VALUE_KEY, sBase + "KeyColumn"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_SUM, sBase + "SumColumn"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_COUNT, sBase + "CountColumn"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_AVG, sBase + "AverageColumn"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_MIN, sBase + "MinColumn"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_MAX, sBase + "MaxColumn"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_DELTA, sBase + "DeltaColumn"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_DIVIDE, sBase + "DivideSource"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_ADD, sBase + "AddSource"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_SUB, sBase + "SubtractSource"); + m_mapColumnClass.put(VALUE_FUNC + "," + VALUE_MULTI, sBase + "MultiplySource"); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/AddExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/AddExtractor.java new file mode 100755 index 0000000000000..2827c85ee77ee --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/AddExtractor.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.util.Base; +import com.tangosol.util.extractor.MultiExtractor; +import com.tangosol.util.ImmutableArrayList; +import com.tangosol.util.ValueExtractor; + + +/** +* MultiExtractor implementation to add two ValueExtractors and extract the result. +* +* @author ew 2008.02.28 +* @since Coherence 3.4 +*/ +public class AddExtractor + extends MultiExtractor + { + // ------ constructors --------------------------------------------------- + + /** + * Construct an AddExtractor. + * + * @param aExtractor An array of ValueExtractors. The results of the + * first two extractors will be added. If more + * than two (2) extractors are passed, the excess + * extractors will be ignored. + */ + public AddExtractor(ValueExtractor[] aExtractor) + { + super(aExtractor); + Base.azzert(aExtractor.length == 2, "Report addition requires " + + "two and only two arguments."); + + } + + // ----- ValueExtractor interface ---------------------------------------- + + /** + * @inheritDoc + */ + public Object extract(Object oTarget) + { + ImmutableArrayList arResults = (ImmutableArrayList)super.extract(oTarget); + if (arResults.size() > 1) + { + Object o1 = arResults.get(0); + Object o2 = arResults.get(1); + if (o1 instanceof String || o2 instanceof String) + { + return o1.toString() + o2.toString(); + } + else if (o1 instanceof Number && o2 instanceof Number) + { + return new Double(((Number)o1).doubleValue() + + ((Number)o2).doubleValue()); + } + } + + return null; + } + +} diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/AggregateExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/AggregateExtractor.java new file mode 100755 index 0000000000000..719ef78b18789 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/AggregateExtractor.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.coherence.reporter.DataSource; +import com.tangosol.util.ValueExtractor; + +/** +* Extractor wrapper class for aggregates. +* +* @author ew 2008.03.17 +* @since Coherence 3.4 +*/ +public class AggregateExtractor + implements ValueExtractor + { + // ----- constructors ---------------------------------------------------- + /** + * Contruct an extractor for an aggregate result. + * + * @param source the data source of the aggregate + * @param AggregateNdx the aggregate index in the data source. + */ + public AggregateExtractor(DataSource source, int AggregateNdx) + { + m_source = source; + m_iAggregateNdx = AggregateNdx; + } + + // ----- ValueExtractor interface ---------------------------------------- + /** + * @inheritDoc + */ + public Object extract(Object oTarget) + { + return m_source.getAggValue(oTarget, m_iAggregateNdx); + } + + // ----- data members ---------------------------------------------------- + /* + * the data source for the aggregate + */ + protected DataSource m_source; + + /* + * the location of the aggregate in the data source. + */ + protected int m_iAggregateNdx; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/AttributeExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/AttributeExtractor.java new file mode 100755 index 0000000000000..79331ba021fc2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/AttributeExtractor.java @@ -0,0 +1,340 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.coherence.reporter.Constants; + +import com.tangosol.net.management.MBeanHelper; + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +import java.util.Map; + +import javax.management.MBeanServer; +import javax.management.ObjectName; +import javax.management.Attribute; +import javax.management.InstanceNotFoundException; +import javax.management.MBeanException; +import javax.management.AttributeNotFoundException; +import javax.management.ReflectionException; +import javax.management.openmbean.CompositeData; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; + + +/** +* MBean Attribute ValueExtractor implementation. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class AttributeExtractor + implements ValueExtractor, Constants + { + + // ----- constructors ---------------------------------------------------- + + /** + * Construct a AttributeExtractor based on a Attribute name. + * + * @param sAttribute the name of the attribute to extract + * @param cDelim the delimiter for array attributes + * @param fReturnNeg a flag that allows for the return of negative values + * By default negative numbers are returned as zero + */ + public AttributeExtractor(String sAttribute, char cDelim, boolean fReturnNeg) + { + this(sAttribute, cDelim, fReturnNeg, MBeanHelper.findMBeanServer()); + } + + /** + * Construct a AttributeExtractor based on a Attribute name. + * + * @param sAttribute the name of the attribute to extract + * @param cDelim the delimiter for array attributes + * @param fReturnNeg a flag that allows for the return of negative values + * By default negative numbers are returned as zero + * @param server the {@link MBeanServer} to query against + */ + public AttributeExtractor(String sAttribute, char cDelim, boolean fReturnNeg, MBeanServer server) + { + super(); + m_sAttribute = sAttribute; + m_cDelim = cDelim; + m_fReturnNeg = fReturnNeg; + f_mbs = server; + } + + // ----- ValueExtractor interface ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Object extract(Object oTarget) + { + try + { + ObjectName oJoinName = (ObjectName) oTarget; + + String sAttribName = m_sAttribute; + String sCurrent = currentKey(sAttribName); + Object oData = f_mbs.getAttribute(oJoinName, sCurrent); + Object oReturn; + + if (oData == null) + { + return DEFAULT_VALUE; + } + + if (!(sCurrent.equals(sAttribName))) + { + oData = getValue(nextKey(sAttribName), oData); + } + + if (oData == null) + { + oReturn = DEFAULT_VALUE; + } + else + { + oReturn = getValue(sAttribName, oData); + if (m_fReturnNeg) + { + return oReturn; + } + else + { + if (oReturn instanceof Number && + ((Number) oReturn).doubleValue() < 0) + { + if (oReturn instanceof Long || + oReturn instanceof Integer) + { + return Long.valueOf(0); + } + else + { + return new Double(0.0); + } + } + } + } + + return oReturn; + } + catch (InstanceNotFoundException e) + { + // Exceptions will occure when nodes are removed from the grid after + // query and before the data is extracted. The Default 'n/a' + // will be returned when this occurs. + // Return Default + } + catch (MBeanException e) + { + // Return Default + } + catch (AttributeNotFoundException e) + { + // Return Default + } + catch (ReflectionException e) + { + // Return Default + } + return DEFAULT_VALUE; + } + + // ----- helpers --------------------------------------------------------- + + /** + * Recursively inspects JMC composite data and extract the value in the sKey + * path. + * + * @param sKey the key path to the data. sKey is a '\' delimited + * string of Composite data names. + * @param oData the composite data attribute to extract the value + * + * @return the information located in the sKey path + */ + protected Object getValue(String sKey, Object oData) + { + String sName = currentKey(sKey); + char cSubDelim = m_cDelim; + + if (sName.length() == 0) return oData; + + if (oData instanceof CompositeData) + { + CompositeData oCompData = (CompositeData) oData; + Object oValue = oCompData.get(sName); + return getValue(nextKey(sKey), oValue); + } + + else if (oData instanceof Attribute) + { + return getValue(nextKey(sName), ((Attribute) oData).getValue()); + } + + else if (oData instanceof TabularData) + { + TabularDataSupport oTabData = (TabularDataSupport) oData; + String[] asKey = {sName}; + Object cdData = oTabData.get(asKey); + return getValue(nextKey(sKey), cdData); + } + + else if (oData instanceof Object[]) + { + Object[] aoData = (Object[]) oData; + for (int c = 0; c < aoData.length; c++) + { + if (aoData[c] instanceof Attribute) + { + Attribute a = (Attribute) aoData[c]; + String sAttribName = a.getName(); + if (sAttribName.equals(sName)) + { + Object oValue = a.getValue(); + return getValue(nextKey(sKey), oValue); + } + } + } + } + + else if (oData instanceof Map) + { + Map mapData = (Map) oData; + Object oValue = mapData.get(sName); + return getValue(nextKey(sKey), oValue); + } + + else if (oData instanceof long[]) + { + long[] anData = (long[]) oData; + int nSize = anData.length; + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < nSize; i++) + { + if (i != 0) + { + sb.append(cSubDelim); + } + sb.append(anData[i]); + } + return sb.toString(); + } + + else if (oData instanceof int[]) + { + int[] anData = (int[]) oData; + int nSize = anData.length; + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < nSize; i++) + { + if (i != 0) + { + sb.append(cSubDelim); + } + sb.append(anData[i]); + } + return sb.toString(); + } + + else if (oData instanceof double[]) + { + double[] anData = (double[]) oData; + int nSize = anData.length; + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < nSize; i++) + { + if (i != 0) + { + sb.append(cSubDelim); + } + sb.append(anData[i]); + } + return sb.toString(); + } + + else + { + if (oData == null) + { + oData = ""; + } + } + + return oData; + } + + /** + * return the first part of a '/' delimited string + * + * @param sKey a '/' delimited path string for composite data. + * + * @return the first part of the path. + */ + public static String currentKey(String sKey) + { + if (sKey.length() == 0) return ""; + return Base.parseDelimitedString(sKey, '/')[0]; + } + + /** + * Remove the first part of the key path and return the rest of the string. + * + * @param sKey a '/' delimited key path string to access composite data + * + * @return sKey after removing the first part of the key + */ + public static String nextKey(String sKey) + { + String[] asKey = Base.parseDelimitedString(sKey, '/'); + String sRet = ""; + long lLen = asKey.length; + for (int c = 1; c < lLen; c++) + { + sRet += asKey[c]; + if (c != lLen - 1) + { + sRet += "/"; + } + } + return sRet; + } + + //----- data members ---------------------------------------------------- + + /** + * The {@link MBeanServer} to query against. + */ + protected final MBeanServer f_mbs; + + /** + * A JMX ObjectName string containing value replacement macros The macros will + * be replaced with data from column information during runtime. + */ + protected String m_sJoinTemplate; + + /** + * The Attribute name string to be extracted. + */ + protected String m_sAttribute; + + /** + * The column delimiter for string values. + */ + protected char m_cDelim; + + /** + * flag allow the return of a negative number + */ + protected boolean m_fReturnNeg = false; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/ConstantExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/ConstantExtractor.java new file mode 100755 index 0000000000000..c1b1c027ad1d8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/ConstantExtractor.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.util.ValueExtractor; + +/** +* Generic extractor for constant values. +* +* @author ew 2008.03.17 +* @since Coherence 3.4 +*/ +public class ConstantExtractor + implements ValueExtractor + { + // ----- constructors ---------------------------------------------------- + + /** + * Construct an Extractor for the Value. + * + * @param oValue the value to be extracted. + */ + public ConstantExtractor(Object oValue) + { + m_oConstant = oValue; + } + + // ----- ValueExtractor interface ---------------------------------------- + + /** + * {@inheritDoc} + */ + public Object extract(Object oTarget) + { + return m_oConstant; + } + + //----- data members ----------------------------------------------------- + + /** + * The constant value of the extractor + */ + protected Object m_oConstant; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/CorrelatedExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/CorrelatedExtractor.java new file mode 100755 index 0000000000000..33e93647b77e4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/CorrelatedExtractor.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.util.ValueExtractor; + +/** +* ValueExtractor to extract a fixed key value extractor in a query. +* +* @author ew 2008.03.17 +* @since Coherence 3.4 +*/ +public class CorrelatedExtractor + implements ValueExtractor + { + //----- Constructors ----------------------------------------------------- + /** + * Construct an extractor that can have the target object "fixed" for the + * duration of a query. + * + * @param veSource the source extractor. + */ + public CorrelatedExtractor(ValueExtractor veSource) + { + m_veSource = veSource; + } + + //----- ValueExtractor interface ----------------------------------------- + /** + * @inheritDoc + */ + public Object extract(Object oTarget) + { + if (m_oTarget == null) + { + return m_veSource.extract(oTarget); + } + else + { + return m_veSource.extract(m_oTarget); + } + } + + //----- accessors -------------------------------------------------------- + /** + * Set the fixed target for the accessor. + * + * @param oTarget the fixed target for the wapped Extractor + */ + public void setTarget(Object oTarget) + { + m_oTarget = oTarget; + } + + //----- data members ----------------------------------------------------- + /** + * the fixed target key. + */ + protected Object m_oTarget; + + /** + * the related ValueExtractor + */ + protected ValueExtractor m_veSource; + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/DeltaExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/DeltaExtractor.java new file mode 100755 index 0000000000000..a15408d275015 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/DeltaExtractor.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.util.ValueExtractor; + +import java.util.Map; +import java.util.Set; +import java.util.Iterator; + + +/** +* ValueExtractor implementation to extract the difference between the current +* extracted value and the prior extracted value. This will always return +* non-negative values. If the prior value is greater than the current value, +* the current value is returned. +* +* @author ew 2008.03.10 +* @since Coherence 3.4 +*/ +public class DeltaExtractor + implements ValueExtractor + { + // ----- constructors ---------------------------------------------------- + + /** + * Construct a DeltaExtractor + * + * @param mapPrior A map of the prior values. The key is determined by the + * mapKey and the value is the prior value. + * @param veSource the source ValueExtractor. + * @param mapKey A map of ValueExtractors used to determine the key for + * the prior map (mapPrior). If mapKey is null, the prior + * value key will be the extraction target. + */ + public DeltaExtractor(Map mapPrior, ValueExtractor veSource, Map mapKey) + { + m_veSource = veSource; + m_mapPrior = mapPrior; + m_mapKey = mapKey; + } + + + // ----- ValueExtractor interface ---------------------------------------- + + /** + * @inheritDoc + */ + public Object extract(Object oTarget) + { + Object oValue = m_veSource.extract(oTarget); + Object oPrior = m_mapPrior.get(getKey(oTarget)); + if (oPrior == null) + { + return oValue; + } + + if (oValue instanceof Number && oPrior instanceof Number) + { + Double dValue = ((Number) oValue).doubleValue(); + Double dPrior = ((Number) oPrior).doubleValue(); + return dValue < dPrior ? dValue : dValue - dPrior; + } + + return null; + } + + + // ----- helpers --------------------------------------------------------- + + /** + * Determine the key for the prior map based on the + * + * @param oTarget the MBean Object name to convert to the internal delta key. + * + * @return the internal delta key + */ + public Object getKey(Object oTarget) + { + Map mapKey = m_mapKey; + + // Most likely instance. + if (mapKey == null || mapKey.size() == 0) + { + return oTarget; + } + else + { + Set setKey = mapKey.entrySet(); + StringBuffer sb = new StringBuffer(); + for (Iterator iter = setKey.iterator(); iter.hasNext();) + { + Map.Entry entry = (Map.Entry) iter.next(); + String oKey = (String) entry.getKey(); + Object oValue = ((ValueExtractor) entry.getValue()).extract(oTarget); + String sValue = oValue == null ? "n/a" : oValue.toString(); + sb.append(oKey).append('=').append(sValue).append(','); + } + return sb.toString(); + } + } + + + // ----- data members ---------------------------------------------------- + /** + * The value extractor that will provide the values for the delta + */ + protected ValueExtractor m_veSource; + + /** + * A Map containing the prior values for the source + */ + protected Map m_mapPrior; + + /** + * A Map containing the aggregate keys for each target + */ + protected Map m_mapKey; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/DivideExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/DivideExtractor.java new file mode 100755 index 0000000000000..754e3761e6da3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/DivideExtractor.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.util.Base; +import com.tangosol.util.extractor.MultiExtractor; +import com.tangosol.util.ImmutableArrayList; +import com.tangosol.util.ValueExtractor; + + +/** +* MultiExtractor extension to divide the results of the first ValueExtractor +* by the results of the second ValueExtractor. If the demominator is zero (0) +* or not numeric, null will be returned. +* +* @author ew 2008.02.20 +* @since Coherence 3.4 +*/ +public class DivideExtractor + extends MultiExtractor + { + // ----- constructors ---------------------------------------------------- + + /** + * Construct a DivideExtractor. + * + * @param aExtractor An array of ValueExtractors. The results of the first + * extractor will be divided by the results of the + * second. If more than two (2) extractors are passed. + * the excess extractors will be ignored. If less than + * two (2) extractors null will be returned. + */ + public DivideExtractor(ValueExtractor[] aExtractor) + { + super(aExtractor); + Base.azzert(aExtractor.length == 2, "Report division requires " + + "two and only two arguments."); + + } + + // ----- ValueExtractor interface ---------------------------------------- + + /** + * @inheritDoc + */ + public Object extract(Object oTarget) + { + ImmutableArrayList arResults = (ImmutableArrayList)super.extract(oTarget); + if (arResults.size() > 1) + { + Object o1 = arResults.get(0); + Object o2 = arResults.get(1); + + if (o2 == null || ((Number) o2).doubleValue() == 0) + { + return null; + } + else if (o1 instanceof Number && o2 instanceof Number) + { + return new Double(((Number) o1).doubleValue() + / ((Number) o2).doubleValue()); + } + } + return null; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/JoinExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/JoinExtractor.java new file mode 100755 index 0000000000000..b2a291d556dd5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/JoinExtractor.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + +import com.tangosol.coherence.reporter.Constants; +import com.tangosol.coherence.reporter.Reporter; + +import com.tangosol.net.management.MBeanHelper; + +import com.tangosol.util.extractor.MultiExtractor; +import com.tangosol.util.ValueExtractor; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + + +/** +* MultiExtractor implementation to return the results from a related object +* instead of the target extraction. +* +* @author ew 2008.02.20 +* @since Coherence 3.4 +*/ +public class JoinExtractor + extends MultiExtractor + implements Constants + { + // ----- constructors ---------------------------------------------------- + /** + * Construct a JoinExtractor + * + * @param aExtractors an array of value extractors used to map the + * JoinTemplate to the new target. The extractors + * will replace the the JoinTemplate {macros} in order + * @param sJoinTemplate a "macro-ized" string that represents the new target + * key + * @param veSource the new source ValueExtractor + */ + public JoinExtractor(ValueExtractor[] aExtractors, String sJoinTemplate, + ValueExtractor veSource) + { + this(aExtractors, sJoinTemplate, veSource, MBeanHelper.findMBeanServer()); + } + + /** + * Construct a JoinExtractor + * + * @param aExtractors an array of value extractors used to map the + * JoinTemplate to the new target. The extractors + * will replace the the JoinTemplate {macros} in order + * @param sJoinTemplate a "macro-ized" string that represents the new target + * key + * @param veSource the new source ValueExtractor + * @param server the {@link MBeanServer} to query + */ + public JoinExtractor(ValueExtractor[] aExtractors, String sJoinTemplate, + ValueExtractor veSource, MBeanServer server) + { + super(aExtractors); + + m_sJoinTemplate = sJoinTemplate; + m_veSource = veSource; + f_mbs = server; + } + + // ----- ValueExtractor interface ---------------------------------------- + + /** + * @inheritDoc + */ + public Object extract(Object oTarget) + { + List listResult = (List) super.extract(oTarget); + Set set = Reporter.getMacros(m_sJoinTemplate); + String sJoinTarget = m_sJoinTemplate; + int index = 0; + + for (Iterator iter = set.iterator(); iter.hasNext();) + { + String sId = (String)iter.next(); + + // by convention the results and the macro count are equal. + // this is done in the AttributeLocator + Object oValue = listResult.get(index); + + if (oValue != null) + { + // The replaceAll is to work around a Bug with replaceAll/regex + // There is a Matcher method quoteReplacement in 1.5+ + // for 1.4 compatiblity the replace all escape sequences the $ + sJoinTarget = sJoinTarget.replaceAll(MACRO_START + sId + MACRO_STOP, + oValue.toString().replaceAll("\\$", "\\\\\\$")); + } + index++; + } + + try + { + ObjectName onameTarget = new ObjectName(sJoinTarget); + if (sJoinTarget.contains("*")) + { + // Wild card in ObjectName. Need to resolve the actual ObjectName. + Set setNames = f_mbs.queryNames(onameTarget, null); + // We only support join with a single target. Else we pass the unresolved object name + // and let the value extractor handle it. + onameTarget = setNames.size() == 1 ? setNames.iterator().next() : onameTarget; + } + return m_veSource.extract(onameTarget); + } + catch (MalformedObjectNameException e) + { + // the join definition failed to create a valid object. + } + return null; + } + + // ----- data members ---------------------------------------------------- + + /** + * The {@link MBeanServer} this ValueExtractor operates against. + */ + protected final MBeanServer f_mbs; + + /* + * a macro-ized string used to map the related object. + */ + protected String m_sJoinTemplate; + + /* + * the "wrapped" ValueExtractor. + */ + protected ValueExtractor m_veSource; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/KeyExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/KeyExtractor.java new file mode 100755 index 0000000000000..1b420a3a6e27c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/KeyExtractor.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +import javax.management.ObjectName; + +/** +* ValueExtractor implmentation to extract part of the MBean ObjectName. +* +* @author ew 2008.02.20 +* @since Coherence 3.4 +*/ +public class KeyExtractor + implements ValueExtractor + { + // ----- constructors ---------------------------------------------------- + + /** + * Construct a KeyExtractor for the name "part" + * + * @param sName the name of the ObjectName part to extract. + */ + public KeyExtractor(String sName) + { + m_sKey = sName; + } + + // ----- ValueExtractor interface ---------------------------------------- + + /** + * @inheritDoc + */ + public Object extract(Object oTarget) + { + Base.azzert(oTarget instanceof ObjectName, "KeyExtractor only applies " + + "to MBean ObjectName"); + + // the returned value is a String + return ((ObjectName) oTarget).getKeyPropertyList().get(m_sKey); + } + + /* + * the key part to extract. + */ + protected String m_sKey; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/MultiplyExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/MultiplyExtractor.java new file mode 100755 index 0000000000000..a8ba05eeb0085 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/MultiplyExtractor.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.util.Base; +import com.tangosol.util.extractor.MultiExtractor; +import com.tangosol.util.ValueExtractor; +import com.tangosol.util.ImmutableArrayList; + + +/** +* MultiExtractor extension to muliply the results of the two (2) +* ValueExtractor(s). +* +* @author ew 2008.02.20 +* @since Coherence 3.4 +*/ +public class MultiplyExtractor + extends MultiExtractor + { + // ------ constructors --------------------------------------------------- + /** + * Construct a MultiplyExtractor. + * + * @param aExtractor An array of ValueExtractors. The results of the + * first two extractors will be multiplied. If more + * than two (2) extractors are passed, the excess + * extractors will be ignored. + */ + public MultiplyExtractor(ValueExtractor[] aExtractor) + { + super(aExtractor); + Base.azzert(aExtractor.length == 2, "Report multiplication requires " + + "two and only two arguments"); + } + + public Object extract(Object oTarget) + { + ImmutableArrayList arResults = (ImmutableArrayList)super.extract(oTarget); + if (arResults.size() > 1) + { + Object o1 = arResults.get(0); + Object o2 = arResults.get(1); + if (o1 instanceof Number && o2 instanceof Number) + { + return new Double(((Number)o1).doubleValue() * ((Number)o2).doubleValue()); + } + } + return null; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/OperationExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/OperationExtractor.java new file mode 100644 index 0000000000000..2cab44887c335 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/OperationExtractor.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.reporter.extractor; + +import com.tangosol.coherence.reporter.Constants; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.net.management.MBeanHelper; + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +import java.lang.reflect.Array; + +import javax.management.MBeanServer; +import javax.management.ObjectName; + +/** + * ValueExtractor implementation to extract value from an + * MBean operation invocation. + * + * @author sr 2017.09.20 + * @since Coherence 12.2.1 + */ +public class OperationExtractor + implements ValueExtractor, Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a OperationExtractor based on an Operation name. + * + * @param sMethodName the name of the operation to be invoked + * @param chDelim the delimiter to use for merging the individual array elements, if the output is an + * array + * @param aoMethodParamsParams the input parameters array + * @param asParamTypes an array containing the input parameter types of the operation + */ + public OperationExtractor(String sMethodName, char chDelim, Object[] aoMethodParamsParams, String[] asParamTypes) + { + this(sMethodName, chDelim, aoMethodParamsParams, asParamTypes, MBeanHelper.findMBeanServer()); + } + + /** + * Construct a OperationExtractor based on an Operation name. + * + * @param sMethodName the name of the operation to be invoked + * @param chDelim the delimiter to use for merging the individual array elements, if the output is an + * array + * @param aoMethodParamsParams the input parameters array + * @param asParamTypes an array containing the input parameter types of the operation + * @param server the {@link MBeanServer} to query against + */ + public OperationExtractor(String sMethodName, char chDelim, Object[] aoMethodParamsParams, String[] asParamTypes, MBeanServer server) + { + m_sMethodName = sMethodName; + m_chDelim = chDelim; + m_aoMethodParamsParams = aoMethodParamsParams; + m_asParamTypes = asParamTypes; + f_mbs = server; + } + + // ----- ValueExtractor interface --------------------------------------- + + @Override + public Object extract(Object oTarget) + { + try + { + ObjectName objectName = (ObjectName) oTarget; + Object[] aoParams = m_aoMethodParamsParams; + String[] asParamTypes = m_asParamTypes; + + Object oResult = f_mbs.invoke(objectName, m_sMethodName, aoParams, asParamTypes); + return getValueForReportColumn(oResult); + } + catch (Exception e) + { + CacheFactory.log("OperationExtractor.extract: handled exception while invoking an MBean operation" + + "\nStack trace:" + Base.printStackTrace(e), Base.LOG_ERR); + // reporter extractors does not seem to throw the exception further, + // rather they are just returning default values + } + return DEFAULT_VALUE; + } + + // ----- OperationExtractor methods ------------------------------------- + + /** + * Process the input object to return a value which is amenable to be added as a report column value. + * + * @param oResult the object to be processed + * + * @return the object to be added in a report column + */ + protected Object getValueForReportColumn(Object oResult) + { + // if the result is an array, append the elements with the delimiter in between + char chSubDelim = m_chDelim; + Class clzResult = oResult.getClass(); + String sPrefix = ""; + if (clzResult.isArray()) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0, c = Array.getLength(oResult); i < c; i++) + { + sb.append(sPrefix).append(Array.get(oResult, i)); + sPrefix = String.valueOf(chSubDelim); + } + return sb.toString(); + } + return oResult; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link MBeanServer} to query against. + */ + protected final MBeanServer f_mbs; + + /** + * The name of the method to be invoked. + */ + protected String m_sMethodName; + + /** + * The column delimiter for string values. + */ + protected char m_chDelim; + + /** + * The array of Object parameters. + */ + protected Object[] m_aoMethodParamsParams; + + /** + * The method parameter type array. + */ + protected String[] m_asParamTypes; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/SubQueryExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/SubQueryExtractor.java new file mode 100755 index 0000000000000..8842633a31120 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/SubQueryExtractor.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.util.ValueExtractor; +import com.tangosol.util.ImmutableArrayList; +import com.tangosol.util.extractor.MultiExtractor; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.coherence.reporter.Constants; +import com.tangosol.coherence.reporter.JMXQueryHandler; +import com.tangosol.coherence.reporter.Reporter; + +import java.util.Set; +import java.util.Iterator; + + +/** +* A MultiExtractor extension to implement static and correlated sub queries +* from a MBeanServer source. +* +* @author ew 2008.02.20 +* @since Coherence 3.4 +*/ +public class SubQueryExtractor + implements ValueExtractor, Constants + { + // ----- constructors ---------------------------------------------------- + + /** + * Construct a sub query value extractor. + * + * @param aExtractor ValueExtractor array containing the macro replacement + * extractors for the pattern replacement. + * @param xmlQuery the XML definition of the query. + * @param jmxqOuter a reference to the outer query. + * @param sColumnId the column identifier for the result + */ + public SubQueryExtractor(ValueExtractor[] aExtractor, XmlElement xmlQuery, + JMXQueryHandler jmxqOuter, String sColumnId) + { + if (aExtractor.length > 0) + { + m_ME = new MultiExtractor(aExtractor); + } + + m_sPatternTemplate = xmlQuery.getElement(TAG_PATTERN).getString(); + m_xmlQuery = xmlQuery; + m_jmxqOuter = jmxqOuter; + m_sColumnId = sColumnId; + } + + // ----- ValueExtractor interface ---------------------------------------- + + /** + * @inheritDoc + */ + public Object extract(Object oTarget) + { + ImmutableArrayList arResults = (m_ME != null) ? + (ImmutableArrayList) m_ME.extract(oTarget) : null; + Set setMacros = Reporter.getMacros(m_sPatternTemplate); + String sPattern = m_sPatternTemplate; + int c = 0; + + // replace the values in the subquery pattern with the values from the + // current object + if (arResults != null) + { + for (Iterator iter = setMacros.iterator(); iter.hasNext();) + { + String sId = (String)iter.next(); + Object oValue = arResults.get(c); + if (oValue != null) + { + sPattern = sPattern.replaceAll(MACRO_START + sId + MACRO_STOP, + oValue.toString()); + } + c++; + } + } + + + // update the query xml definition with the new target. + m_xmlQuery.getElement(TAG_PATTERN).setString(sPattern); + + // create the sub query + JMXQueryHandler inner = new JMXQueryHandler(); + + // Set the correlated target for the sub query. This is used to extract + // correlated values from the outer query + inner.setCorrelated(oTarget); + + // Set inner query with the new query and the context (column and + // filter definitions from the outer context. + inner.setContext(m_xmlQuery, m_jmxqOuter.getContext()); + + // execute the inner query. + inner.execute(); + + // get the value of the inner query. null is passed because inner + // queries can only be aggregates. + return inner.getValue(null, m_sColumnId); + } + + // ----- data members ---------------------------------------------------- + + /* + * the xml definition of the sub query + */ + protected XmlElement m_xmlQuery; + + /* + * the join template for the sub query pattern + */ + protected String m_sPatternTemplate; + + /* + * the reference to the outer query. + */ + protected JMXQueryHandler m_jmxqOuter; + + /* + * the result column identifier (the aggregate column returned). + */ + protected String m_sColumnId; + + protected MultiExtractor m_ME; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/SubtractExtractor.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/SubtractExtractor.java new file mode 100755 index 0000000000000..39e930c8013f2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/extractor/SubtractExtractor.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.extractor; + + +import com.tangosol.util.Base; +import com.tangosol.util.extractor.MultiExtractor; +import com.tangosol.util.ImmutableArrayList; +import com.tangosol.util.ValueExtractor; + +import java.util.Date; + + +/** +* MultiExtractor extension to subtract the results of the second ValueExtractor +* by the results of the first ValueExtractor. If either one of the results is +* not numeric, null will be returned. +* +* @author ew 2008.02.20 +* @since Coherence 3.4 +*/ + +public class SubtractExtractor + extends MultiExtractor + { + // ----- constructors ---------------------------------------------------- + + /** + * Construct a SubtractExtractor. + * + * @param aExtractor An array of ValueExtractors. The results of the second + * extractor will be subtracted from the results of the + * first. If more than two (2) extractors are passed. + * the excess extractors will be ignored. If less than + * two (2) extractors an assert error will occur. + */ + public SubtractExtractor(ValueExtractor[] aExtractor) + { + super(aExtractor); + Base.azzert(aExtractor.length == 2, "Report subraction requires " + + "two and only two arguments"); + } + + // ----- ValueExtractor interface ---------------------------------------- + + /** + * @inheritDoc + */ + public Object extract(Object oTarget) + { + ImmutableArrayList arResults = (ImmutableArrayList)super.extract(oTarget); + if (arResults.size() > 1) + { + Object o1 = arResults.get(0); + Object o2 = arResults.get(1); + if (o1 instanceof Number && o2 instanceof Number) + { + return new Double(((Number) o1).doubleValue() + - ((Number) o2).doubleValue()); + } + if (o1 instanceof Date && o2 instanceof Date) + { + return Long.valueOf(((Date) o1).getTime() + - ((Date) o2).getTime()); + } + } + return null; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AddLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AddLocator.java new file mode 100755 index 0000000000000..4bb847b0a4898 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AddLocator.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.AddExtractor; + +import com.tangosol.util.ValueExtractor; + +/** +* Instantiate an AddExtractor. +* +* @author ew 2008.02.20 +* @since Coherence 3.4 +*/ +public class AddLocator + extends FunctionLocator + { + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + if (m_veExtractor == null) + { + ValueExtractor[] aVE = new ValueExtractor[2]; + + buildExtractors(); + + aVE[0] = m_veColumn1; + aVE[1] = m_veColumn2; + + m_veExtractor = new AddExtractor(aVE); + } + return m_veExtractor; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AggregateLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AggregateLocator.java new file mode 100755 index 0000000000000..32272c9bab2e0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AggregateLocator.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.AggregateExtractor; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +import java.util.Set; + + +/** +* A super class for single column aggregations. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class AggregateLocator + extends BaseLocator + { + /** + * @inheritDoc + */ + public void configure(XmlElement xml) + { + super.configure(xml); + String sColumnRef = xml.getSafeElement(TAG_COLUMNREF).getString(); + m_veColumn = Base.checkNotNull(m_queryHandler.ensureExtractor(sColumnRef), "Column extractor"); + } + + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + if (m_veAggregate == null) + { + m_veAggregate = new AggregateExtractor(m_source, m_iAggregatePos); + } + return m_veAggregate; + } + + /** + * @inheritDoc + */ + public void reset(Set setResults) + { + super.reset(setResults); + m_veAggregate = null; // must be nulled out for repeated calls. + m_veColumn = null; // must be nulled out for repeated calls. + } + + /** + * @inheritDoc + */ + public boolean isAggregate() + { + return true; + } + + + // ----- data members ---------------------------------------------------- + + /** + * The Extractor to be aggregated. + */ + protected ValueExtractor m_veColumn; + + /** + * The Extractor to be aggregated. + */ + protected ValueExtractor m_veAggregate; + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AttributeLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AttributeLocator.java new file mode 100755 index 0000000000000..360b6c850dbb2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AttributeLocator.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.Constants; +import com.tangosol.coherence.reporter.extractor.AttributeExtractor; +import com.tangosol.coherence.reporter.extractor.JoinExtractor; +import com.tangosol.coherence.reporter.JMXQueryHandler; +import com.tangosol.coherence.reporter.Reporter; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +import javax.management.MBeanServer; + +import java.util.Iterator; +import java.util.Set; + +/** +* Locator for configuring an AttributeExtractor or JoinExtractor +* +* @since Coherence 3.4 +* @author ew 2008.01.28 +* +*/ +public class AttributeLocator + extends BaseLocator + implements Constants + { + /** + * @inheritDoc + */ + public void configure(XmlElement xmlConfig) + { + super.configure(xmlConfig); + m_fReturnNeg = xmlConfig.getSafeElement("return-neg").getBoolean(false); + setJoinTemplate(xmlConfig.getSafeElement(Reporter.TAG_QUERY) + .getSafeElement(Reporter.TAG_PATTERN) + .getString("")); + } + + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + if (m_veExtractor == null) + { + String sTemplate = m_sJoinTemplate; + MBeanServer server = m_source.getMBeanServer(); + + if(sTemplate == null || sTemplate.length() == 0) + { + m_veExtractor = new AttributeExtractor(m_sName, m_cDelim, m_fReturnNeg, server); + } + else + { + // the attribute is a join. + Set setMacros = Reporter.getMacros(sTemplate); + if (setMacros.size() > 0) + { + int nSize = setMacros.size(); + ValueExtractor[] aVE = new ValueExtractor[nSize]; + int c = 0; + JMXQueryHandler qh = m_queryHandler; + for (Iterator i = setMacros.iterator(); i.hasNext();) + { + String sExtractorName = (String)i.next(); + aVE[c] = Base.checkNotNull(qh.ensureExtractor(sExtractorName), "Column extractor"); + c++; + } + return new JoinExtractor(aVE, m_sJoinTemplate, + new AttributeExtractor(m_sName, ',', m_fReturnNeg, server), + server); + } + return null; // not sure if this is a valid case. + } + } + return m_veExtractor; + } + + /** + * Obtain the join template string for joined attributes + * + * @return a JMX query string containing value replacement {macros}. + */ + public String getJoinTemplate() + { + return m_sJoinTemplate; + } + + /** + * Set the join template string for joined attributes + * + * @param sTemplate a JMX query string containing v alue replacement {macros}. + */ + protected void setJoinTemplate(String sTemplate) + { + m_sJoinTemplate = sTemplate; + } + + /** + * @inheritDoc + */ + public boolean isRowDetail() + { + return true; + } + + //----- data members ----------------------------------------------------- + + /** + * A JMX query string containing value replacement macros The macros will + * be replaced with data from column information during runtime. + */ + protected String m_sJoinTemplate; + + /** + * flag allow the return of a negative number + */ + protected boolean m_fReturnNeg = false; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AverageLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AverageLocator.java new file mode 100755 index 0000000000000..e75d1151a3c66 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/AverageLocator.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.util.aggregator.DoubleAverage; +import com.tangosol.util.InvocableMap; + +/** +* Calculates an average for values of any numberic type extracted from a set +* of MBeans. All the extracted objects will be treated as Java double +* values. If the set of entries is empty, a null result is returned. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class AverageLocator + extends AggregateLocator + { + /** + * @inheritDoc + */ + public InvocableMap.EntryAggregator getAggregator() + { + return new DoubleAverage(m_veColumn); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/BaseLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/BaseLocator.java new file mode 100755 index 0000000000000..43ebf00ee9f70 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/BaseLocator.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.Constants; +import com.tangosol.coherence.reporter.DataSource; +import com.tangosol.coherence.reporter.JMXQueryHandler; +import com.tangosol.coherence.reporter.QueryHandler; +import com.tangosol.coherence.reporter.Reporter; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.InvocableMap; +import com.tangosol.util.ValueExtractor; + +import java.util.Set; + + +/** +* Base class for providing standard Locator functionality. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class BaseLocator + implements Constants, ColumnLocator + { + // ----- ColumnLocator interface ----------------------------------------- + + /** + * @inheritDoc + */ + public Object getValue(Object oKey) + { + if (isAggregate()) + { + return m_source.getAggValue(oKey, m_iAggregatePos); + } + else + + { + return m_source.getValue(oKey, m_iExtractorPos); + } + } + + /** + * @inheritDoc + */ + public void reset(Set setResults) + { + m_veExtractor = null; + } + + /** + * @inheritDoc + */ + public void setDataSource(DataSource source) + { + m_source = source; + if (isAggregate()) + { + m_iAggregatePos = source.addAggregator(getAggregator()); + } + else + { + ValueExtractor ve = getExtractor(); + m_iExtractorPos = source.addExtractor(ve); + + // group-by for hidden columns is not supported (see COH-14871) + if (m_fGroupBy && !m_fHidden) + { + source.addGroupBy(ve); + } + } + } + + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + return null; + } + + /** + * @inheritDoc + */ + public InvocableMap.EntryAggregator getAggregator() + { + return null; + } + + /** + * @inheritDoc + */ + public boolean isAggregate() + { + return false; + } + + /** + * @inheritDoc + */ + public void configure(XmlElement xmlConfig) + { + m_xml = xmlConfig; + m_cDelim = getDelim(); + + m_sName = xmlConfig.getSafeElement(Reporter.TAG_COLUMNNAME).getString(); + m_sId = xmlConfig.getSafeAttribute("id").getString(); + m_fGroupBy = xmlConfig.getSafeElement("group-by").getBoolean(false); + m_fHidden = xmlConfig.getSafeElement("hidden").getBoolean(false); + + if (m_sId.length() == 0) + { + m_sId = m_sName; + } + } + + /** + * @inheritDoc + */ + public XmlElement getConfig() + { + return m_xml; + } + + /** + * @inheritDoc + */ + public boolean isRowDetail() + { + return false; + } + + /** + * @inheritDoc + */ + public String getName() + { + return m_sName; + } + + /** + * @inheritDoc + */ + public String getId() + { + return m_sId; + } + + /** + * @inheritDoc + */ + public void setQuery(QueryHandler handler) + { + m_queryHandler = (JMXQueryHandler) handler; + } + + /** + * @inheritDoc + */ + public void configure(XmlElement xmlConfig, JMXQueryHandler handler, DataSource source) + { + setQuery(handler); + configure(xmlConfig); + setDataSource(source); + } + + // ----- accessors and helpers ------------------------------------------- + /** + * extract the column delimiter from the report configuration + * + * @return the column delimiter character + */ + protected char getDelim() + { + if (m_cDelim == Character.UNASSIGNED) + { + String sDelim = m_xml.getSafeElement(TAG_DELIM).getString( + VALUE_TAB); + + if (sDelim.equals(VALUE_TAB)) + { + m_cDelim = '\t'; + } + else if (sDelim.equals(VALUE_SPACE)) + { + m_cDelim = ' '; + } + else + { + m_cDelim = sDelim.charAt(0); + } + } + return m_cDelim; + } + + + // ----- data members ---------------------------------------------------- + + /** + * The column name. + */ + protected String m_sName; + + /** + * The column name. + */ + protected String m_sId; + + /** + * The configuration XML + */ + protected XmlElement m_xml; + + /** + * The column delimiter. + */ + protected char m_cDelim; + + /** + * Reference to the QueryHandler. + */ + protected JMXQueryHandler m_queryHandler; + + /** + * The Value Extractor of the Locator + */ + protected ValueExtractor m_veExtractor; + + /** + * The index into the Extractor results for the Locator + */ + protected int m_iExtractorPos; + + /** + * The index into the Extractor results for the Locator + */ + protected int m_iGroupPos; + + /** + * The index into the aggregator results for the Locator + */ + protected int m_iAggregatePos; + + /** + * Datasource where extractor and aggregate results reside. + */ + protected DataSource m_source; + + /** + * The flag indicating whether the column should be part of the group by clause. + */ + protected boolean m_fGroupBy; + + /** + * The flag indicating whether the column should be hidden. + */ + protected boolean m_fHidden; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/BatchLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/BatchLocator.java new file mode 100755 index 0000000000000..7830745a55982 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/BatchLocator.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.ConstantExtractor; + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +/** +* Global column to include a Batch identifier into a report. The batch identifier +* is incremented with each execution of the Report Group. The batch identifier +* is helpfull when needing to associate data from related reports. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class BatchLocator + extends BaseLocator + { + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + super.getExtractor(); + if (m_veExtractor == null) + { + m_veExtractor = new ConstantExtractor( + Long.valueOf(m_queryHandler.getBatch())); + } + return m_veExtractor; + } + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/ColumnLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/ColumnLocator.java new file mode 100755 index 0000000000000..3920974a9cc26 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/ColumnLocator.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.DataSource; +import com.tangosol.coherence.reporter.JMXQueryHandler; +import com.tangosol.coherence.reporter.QueryHandler; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.InvocableMap; +import com.tangosol.util.ValueExtractor; + +import java.util.Set; + + +/** +* ColumnLocator is used to include custom information (for filtering or display) +* in a Report. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public interface ColumnLocator + { + /** + * Configure the ColumnLocator. + * + * @param xmlConfig the XML configuration + */ + public void configure(XmlElement xmlConfig); + + /** + * Configure the ColumnLocator. + * + * @param xmlConfig the XML configuration + * @param handler the JMXQueryHandler associated with the Locator + * @param source the data source for the locator + */ + public void configure(XmlElement xmlConfig, JMXQueryHandler handler, DataSource source); + + /** + * Get the string Identifier of the QuerySource. + * + * @return the string Identifier of the QuerySource + */ + public String getId(); + + /** + * Configure the queryHandler context for the Locator. The handler is used + * to instantiate related Locators. + * + * @param handler the handler associated with the Locator + */ + public void setQuery(QueryHandler handler); + + /** + * Locate and return the data value. + * + * @param oKey the key for the data + * + * @return the aggregate or extracted value + */ + public Object getValue(Object oKey); + + /** + * Determine if the underlying data source is an aggregate value. + * + * @return true if the value is an aggregate + */ + public boolean isAggregate(); + + /** + * Determine if the underlying data source has detailed values. + * + * @return true if the value is detailed + */ + public boolean isRowDetail(); + /** + * Clean up or process information after the completion of the query. + * + * @param setResults the set of keys from the query. + */ + public void reset(Set setResults); + + /** + * Obtain the EntryAggregator for the column. + * + * @return the EntryAggregator + */ + public InvocableMap.EntryAggregator getAggregator(); + + /** + * Obtain the ValueExtractor for the column. + * + * @return the ValueExtractor + */ + public ValueExtractor getExtractor(); + + /** + * Set the DataSource for the ColumnLocator. + * + * @param source the source for the data + */ + public void setDataSource(DataSource source); + + /** + * Get the configuration XML for the ColumnLocator + * + * @return an XmlElement containing the configuration + */ + public XmlElement getConfig(); + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/ConstantLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/ConstantLocator.java new file mode 100755 index 0000000000000..f154054653cee --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/ConstantLocator.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.ConstantExtractor; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.ValueExtractor; + +/** +* Class to include a constant value as a report column. Constants are most +* frequently used in functions and are not visible. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class ConstantLocator + extends BaseLocator + { + /** + * @inheritDoc + */ + public void configure(XmlElement xml) + { + super.configure(xml); + + String sType = xml.getSafeElement(TAG_TYPE).getString("double"); + Object oValue = null; + if (sType.equals("string")) + { + oValue = xml.getSafeElement(TAG_VALUE).getString(); + } + else if (sType.equals("double")) + { + oValue = new Double(xml.getSafeElement(TAG_VALUE).getDouble()); + } + m_Ret = oValue; + } + + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + super.getExtractor(); + if (m_veExtractor == null) + { + m_veExtractor = new ConstantExtractor(m_Ret); + } + return m_veExtractor; + } + + /** + * @inheritDoc + */ + public Object getValue(Object oKey) + { + return m_Ret; + } + + // ----- constants ------------------------------------------------------- + + /** + * the constant data type XML tag + */ + public static String TAG_TYPE = "data-type"; + + /** + * the constant value XML tag + */ + public static String TAG_VALUE = "value"; + + // ----- data members ---------------------------------------------------- + + /** + * the value of the constant + */ + protected Object m_Ret; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/CorrelatedLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/CorrelatedLocator.java new file mode 100755 index 0000000000000..fd3ef488bae69 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/CorrelatedLocator.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.CorrelatedExtractor; +import com.tangosol.coherence.reporter.Reporter; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +/** +* Class to include a correlated reference in a sub query filter. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class CorrelatedLocator + extends BaseLocator + { + // ----- ColumnLocator interface ----------------------------------------- + /** + * @inheritDoc + */ + public void configure(XmlElement xml) + { + super.configure(xml); + m_sColumnRef = xml.getSafeElement(Reporter.TAG_COLUMNREF).getString(); + } + + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + super.getExtractor(); + if (m_veExtractor == null) + { + m_veExtractor = new CorrelatedExtractor( + Base.checkNotNull(m_queryHandler.ensureExtractor(m_sColumnRef), "Column extractor")); + } + ((CorrelatedExtractor) m_veExtractor).setTarget(m_oCorrelated); + return m_veExtractor; + } + + // ----- accessors ------------------------------------------------------- + + /** + * Set the reference to the outer object. + * + * @param oCorrelatedRef the correlated MBean reference name + */ + public void setCorrellatedObject(Object oCorrelatedRef) + { + m_oCorrelated = oCorrelatedRef; + } + + // ----- data members ---------------------------------------------------- + /** + * The wrapped column reference + */ + protected ColumnLocator m_columnLocator; + + /** + * The wrapped column reference + */ + protected Object m_oCorrelated; + + /** + * The correlated column string identifier. + */ + protected String m_sColumnRef; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/CountLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/CountLocator.java new file mode 100755 index 0000000000000..ca770c57a23f7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/CountLocator.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + +import com.tangosol.util.InvocableMap; +import com.tangosol.util.aggregator.Count; + + +/** +* Class to calculate the maximum data value for a column +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class CountLocator + extends AggregateLocator + { + public InvocableMap.EntryAggregator getAggregator() + { + return new Count(); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/DateTimeLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/DateTimeLocator.java new file mode 100755 index 0000000000000..1b2805648853e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/DateTimeLocator.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.ConstantExtractor; + +import com.tangosol.util.ValueExtractor; + +import java.util.Date; + + +/** +* Global column to include report start time as a column. Including time +* is helpful when doing time series analysis. +* +* @since Coherence 3.4 +* @author ew 2008.01.28 +*/ +public class DateTimeLocator + extends BaseLocator + { + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + super.getExtractor(); + if (m_veExtractor == null) + { + m_veExtractor = new ConstantExtractor( + new Date(m_queryHandler.getStartTime())); + } + return m_veExtractor; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/DeltaLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/DeltaLocator.java new file mode 100755 index 0000000000000..8d1aadea94b97 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/DeltaLocator.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.DeltaExtractor; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; +import com.tangosol.util.ListMap; +import com.tangosol.util.ValueExtractor; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + + +/** +* A Locator that returns the difference between 2 executions a ValueExtractor +* or aggregate. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class DeltaLocator + extends BaseLocator + { + // ----- Constructor ----------------------------------------------------- + /** + * Construct a Delta locator. + */ + public DeltaLocator() + { + super(); + m_mapPrior = new HashMap(); + m_mapKey = new ListMap(); + } + + // ----- ColumnLocator interface ----------------------------------------- + + /** + * @inheritDoc + */ + public void configure(XmlElement xml) + { + super.configure(xml); + String sId = xml.getSafeElement(TAG_COLUMNREF).getString(); + + m_columnLocator = Base.checkNotNull(m_queryHandler.ensureColumnLocator(xml, sId), "Column locator"); + m_sSourceId = sId; + buildExtractors(); + + XmlElement xmlColumns = xml.getSafeElement(TAG_PARAMS); + List listColumns = xmlColumns.getElementList(); + if (listColumns != null) + { + for (Iterator iter = listColumns.iterator(); iter.hasNext(); ) + { + sId = ((XmlElement) iter.next()).getString(); + ValueExtractor ve = Base.checkNotNull(m_queryHandler.ensureExtractor(sId), "Column extractor"); + m_mapKey.put(sId, ve); + } + } + } + + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + super.getExtractor(); + return buildExtractors(); + } + + /** + * @inheritDoc + */ + public void reset(Set setResults) + { + ValueExtractor veSource = m_veSource; + DeltaExtractor veDelta = m_veDelta; + + m_mapPrior.clear(); + + for (Iterator i = setResults.iterator(); i.hasNext();) + { + Map.Entry entry = (Map.Entry) i.next(); + Object oValue = veSource.extract(entry.getKey()); + Object oKey = veDelta.getKey(entry.getKey()); + putPrior(oKey, oValue); + } + + m_veExtractor = null; + m_veSource = null; + m_veDelta = null; + } + + /** + * @inheritDoc + */ + public boolean isRowDetail() + { + return m_columnLocator.isRowDetail(); + } + + // ----- accessors and helpers ------------------------------------------- + + /** + * Maintain a Map of the prior values of the Column. + * + * @param oKey the key for the column. + * @param oValue the prior value for the column. + */ + public void putPrior(Object oKey, Object oValue) + { + m_mapPrior.put(oKey, oValue); + } + + protected ValueExtractor buildExtractors() + { + if (m_veSource == null) + { + m_veSource = m_queryHandler.ensureExtractor(m_sSourceId); + } + if (m_veDelta == null) + { + m_veDelta = new DeltaExtractor(m_mapPrior, m_veSource, m_mapKey); + } + return m_veDelta; + } + + //----- data members ----------------------------------------------------- + /** + * Map containing the prior value of the column. + * Key is the ObjectName key property list string. + * Value prior result object + */ + protected Map m_mapPrior; + + /** + * Reference to the column that returns the value + */ + protected ColumnLocator m_columnLocator; + + /** + * The source data value extractor + */ + protected ValueExtractor m_veSource; + + /** + * The value extractor used by QueryHandler. + */ + protected DeltaExtractor m_veDelta; + + /** + * Reference to the key locator for the delta + */ + protected Map m_mapKey; + + protected String m_sSourceId; + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/DivideLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/DivideLocator.java new file mode 100755 index 0000000000000..46628c01083b1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/DivideLocator.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.DivideExtractor; + +import com.tangosol.util.ValueExtractor; + +/** +* Column to divide one column by the second. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class DivideLocator + extends FunctionLocator + { + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + super.getExtractor(); + + if (m_veExtractor == null) + { + ValueExtractor[] aVE = new ValueExtractor[2]; + buildExtractors(); + aVE[0] = m_veColumn1; + aVE[1] = m_veColumn2; + + m_veExtractor = new DivideExtractor(aVE); + } + return m_veExtractor; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/FunctionLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/FunctionLocator.java new file mode 100755 index 0000000000000..9f2f5423d0e63 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/FunctionLocator.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.Reporter; +import com.tangosol.coherence.reporter.DataSource; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; + + +/** +* Base class for locator that calcculate their value based on two other locator. +* +* @since Coherence 3.4 +* @author ew 2008.01.28 +*/ +public class FunctionLocator + extends BaseLocator + { + + /** + * @inheritDoc + */ + public void configure(XmlElement xmlConfig) + { + super.configure(xmlConfig); + + XmlElement xmlColumns = xmlConfig.getElement(Reporter.TAG_PARAMS); + List listColumns = xmlColumns.getElementList(); + + Iterator iter = listColumns.iterator(); + String sId = ((XmlElement) iter.next()).getString(); + ColumnLocator qc = m_queryHandler.ensureColumnLocator(null, sId); + + m_veColumn1 = Base.checkNotNull(m_queryHandler.ensureExtractor(sId), "Column extractor"); + + boolean fAgg1 = qc.isAggregate(); + boolean fDet1 = qc.isRowDetail(); + + m_sId1 = sId; + sId = ((XmlElement) iter.next()).getString(); + qc = m_queryHandler.ensureColumnLocator(null, sId); + + m_veColumn2 = Base.checkNotNull(m_queryHandler.ensureExtractor(sId), "Column extractor"); + + boolean fAgg2 = qc.isAggregate(); + boolean fDet2 = qc.isRowDetail(); + + m_sId2 = sId; + m_fAggregate = fAgg1 && fAgg2; + m_fDetail = fDet1 || fDet2; + } + + /** + * @inheritDoc + */ + public void reset(Set setResults) + { + super.reset(setResults); + + // Reset the ValueExtractors for subsequent executions. + m_veColumn1 = null; + m_veColumn2 = null; + } + + + /** + * @inheritDoc + */ + public Object getValue(Object oKey) + { + if (m_fDetail) + { + return m_source.getValue(oKey, m_iExtractorPos); + } + else + { + return m_source.getScalarValue(oKey, m_iExtractorPos); + } + } + + + /** + * @inheritDoc + */ + public void setDataSource(DataSource source) + { + m_source = source; + if (m_fDetail) + { + m_iExtractorPos = m_source.addExtractor(getExtractor()); + } + else + { + m_iExtractorPos = m_source.addScalar((getExtractor())); + } + } + /** + * @inheritDoc + */ + public boolean isAggregate() + { + return m_fAggregate; + } + + /** + * @inheritDoc + */ + public boolean isRowDetail() + { + return m_fDetail; + } + + protected void buildExtractors() + { + if (m_veColumn1 == null) + { + m_veColumn1 = m_queryHandler.ensureExtractor(m_sId1); + } + + if (m_veColumn2 == null) + { + m_veColumn2 = m_queryHandler.ensureExtractor(m_sId2); + } + } + + // ----- data members ---------------------------------------------------- + /* + * the first column operand + */ + protected ValueExtractor m_veColumn1; + + /* + * the second column operand + */ + protected ValueExtractor m_veColumn2; + + /* + * the first column Identifier + */ + protected String m_sId1; + + /* + * the second column Identifier + */ + protected String m_sId2; + + /** + * Flag to determine if the underlying locators are aggregates + */ + protected boolean m_fAggregate; + + /** + * Flag to determine if the function needs to display detail + */ + protected boolean m_fDetail; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/KeyLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/KeyLocator.java new file mode 100755 index 0000000000000..acae5ca16f6b6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/KeyLocator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.KeyExtractor; + +import com.tangosol.util.ValueExtractor; + +/** +* Column returning a JMX ObjectName part. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class KeyLocator + extends BaseLocator + { + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + super.getExtractor(); + if (m_veExtractor == null) + { + m_veExtractor = new KeyExtractor(m_sName); + } + return m_veExtractor; + } + + /** + * @inheritDoc + */ + public boolean isRowDetail() + { + return true; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/MaxLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/MaxLocator.java new file mode 100755 index 0000000000000..eb161a2de79dd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/MaxLocator.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.util.InvocableMap; +import com.tangosol.util.aggregator.ComparableMax; + + +/** +* Calculates a minimum of numeric values extracted from a set of MBeans. All +* the extracted Number objects will be treated as Java double values. +* +* +* @since Coherence 3.4 +* @author ew 2008.01.28 +*/ + +public class MaxLocator + extends AggregateLocator + { + /** + * @inheritDoc + */ + public InvocableMap.EntryAggregator getAggregator() + { + return new ComparableMax(m_veColumn); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/MinLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/MinLocator.java new file mode 100755 index 0000000000000..645e6f9cdc548 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/MinLocator.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.util.InvocableMap; +import com.tangosol.util.aggregator.ComparableMin; + + +/** +* Calculates a minimum of numeric values extracted from a set of MBeans. All +* the extracted Number objects will be treated as Java double values. +* +* +* @since Coherence 3.4 +* @author ew 2008.01.28 +*/ + +public class MinLocator + extends AggregateLocator + { + public InvocableMap.EntryAggregator getAggregator() + { + return new ComparableMin(m_veColumn); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/MultiplyLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/MultiplyLocator.java new file mode 100755 index 0000000000000..7eec0eeb4f64b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/MultiplyLocator.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.MultiplyExtractor; + +import com.tangosol.util.ValueExtractor; + +/** +* FunctionLocator extension to multiply two locators +* +* @since Coherence 3.4 +* @author ew 2008.01.28 +*/ +public class MultiplyLocator + extends FunctionLocator + { + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + super.getExtractor(); + if (m_veExtractor == null) + { + ValueExtractor[] aVE = new ValueExtractor[2]; + + if (m_veColumn1 == null || m_veColumn2 == null) + { + buildExtractors(); + } + aVE[0] = m_veColumn1; + aVE[1] = m_veColumn2; + + return new MultiplyExtractor(aVE); + } + return m_veExtractor; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/NodeLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/NodeLocator.java new file mode 100644 index 0000000000000..255e6118cdcd8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/NodeLocator.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.reporter.extractor.ConstantExtractor; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.util.ValueExtractor; + + +/** + * Locator for the node macro; finds the nodeId of the local member + * + * @since Coherence 12.1.3 + * @author sw 2012.11.29 + */ +public class NodeLocator + extends BaseLocator + { + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + ValueExtractor ve = m_veExtractor; + if (ve == null) + { + ve = m_veExtractor = new ConstantExtractor( + Integer.valueOf(CacheFactory.ensureCluster().getLocalMember().getId())); + } + return ve; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/ObjectNameLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/ObjectNameLocator.java new file mode 100644 index 0000000000000..c8e1fcc8e16b0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/ObjectNameLocator.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.util.ValueExtractor; +import com.tangosol.util.extractor.IdentityExtractor; + +/** +* Locator to include the ObjectName in a Report. To remove Group By Logic. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ + +public class ObjectNameLocator + extends BaseLocator + { + public ValueExtractor getExtractor() + { + return new IdentityExtractor(); + } + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/OperationLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/OperationLocator.java new file mode 100644 index 0000000000000..c975d0894bcc2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/OperationLocator.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.coherence.reporter.locator; + +import com.tangosol.coherence.reporter.extractor.OperationExtractor; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.ValueExtractor; + +/** + * Locator for returning an MBean operation extractor. + * + * @author sr 2017.09.20 + * @since Coherence 12.2.1 + */ +public class OperationLocator + extends BaseLocator + { + // ----- ColumnLocator interface ---------------------------------------- + + @Override + public void configure(XmlElement xml) + { + super.configure(xml); + + XmlElement xmlParams = xml.getElement("init-params"); + if (xmlParams != null) + { + // parseInitParams method throws an exception in case of null + // param types, hence we do not need an extra validation here + m_aoMethodParams = XmlHelper.parseInitParams(xmlParams); + m_asSignatureTypes = XmlHelper.parseParamTypes(xmlParams); + } + } + + @Override + public ValueExtractor getExtractor() + { + if (m_veExtractor == null) + { + m_veExtractor = new OperationExtractor(m_sName, m_cDelim, m_aoMethodParams, m_asSignatureTypes, m_source.getMBeanServer()); + } + return m_veExtractor; + } + + // ----- data members --------------------------------------------------- + + /** + * The parameters array. + */ + protected Object[] m_aoMethodParams; + + /** + * The signature parameter array. + */ + protected String[] m_asSignatureTypes; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/PropertyLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/PropertyLocator.java new file mode 100755 index 0000000000000..c512a0584b464 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/PropertyLocator.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.coherence.config.Config; + +import com.tangosol.coherence.reporter.extractor.ConstantExtractor; + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.ValueExtractor; + +/** +* Class to include an immutable system property into a report. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class PropertyLocator + extends BaseLocator + { + /** + * @inheritDoc + */ + public void configure(XmlElement xml) + { + super.configure(xml); + m_sProperty = Config.getProperty(m_sName); + } + + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + super.getExtractor(); + + if (m_veExtractor == null) + { + m_veExtractor = new ConstantExtractor(m_sProperty); + } + return m_veExtractor; + } + + /** + * @inheritDoc + */ + public Object getValue(Object oKey) + { + return m_sProperty; + } + + //---- data members ------------------------------------------------------ + /** + * The value extracted from the system property + */ + protected String m_sProperty; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/SubQueryLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/SubQueryLocator.java new file mode 100755 index 0000000000000..abbb17dab49a5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/SubQueryLocator.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; +import com.tangosol.util.ValueExtractor; + +import com.tangosol.coherence.reporter.Constants; +import com.tangosol.coherence.reporter.extractor.SubQueryExtractor; +import com.tangosol.coherence.reporter.Reporter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.Iterator; + + +/** +* Column to evaluate a sub query. +* +* @since Coherence 3.4 +* @author ew 2008.01.28 +*/ +public class SubQueryLocator + extends BaseLocator + implements Constants + { + /** + * @inheritDoc + */ + public void configure(XmlElement xmlConfig) + { + super.configure(xmlConfig); + + m_xmlQuery = xmlConfig.getSafeElement(Reporter.TAG_QUERY); + + XmlElement xmlColumns = xmlConfig.getSafeElement(Reporter.TAG_PARAMS); + XmlElement xmlRef = xmlConfig.getSafeElement(Reporter.TAG_COLUMNREF); + String sColumnRef = xmlRef.getString(); + List listColumns = xmlColumns.getElementList(); + int nParamCount = listColumns.size(); + + m_filterColumns = new ArrayList(nParamCount); + m_sSourceId = sColumnRef; + } + + /** + * @inheritDoc + */ + public ValueExtractor getExtractor() + { + if (m_veExtractor == null) + { + String sPattern = m_xmlQuery.getSafeElement(TAG_PATTERN) + .getString(); + Set setMacros = Reporter.getMacros(sPattern); + ValueExtractor[] veCorrelated = new ValueExtractor[setMacros.size()]; + + int c = 0; + for (Iterator iter = setMacros.iterator(); iter.hasNext();) + { + String sId = (String)iter.next(); + + veCorrelated[c] = Base.checkNotNull(m_queryHandler.ensureExtractor(sId), "Column extractor"); + } + + m_fCorrelated = setMacros.size() > 0; + + m_veExtractor = new SubQueryExtractor(veCorrelated, m_xmlQuery, + m_queryHandler, m_sSourceId); + } + + return m_veExtractor; + } + + /** + * @inheritDoc + */ + public boolean isRowDetail() + { + return m_fCorrelated; + } + + + //------ data members ---------------------------------------------------- + /** + * The query string template. + */ + protected XmlElement m_xmlQuery; + + /** + * The list of correlated locator used in the subquery filter. + */ + protected List m_filterColumns; + + /** + * The source column for the subquery + */ + protected ColumnLocator m_asColumnLocator; + + /** + * The identifier for the source column for the subquery + */ + protected String m_sSourceId; + + /** + * Determine if the Subquery is Correlated + */ + protected boolean m_fCorrelated; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/SubtractLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/SubtractLocator.java new file mode 100755 index 0000000000000..f39d582cd7216 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/SubtractLocator.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.util.ValueExtractor; +import com.tangosol.coherence.reporter.extractor.SubtractExtractor; + + +/** +* FunctionLocator extension that subtracts the second value from the first +* value. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class SubtractLocator + extends FunctionLocator + { + /** + * @inhertiDoc + */ + public ValueExtractor getExtractor() + { + ValueExtractor[] aVE = new ValueExtractor[2]; + + if (m_veColumn1 == null || m_veColumn2 == null) + { + buildExtractors(); + } + + aVE[0] = m_veColumn1; + aVE[1] = m_veColumn2; + + return new SubtractExtractor(aVE); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/SumLocator.java b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/SumLocator.java new file mode 100755 index 0000000000000..9f931c89a6339 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/coherence/reporter/locator/SumLocator.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.coherence.reporter.locator; + + +import com.tangosol.util.aggregator.DoubleSum; +import com.tangosol.util.InvocableMap; + +/** +* Sums up numeric values extracted from a set of report locator. All the +* extracted Number objects will be treated as Java double values. +* +* @author ew 2008.01.28 +* @since Coherence 3.4 +*/ +public class SumLocator + extends AggregateLocator + { + /** + * @inheritDoc + */ + public InvocableMap.EntryAggregator getAggregator() + { + return new DoubleSum(m_veColumn); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/ConfigurationException.java b/prj/coherence-core/src/main/java/com/tangosol/config/ConfigurationException.java new file mode 100644 index 0000000000000..eb4ba4848a52f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/ConfigurationException.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config; + +/** + * A {@link ConfigurationException} captures information concerning an invalid configuration of Coherence. + * Specifically it details what the problem was and advice for resolving the issue. Optionally + * a {@link ConfigurationException} may be include the causing {@link Exception}. + * + * @author bo 2011.06.15 + * @since Coherence 12.1.2 + */ +@SuppressWarnings("serial") +public class ConfigurationException + extends RuntimeException + { + // ----- constructors ------------------------------------------------- + + /** + * Constructs a {@link ConfigurationException}. + * + * @param sProblem the problem that occurred + * @param sAdvice the advice to fix the problem + */ + public ConfigurationException(String sProblem, String sAdvice) + { + m_sProblem = sProblem; + m_sAdvice = sAdvice; + } + + /** + * Constructs a {@link ConfigurationException} (with a cause). + * + * @param sProblem the problem that occurred + * @param sAdvice the advice to fix the problem + * @param cause the {@link Throwable} causing the problem + */ + public ConfigurationException(String sProblem, String sAdvice, Throwable cause) + { + super(cause); + + m_sProblem = sProblem; + m_sAdvice = sAdvice; + } + + // ----- Exception interface -------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String getMessage() + { + String sResult = ""; + + sResult += "Configuration Exception\n"; + sResult += "-----------------------\n"; + sResult += "Problem : " + getProblem() + "\n"; + sResult += "Advice : " + getAdvice() + "\n"; + + if (getCause() != null) + { + sResult += "Caused By : " + getCause().toString() + "\n"; + } + + return sResult; + } + + // ----- ConfigurationException methods --------------------------------- + + /** + * Returns what the problem was. + * + * @return A string detailing the problem + */ + public String getProblem() + { + return m_sProblem; + } + + /** + * Returns advice to resolve the issue. + * + * @return A string detailing advice to resolve the issue + */ + public String getAdvice() + { + return m_sAdvice; + } + + // ----- data members --------------------------------------------------- + + /** + * Advice for resolving the issue. + */ + private String m_sAdvice; + + /** + * The problem that occurred. + */ + private String m_sProblem; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/annotation/Injectable.java b/prj/coherence-core/src/main/java/com/tangosol/config/annotation/Injectable.java new file mode 100644 index 0000000000000..69faa0dad0836 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/annotation/Injectable.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Denotes that a Java Bean setter method may be injected with a value. + * + * @author bo 2011.06.15 + * @since Coherence 12.1.2 + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Injectable + { + /** + * The optional name that of which may be used to choose the + * appropriate value to inject when multiple values of the same type + * are available to be injected. + * + * @return the name to be used for choosing an appropriate value to inject + */ + String value() default ""; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/annotation/package.html b/prj/coherence-core/src/main/java/com/tangosol/config/annotation/package.html new file mode 100644 index 0000000000000..410b9f4a3aa9d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/annotation/package.html @@ -0,0 +1,6 @@ + +Defines annotations typically used for configuring, documenting and injecting +values into objects. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/ChainedParameterResolver.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ChainedParameterResolver.java new file mode 100644 index 0000000000000..fcdd311667b0b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ChainedParameterResolver.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.ExternalizableHelper; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import java.util.Arrays; + +import javax.json.bind.annotation.JsonbProperty; + + +/** + * A {@link ChainedParameterResolver} is a {@link ParameterResolver} that + * consults zero or more provided {@link ParameterResolver}s in the order in + * which they were defined or added to resolve a {@link Parameter}. + * + * @author bo 2012.12.4 + * @since Coherence 12.1.2 + */ +public class ChainedParameterResolver + implements ParameterResolver, ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor needed for serialization. + */ + public ChainedParameterResolver() + { + m_aResolvers = new ParameterResolver[1]; + } + + /** + * Construct a {@link ChainedParameterResolver} based on the specified + * {@link ParameterResolver}s. + * + * @param resolvers the {@link ParameterResolver}s to be chained + */ + public ChainedParameterResolver(ParameterResolver... resolvers) + { + for (ParameterResolver resolver : resolvers) + { + if (resolver == null) + { + throw new NullPointerException("A null ParameterResolver found." + + " Only non-null ParameterResolvers are permitted"); + } + } + m_aResolvers = resolvers; + } + + // ----- ParameterResolver interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Parameter resolve(String sName) + { + for (ParameterResolver resolver : m_aResolvers) + { + Parameter param = resolver.resolve(sName); + if (param != null) + { + return param; + } + } + return null; + } + + // ----- logging support ------------------------------------------------ + + /** + * Return a human-readable String representation of this class. + * + * @return the description of this class + */ + protected String getDescription() + { + return "Resolvers=" + Arrays.toString(m_aResolvers); + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + int cResolver = ExternalizableHelper.readInt(in); + ParameterResolver[] aResolver = new ParameterResolver[cResolver]; + + for (int i = 0; i < cResolver; i++) + { + aResolver[i] = (ParameterResolver) ExternalizableHelper.readObject(in); + } + + m_aResolvers = aResolver; + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + ParameterResolver[] aResolver = m_aResolvers; + int cResolver = aResolver.length; + + ExternalizableHelper.writeInt(out, cResolver); + for (int i = 0; i < cResolver; i++) + { + ExternalizableHelper.writeObject(out, aResolver[i]); + } + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader in) throws IOException + { + m_aResolvers = (ParameterResolver[]) in.readObjectArray(0, EMPTY_RESOLVER_ARRAY); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter out) throws IOException + { + out.writeObjectArray(0, m_aResolvers); + } + + // ----- constants ------------------------------------------------------ + + /** + * An empty array of {@link ParameterResolver}s. + */ + private static final ParameterResolver[] EMPTY_RESOLVER_ARRAY = new ParameterResolver[0]; + + /** + * The {@link ParameterResolver}s to consult when attempting to resolve + * a {@link Parameter}. + */ + @JsonbProperty("resolvers") + private ParameterResolver[] m_aResolvers; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/Expression.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/Expression.java new file mode 100644 index 0000000000000..74bc285d0ef30 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/Expression.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +/** + * A {@link Expression} represents a calculation to be evaluated at runtime, during which, one or more + * {@link Parameter}s may be required. + * + * @param the type of value returned when the {@link Expression} is evaluated + * + * @author bo 2011.06.24 + * @since Coherence 12.1.2 + */ +public interface Expression + { + /** + * Evaluates the {@link Expression} to produce a value of type T. + * + * @param resolver the {@link ParameterResolver} for resolving any parameters used by the {@link Expression} + * + * @return The result of evaluating the expression + */ + public T evaluate(ParameterResolver resolver); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/ExpressionParser.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ExpressionParser.java new file mode 100644 index 0000000000000..771b4b168c57f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ExpressionParser.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import java.text.ParseException; + +/** + * An {@link ExpressionParser} parses a {@link String} representation of some calculation to produce an + * {@link Expression}, that of which when evaluated will return an expected type of value. + * + * @author bo 2011.10.18 + * @since Coherence 12.1.2 + */ +public interface ExpressionParser + { + /** + * Attempts to parse the provided {@link String} to produce an {@link Expression} of an expected type. + * + * @param sExpression the {@link String} representation of the {@link Expression} + * @param clzResultType the type of value the {@link Expression} will return when evaluated + * + * @return an {@link Expression} that will when evaluated will produce the required type + * + * @throws ParseException when an error occurred attempting to parse the {@link String} + */ + public Expression parse(String sExpression, Class clzResultType) + throws ParseException; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/LiteralExpression.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/LiteralExpression.java new file mode 100644 index 0000000000000..47f91d8818142 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/LiteralExpression.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.ExternalizableHelper; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import javax.json.bind.annotation.JsonbProperty; + +/** + * A {@link LiteralExpression} is a literal (aka: constant) {@link Expression}. + * + * @param T the type of the literal constant + * + * @author bo 2011.06.24 + * @since Coherence 12.1.2 + */ +public class LiteralExpression + implements Expression, ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor needed for serialization. + * + */ + public LiteralExpression() + { + } + + /** + * Construct a {@link LiteralExpression}. + * + * @param the type of the constant + * @param value the value of the constant + */ + public LiteralExpression(T value) + { + m_value = value; + } + + // ----- Expression interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public T evaluate(ParameterResolver resolver) + { + return m_value; + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + m_value = (T) ExternalizableHelper.readObject(in); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + ExternalizableHelper.writeObject(out, m_value); + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader reader) throws IOException + { + m_value = (T) reader.readObject(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter writer) throws IOException + { + writer.writeObject(0, m_value); + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return String.format("LiteralExpression{value=%s}", m_value); + } + + // ----- data members --------------------------------------------------- + + /** + * The value of the constant. + */ + @JsonbProperty("value") + private T m_value; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/NullParameterResolver.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/NullParameterResolver.java new file mode 100644 index 0000000000000..a51565f5bfe51 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/NullParameterResolver.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * A {@link NullParameterResolver} is a {@link ParameterResolver} that always + * resolves named {@link Parameter}s to null. + *

+ * IMPORTANT: Developers should only use this class when they + * can't easily access the {@link ParameterResolver} provided by the CacheConfig + * getDefaultParameterResolver() method. + *

+ * In most circumstances this class is only ever used for: + * a). testing and/or b) those very rare occasions that you need a + * {@link ParameterResolver} and want all parameters resolved to null. + *

+ * NOTE: This class does not provide a static INSTANCE + * declaration by design. Developers are not meant to use this class very often + * and hence we discourage this by not providing an INSTANCE declaration. + *

+ * + * @author bo 2011.09.27 + * @since Coherence 12.1.2 + */ +public class NullParameterResolver + implements ParameterResolver, ExternalizableLite, PortableObject + { + /** + * Default constructor needed for serialization. + */ + public NullParameterResolver() + { + } + + /** + * {@inheritDoc} + */ + @Override + public Parameter resolve(String sName) + { + return null; + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader reader) throws IOException + { + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter writer) throws IOException + { + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/Parameter.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/Parameter.java new file mode 100644 index 0000000000000..ec67d6cc4a517 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/Parameter.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.Base; +import com.tangosol.util.ExternalizableHelper; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import javax.json.bind.annotation.JsonbProperty; + +/** + * A {@link Parameter} represents an optionally named and optionally explicitly typed {@link Expression}. + *

+ * Terminology: + *

+ * A {@link Parameter} that doesn't have a name, or when the name is irrelevant, is referred to as an + * Actual {@link Parameter}. That is, only the value of the {@link Parameter} is of any importance. + *

+ * A {@link Parameter} with a name and/or when the name is of importance, is referred to as a Formal + * {@link Parameter} or Argument. + *

+ * NOTE: This class is used to represent Actual and Formal {@link Parameter}s. + * + * @author bo 2011.06.22 + * @since Coherence 12.1.2 + */ +public class Parameter + implements Expression, ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor needed for serialization. + */ + public Parameter() + { + } + + /** + * Construct an implicitly typed {@link Parameter} based on an {@link Expression}. + * + * @param sName the name of the {@link Parameter} + * @param expression the {@link Expression} for the {@link Parameter} + */ + public Parameter(String sName, Expression expression) + { + m_sName = sName; + m_clzType = null; + m_expression = expression; + } + + /** + * Construct an implicitly typed {@link Object}-based {@link Parameter}. + * + * @param sName the name of the {@link Parameter} + * @param oValue the value for the {@link Parameter} + */ + public Parameter(String sName, Object oValue) + { + m_sName = sName; + m_clzType = null; + m_expression = new LiteralExpression(oValue); + } + + /** + * Construct an {@link Parameter}. + * + * @param sName the name of the {@link Parameter} + * @param clzType the expected type of the {@link Parameter} + * @param expression the {@link Expression} for the {@link Parameter} + */ + public Parameter(String sName, Class clzType, Expression expression) + { + m_sName = sName; + m_clzType = clzType; + m_expression = expression; + } + + /** + * Construct an explicitly typed {@link Object}-based {@link Parameter}. + * + * @param sName the name of the {@link Parameter} + * @param clzType the expected type of the {@link Parameter} + * @param oValue the value for the {@link Parameter} + */ + public Parameter(String sName, Class clzType, Object oValue) + { + m_sName = sName; + m_clzType = clzType; + m_expression = new LiteralExpression(oValue); + } + + // ----- Expression interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public Value evaluate(ParameterResolver resolver) + { + // evaluate whatever the expression is + Object oValue = m_expression.evaluate(resolver); + + // make sure the result is a value + Value value = oValue instanceof Value ? (Value) oValue : new Value(oValue); + + // when the parameter is explicitly typed, attempt to coerce the type into that which is specified + if (isExplicitlyTyped()) + { + return new Value(value.as(m_clzType)); + } + else + { + return value; + } + } + + // ----- Object interface ----------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + String sResult = ""; + + if (m_sName != null) + { + sResult += "name=" + m_sName; + } + + if (m_clzType != null) + { + sResult += (sResult.isEmpty() ? "" : ", ") + "type=" + m_clzType; + } + + sResult += (sResult.isEmpty() ? "" : ", ") + "expression=" + m_expression; + + return "Parameter{" + sResult + "}"; + } + + // ----- Parameter methods ---------------------------------------------- + + /** + * Obtains the name of the {@link Parameter}. + * + * @return a {@link String} representing the name of the {@link Parameter} + */ + public String getName() + { + return m_sName; + } + + /** + * Obtains the explicitly specified type of the {@link Parameter}. + * + * @return a {@link Class} representing the type of the {@link Parameter} + */ + public Class getExplicitType() + { + return m_clzType; + } + + /** + * Obtains if an expected/actual type of the {@link Parameter} has been specified/is known. + * + * @return true if the type of the {@link Parameter} is known/specified. + * ie: {@link #getExplicitType()} is not null, otherwise returns false. + */ + public boolean isExplicitlyTyped() + { + return m_clzType != null; + } + + /** + * Obtains the {@link Expression} for the {@link Parameter}. + * + * @return the {@link Expression} for the {@link Parameter} + */ + public Expression getExpression() + { + return m_expression; + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + m_sName = ExternalizableHelper.readSafeUTF(in); + m_expression = (Expression) ExternalizableHelper.readObject(in); + + String sClzName = ExternalizableHelper.readSafeUTF(in); + if (sClzName.length() > 0) + { + try + { + m_clzType = Class.forName(sClzName); + } + catch (ClassNotFoundException e) + { + throw Base.ensureRuntimeException(e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + ExternalizableHelper.writeSafeUTF(out, m_sName); + ExternalizableHelper.writeObject(out, m_expression); + ExternalizableHelper.writeSafeUTF(out, m_clzType == null ? "" : m_clzType.getName()); + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader reader) throws IOException + { + m_sName = reader.readString(0); + m_expression = (Expression) reader.readObject(1); + + String sClzName = reader.readString(2); + if (sClzName.length() > 0) + { + try + { + m_clzType = Class.forName(sClzName); + } + catch (ClassNotFoundException e) + { + throw Base.ensureRuntimeException(e); + } + } + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter writer) throws IOException + { + writer.writeString(0, m_sName); + writer.writeObject(1, m_expression); + writer.writeString(2, m_clzType == null ? "" : m_clzType.getName()); + } + + // ----- data members --------------------------------------------------- + + /** + * The name of the {@link Parameter}. + */ + @JsonbProperty("name") + private String m_sName; + + /** + * (optional) The expected type of value of the {@link Parameter}. + *

+ * NOTE: when null a type has not been specified. + */ + @JsonbProperty("classType") + private Class m_clzType; + + /** + * The {@link Expression} representing the value of the {@link Parameter}. + */ + @JsonbProperty("expression") + private Expression m_expression; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/ParameterResolver.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ParameterResolver.java new file mode 100644 index 0000000000000..a92df3d4bb86e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ParameterResolver.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +/** + * A {@link ParameterResolver} provides a mechanism resolve and lookup named {@link Parameter}s. + * + * @author bo 2011.06.22 + * @since Coherence 12.1.2 + */ +public interface ParameterResolver + { + /** + * Obtains the specified named {@link Parameter}. + * + * @param sName the name of the {@link Parameter} + * + * @return the {@link Parameter} or null if the {@link Parameter} can't be resolved + */ + public Parameter resolve(String sName); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/PropertiesParameterResolver.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/PropertiesParameterResolver.java new file mode 100644 index 0000000000000..682a7b1cbbd80 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/PropertiesParameterResolver.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import java.util.Map; +import java.util.Properties; + +/** + * A {@link PropertiesParameterResolver} is a {@link ParameterResolver} that is + * based on a {@link Properties} instance. + * + * @author bo 2011.06.22 + * @since Coherence 12.1.2 + */ +public class PropertiesParameterResolver + implements ParameterResolver + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a {@link PropertiesParameterResolver} using a {@link Map} + * of values. + * + * @param map a {@link Map} of Strings to Strings defining the properties + * (the map values are copied) + */ + public PropertiesParameterResolver(Map map) + { + m_properties = new Properties(); + if (map != null) + { + for (String key : map.keySet()) + { + m_properties.put(key, map.get(key)); + } + } + } + + // ----- ParameterResolver interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Parameter resolve(String sName) + { + String sValue; + + try + { + sValue = m_properties.getProperty(sName); + } + catch (SecurityException e) + { + sValue = null; + } + + if (sValue == null) + { + return null; + } + else + { + return new Parameter(sName, sValue); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Properties} from which to resolve {@link Parameter}s. + */ + private Properties m_properties; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/ScopedParameterResolver.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ScopedParameterResolver.java new file mode 100644 index 0000000000000..7d7e73e47701e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ScopedParameterResolver.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import com.tangosol.coherence.config.ResolvableParameterList; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.util.ExternalizableHelper; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import javax.json.bind.annotation.JsonbProperty; + +/** + * A {@link ScopedParameterResolver} is a {@link ParameterResolver} implementation + * that provides the ability to "scope" {@link Parameter} definitions to either an + * inner (wrapped) or outer {@link ParameterResolver}, such that those being + * defined in the outer {@link ParameterResolver} hide those (of the same name) + * in the inner (wrapped) {@link ParameterResolver}. + *

+ * For example: Parameter "A" defined in the outer {@link ParameterResolver} + * will override and thus "hide" Parameter "A" that is defined in the inner + * {@link ParameterResolver}. + *

+ * + * @author pfm 2011.12.2 + * @since Coherence 12.1.2 + */ +public class ScopedParameterResolver + implements ParameterResolver, ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor needed for serialization. + */ + public ScopedParameterResolver() + { + m_innerResolver = new ResolvableParameterList();; + m_outerResolver = new ResolvableParameterList(); + } + + /** + * Construct a {@link ScopedParameterResolver} given the specified inner {@link ParameterResolver}. + * + * @param resolver the inner {@link ParameterResolver} + */ + public ScopedParameterResolver(ParameterResolver resolver) + { + m_innerResolver = resolver == null ? new ResolvableParameterList() : resolver; + m_outerResolver = new ResolvableParameterList(); + } + + // ----- ParameterResolver interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public Parameter resolve(String sName) + { + // see if m_object has the parameter, if not use the inner resolver. + Parameter param = m_outerResolver.resolve(sName); + + return param == null ? m_innerResolver.resolve(sName) : param; + } + + // ----- ScopedParameterResolver methods -------------------------------- + + /** + * Adds the specified {@link Parameter} to the outer {@link ParameterResolver}. + * + * @param parameter the {@link Parameter} to add + */ + public void add(Parameter parameter) + { + m_outerResolver.add(parameter); + } + + // ----- logging support ------------------------------------------------ + + /** + * Return a human-readable String representation of this class. + * + * @return the description of this class + */ + protected String getDescription() + { + return "Outer Resolver=" + m_outerResolver + ", Inner Resolver=" + m_innerResolver; + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) throws IOException + { + m_innerResolver = (ParameterResolver) ExternalizableHelper.readObject( + in); + m_outerResolver = (ResolvableParameterList) ExternalizableHelper.readObject(in); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) throws IOException + { + ExternalizableHelper.writeObject(out, m_innerResolver); + ExternalizableHelper.writeObject(out, m_outerResolver); + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader reader) throws IOException + { + m_innerResolver = (ParameterResolver) reader.readObject(0); + m_outerResolver = (ResolvableParameterList) reader.readObject(1); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter writer) throws IOException + { + writer.writeObject(0, m_innerResolver); + writer.writeObject(1, m_outerResolver); + } + + // ----- data members --------------------------------------------------- + + /** + * The inner (wrapped) {@link ParameterResolver}. + */ + @JsonbProperty("innerResolver") + private ParameterResolver m_innerResolver; + + /** + * The outer {@link ParameterResolver}. + */ + @JsonbProperty("outerResolver") + private ResolvableParameterList m_outerResolver; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/SystemEnvironmentParameterResolver.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/SystemEnvironmentParameterResolver.java new file mode 100644 index 0000000000000..321154ba55fc1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/SystemEnvironmentParameterResolver.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import com.tangosol.coherence.config.Config; + +/** + * A {@link SystemEnvironmentParameterResolver} is a {@link ParameterResolver} that is + * an environment getter for Coherence environment properties implemented using + * {@link System#getenv(String)}. + + * @author jf 2015.04.15 + * @since Coherence 12.2.1 + */ +public class SystemEnvironmentParameterResolver implements ParameterResolver + { + // ----- ParameterResolver methods -------------------------------- + /** + * Resolve Coherence system property sName + *

+ * Log a WARNING if SecurityException occurs while accessing sName. + * + * @param sName system property name + * + * @return a {@link Parameter} representing the value of sName or + * null if system property not found or if SecurityException was handled. + */ + @Override + public Parameter resolve(String sName) + { + String sValue = Config.getenv(sName); + return sValue == null ? null : new Parameter(sName, sValue); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/SystemPropertyParameterResolver.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/SystemPropertyParameterResolver.java new file mode 100644 index 0000000000000..34ca5225fe11d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/SystemPropertyParameterResolver.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import com.tangosol.coherence.config.Config; + +/** + * A {@link SystemPropertyParameterResolver} is a {@link ParameterResolver} that is + * based on property methods on {@link System}. + *

+ * Replaces {@link PropertiesParameterResolver( java.util.Properties )} since it required + * read and write access for all Properties for simple property access. + * {@link SystemPropertyParameterResolver#resolve(String)} only requires read access for property specified. + * + * @author jf 2015.04.15 + * @since Coherence 12.2.1 + */ +public class SystemPropertyParameterResolver + implements ParameterResolver + { + // ----- ParameterResolver methods -------------------------------------- + + /** + * Resolve Coherence system property sName + * + * @param sName system property name + * + * @return a {@link Parameter} representing the value of sName or + * null if system property not found or if SecurityException was handled. + */ + @Override + public Parameter resolve(String sName) + { + String sValue = Config.getProperty(sName); + + return sValue == null ? null : new Parameter(sName, sValue); + } + + // ----- SystemPropertyParameterResolver methods ------------------------ + + /** + * Return property's value as requested type. + * + * @param sName property name + * @param clzTypeValue coerce system property's value from string to instance of this class + * @param property value's target type + * @return null if property has no value or return property's value coerced from string + * to requested type. + * + * Throws exceptions listed in {@link Value#as(Class)} when coercion fails. + */ + @SuppressWarnings("unchecked") + public T resolve(String sName, Class clzTypeValue) + { + Parameter p = resolve(sName); + + return p == null ? null : p.evaluate(this).as(clzTypeValue); + } + + //----- constants ------------------------------------------------------- + /** + * This singleton instance of the {@link SystemPropertyParameterResolver}. + */ + public static final SystemPropertyParameterResolver INSTANCE = new SystemPropertyParameterResolver(); + + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/Value.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/Value.java new file mode 100644 index 0000000000000..542189ce3a3a6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/Value.java @@ -0,0 +1,526 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import com.oracle.coherence.common.base.Converter; + +import com.tangosol.run.xml.XmlValue; + +import java.lang.reflect.Constructor; + +import java.math.BigDecimal; + +import java.util.HashMap; +import java.util.UnknownFormatConversionException; + +/** + * A {@link Value} is an immutable object that represents a value whose type is unknown at compile time. + * That is, the type of the value will only be known at runtime when it's requested. + *

+ * Much like a Variant + * (wikipedia) a {@link Value} permits runtime coercion into other types, as and when required. + * + * @author bo 2011.06.05 + * @since Coherence 12.1.2 + */ +@SuppressWarnings("rawtypes") +public final class Value + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a null {@link Value}. + */ + public Value() + { + m_oValue = null; + } + + /** + * Construct an {@link Object}-based {@link Value}. + * + * @param oValue the value of the {@link Value} instance + */ + public Value(Object oValue) + { + m_oValue = oValue; + } + + /** + * Construct a {@link String}-based {@link Value}. + *

+ * Note: The provided {@link String} is trimmed for leading and trailing white-space. + * + * @param sValue the value of the {@link Value} instance + */ + public Value(String sValue) + { + m_oValue = sValue == null ? null : sValue.trim(); + } + + /** + * Construct a {@link Value}-based on another {@link Value}. + * + * @param value the value for the resulting {@link Value} instance + */ + public Value(Value value) + { + m_oValue = value.m_oValue; + } + + /** + * Construct a {@link Value} based on the string content of an {@link XmlValue}. + *

+ * Note: + *

    + *
  • The {@link XmlValue} content is used and not the xml itself. + *
  • The content will be trimmed for leading and trailing white-space. + *
+ * + * @param value the value of the {@link Value} instance + */ + public Value(XmlValue value) + { + m_oValue = value == null ? null : value.getString().trim(); + } + + /** + * Determines if the {@link Value} represents a null value. + * + * @return true if the value of the {@link Value} is null, otherwise false + */ + public boolean isNull() + { + return m_oValue == null; + } + + /** + * Obtains the underlying {@link Object} representation of the {@link Value}. + * + * @return The {@link Object} representation of the {@link Value} (may be null) + */ + public Object get() + { + return m_oValue; + } + + /** + * Determines if the {@link Value} supports conversion/coercion to the specified type. + *

+ * NOTE: This does not test whether the {@link Value} can be coerced without an exception. + * + * @param clzType the type to which the {@link Value} should be coerced + * + * @return true if type is coercable, false otherwise + */ + public boolean supports(Class clzType) + { + // determine if we can convert the value to the type + boolean fSupported = clzType.isEnum() || clzType.isAssignableFrom(m_oValue.getClass()) + || clzType.isAssignableFrom(this.getClass()) + || s_mapTypeConvertersByClass.containsKey(clzType); + + if (!fSupported && m_oValue != null) + { + // determine if we can construct an instance of the type with the value or using a string + try + { + fSupported = clzType.getConstructor(m_oValue.getClass()) != null; + } + catch (Exception e) + { + try + { + fSupported = clzType.getConstructor(String.class) != null; + } + catch (Exception e1) + { + fSupported = false; + } + } + } + + return fSupported; + } + + /** + * Attempts to return the value of the {@link Value} coerced to a specified type. + * + * @param the expected type of the value + * @param clzType the expected type of the value (the value to coerce to) + * + * @return the {@link Value} coerced in the required type + * + * @throws ClassCastException If the value of the {@link Value} can't be coerced to the specified type + * @throws NumberFormatException If the value of the {@link Value} can't be coerced to the specified type + * @throws UnknownFormatConversionException If the value of the {@link Value} can't be coerced to the specified type + */ + @SuppressWarnings("unchecked") + public T as(Class clzType) + throws ClassCastException, UnknownFormatConversionException, NumberFormatException + { + if (isNull()) + { + return null; + } + else if (clzType.isInstance(m_oValue)) + { + return (T) m_oValue; + } + else if (clzType.isAssignableFrom(this.getClass())) + { + return (T) this; + } + else if (clzType.isEnum()) + { + // determine the value as a string + String sValue = m_oValue.toString(); + + try + { + return (T) Enum.valueOf((Class) clzType, sValue); + } + catch (Exception exception) + { + // the enum is unknown/unsupported + throw new ClassCastException(String.format("The specified Enum value '%s' is unknown.", sValue)); + } + } + else + { + // attempt to use a predefined converter + Converter converter = (Converter) s_mapTypeConvertersByClass.get(clzType); + + if (converter == null) + { + try + { + // attempt to create an instance of the type using the value + Constructor constructor = clzType.getConstructor(m_oValue.getClass()); + + return (T) constructor.newInstance(m_oValue); + } + catch (Exception e1) + { + try + { + // attempt to create an instance of the type using the value + Constructor constructor = clzType.getConstructor(String.class); + + return (T) constructor.newInstance(m_oValue); + } + catch (Exception e2) + { + throw new ClassCastException(String.format("Can't convert [%s] into a [%s].", m_oValue, + clzType.toString())); + } + } + } + else + { + return converter.convert(m_oValue); + } + } + } + + /** + * {@inheritDoc} + */ + public String toString() + { + return String.format("Value{%s}", m_oValue); + } + + // ----- BigDecimalConverter class -------------------------------------- + + /** + * A {@link BigDecimalConverter} is a {@link BigDecimal}-based implementation of a type {@link Converter}. + */ + private static class BigDecimalConverter + implements Converter + { + /** + * {@inheritDoc} + */ + @Override + public BigDecimal convert(Object oValue) + { + if (oValue == null || oValue instanceof BigDecimal) + { + return (BigDecimal) oValue; + } + else + { + return new BigDecimal(oValue.toString()); + } + } + } + + // ----- BooleanConverter class ----------------------------------------- + + /** + * A {@link BooleanConverter} is a {@link Boolean}-based implementation of a type {@link Converter}. + */ + private static class BooleanConverter + implements Converter + { + /** + * {@inheritDoc} + */ + @Override + public Boolean convert(Object oValue) + { + if (oValue == null || oValue instanceof Boolean) + { + return (Boolean) oValue; + } + else + { + String sBoolean; + + if (oValue instanceof String) + { + sBoolean = (String) oValue; + } + else + { + sBoolean = oValue.toString(); + } + + sBoolean = sBoolean.trim(); + + if (sBoolean.equalsIgnoreCase("true") || sBoolean.equalsIgnoreCase("yes") + || sBoolean.equalsIgnoreCase("on")) + { + return true; + } + else if (sBoolean.equalsIgnoreCase("false") || sBoolean.equalsIgnoreCase("no") + || sBoolean.equalsIgnoreCase("off")) + { + return false; + } + else + { + throw new IllegalArgumentException(String.format( + "The value [%s] is not a boolean (true, yes, on, false, no, off)", sBoolean)); + } + } + } + } + + // ----- ByteConverter class -------------------------------------------- + + /** + * A {@link ByteConverter} is a {@link Byte}-based implementation of a type {@link Converter}. + */ + private static class ByteConverter + implements Converter + { + /** + * {@inheritDoc} + */ + @Override + public Byte convert(Object oValue) + { + if (oValue == null || oValue instanceof Byte) + { + return (Byte) oValue; + } + else + { + return Byte.parseByte(oValue.toString()); + } + } + } + + // ----- DoubleConverter class -------------------------------------------- + + /** + * A {@link DoubleConverter} is a {@link Double}-based implementation of a type {@link Converter}. + */ + private static class DoubleConverter + implements Converter + { + /** + * {@inheritDoc} + */ + @Override + public Double convert(Object oValue) + { + if (oValue == null || oValue instanceof Double) + { + return (Double) oValue; + } + else + { + return Double.parseDouble(oValue.toString()); + } + } + } + + // ----- FloatConverter class -------------------------------------------- + + /** + * A {@link FloatConverter} is a {@link Float}-based implementation of a type {@link Converter}. + */ + private static class FloatConverter + implements Converter + { + /** + * {@inheritDoc} + */ + @Override + public Float convert(Object oValue) + { + if (oValue == null || oValue instanceof Float) + { + return (Float) oValue; + } + else + { + return Float.parseFloat(oValue.toString()); + } + } + } + + // ----- IntegerConverter class -------------------------------------------- + + /** + * A {@link IntegerConverter} is a {@link Integer}-based implementation of a type {@link Converter}. + */ + private static class IntegerConverter + implements Converter + { + /** + * {@inheritDoc} + */ + @Override + public Integer convert(Object oValue) + { + if (oValue == null || oValue instanceof Integer) + { + return (Integer) oValue; + } + else + { + return Integer.parseInt(oValue.toString()); + } + } + } + + // ----- LongConverter class -------------------------------------------- + + /** + * A {@link LongConverter} is a {@link Long}-based implementation of a type {@link Converter}. + */ + private static class LongConverter + implements Converter + { + /** + * {@inheritDoc} + */ + @Override + public Long convert(Object oValue) + { + if (oValue == null || oValue instanceof Long) + { + return (Long) oValue; + } + else + { + return Long.parseLong(oValue.toString()); + } + } + } + + // ----- ShortConverter class -------------------------------------------- + + /** + * A {@link ShortConverter} is a {@link Short}-based implementation of a type {@link Converter}. + */ + private static class ShortConverter + implements Converter + { + /** + * {@inheritDoc} + */ + @Override + public Short convert(Object oValue) + { + if (oValue == null || oValue instanceof Short) + { + return (Short) oValue; + } + else + { + return Short.parseShort(oValue.toString()); + } + } + } + + // ----- StringConverter class -------------------------------------------- + + /** + * A {@link StringConverter} is a {@link String}-based implementation of a type {@link Converter}. + */ + private static class StringConverter + implements Converter + { + /** + * {@inheritDoc} + */ + @Override + public String convert(Object oValue) + { + if (oValue == null || oValue instanceof String) + { + return (String) oValue; + } + else + { + return oValue.toString(); + } + } + } + + // ----- constants ------------------------------------------------------ + + /** + * The map of type converters keyed by the desired type. Type converters will convert some raw value, usually + * a {@link String} or {@link XmlValue} into the desired type. + */ + @SuppressWarnings("serial") + private final static HashMap, Converter> s_mapTypeConvertersByClass = new HashMap, + Converter>() + { + { + put(BigDecimal.class, new BigDecimalConverter()); + put(Boolean.class, new BooleanConverter()); + put(Boolean.TYPE, new BooleanConverter()); + put(Byte.class, new ByteConverter()); + put(Byte.TYPE, new ByteConverter()); + put(Double.class, new DoubleConverter()); + put(Double.TYPE, new DoubleConverter()); + put(Float.class, new FloatConverter()); + put(Float.TYPE, new FloatConverter()); + put(Integer.class, new IntegerConverter()); + put(Integer.TYPE, new IntegerConverter()); + put(Long.class, new LongConverter()); + put(Long.TYPE, new LongConverter()); + put(Short.class, new ShortConverter()); + put(Short.TYPE, new ShortConverter()); + put(String.class, new StringConverter()); + } + }; + + // ----- data members --------------------------------------------------- + + /** + * The value of the {@link Value}. + */ + private final Object m_oValue; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/ValueMacroExpression.java b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ValueMacroExpression.java new file mode 100644 index 0000000000000..4896e3fa911f7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/ValueMacroExpression.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.expression; + +import com.tangosol.io.ExternalizableLite; +import com.tangosol.io.pof.PofReader; +import com.tangosol.io.pof.PofWriter; +import com.tangosol.io.pof.PortableObject; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.util.Base; +import com.tangosol.util.ExternalizableHelper; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * A {@link ValueMacroExpression} is a string value potentially containing expandable macros. + *

+ * Resolving the expression performs macro expansion. The macro syntax is ${system-property default-value}. + * Thus, a value of near-${coherence.client direct} is macro expanded by default to near-direct. + * If system property coherence.client is set to remote, then the value would be expanded to near-remote. + * + * @author jf 2015.05.18 + * @since Coherence 12.2.1 + */ +public class ValueMacroExpression + implements Expression, ExternalizableLite, PortableObject + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor needed for serialization. + * + */ + public ValueMacroExpression() + { + } + + /** + * Construct a {@link ValueMacroExpression}. + * + * @param value the value that potentially contains a macro expression. + */ + public ValueMacroExpression(String value) + { + m_sValue = value; + } + + // ----- Expression interface ------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String evaluate(ParameterResolver resolver) + { + String sValue = m_sValue; + + if (sValue != null) + { + // replace in-lined properties i.e. ${prop-name default-value} using resolver + int nCount = 0; + for (int ofStart = sValue.indexOf("${"); ofStart >= 0; ofStart = sValue.indexOf("${"), nCount++) + { + int ofEnd = sValue.indexOf('}', ofStart); + + if (ofEnd == -1) + { + // missing closing } so no macro to process here + break; + } + + String sMacro = sValue.substring(ofStart, ofEnd + 1); + String sDefault; + String sProp; + + ofStart = sMacro.indexOf(' '); + + if (ofStart >= 0) + { + sDefault = sMacro.substring(ofStart, sMacro.length() - 1).trim(); + sProp = sMacro.substring(2, ofStart); + } + else + { + sDefault = ""; + sProp = sMacro.substring(2, sMacro.length() - 1); + } + + try + { + Parameter p = resolver.resolve(sProp); + String sPropValue = p == null ? sDefault : p.evaluate(resolver).as(String.class); + + if (sPropValue.contains("${" + sProp) && sPropValue.contains("}") || nCount > MAX_MACRO_EXPANSIONS) + { + CacheFactory.log("SystemPropertyPreprocessor: using default value of \"" + sDefault + "\", detected recursive macro definition in system property " + + sProp + " with the value of \"" + sPropValue + "\" ", Base.LOG_ERR); + sPropValue = sDefault; + } + sValue = sValue.replace(sMacro, sPropValue); + } + catch (Exception e) + { + sValue = sValue.replace(sMacro, sDefault); + } + } + } + + return sValue; + } + + // ----- ValueMacroExpression methods ------------------------------------ + + /** + * Check if string contains a macro. + * + * @param sValue string potentially containing a macro + * @return true iff the string value contains a macro + */ + static public boolean containsMacro(String sValue) + { + if (sValue == null) + { + return false; + } + + int ofStart = sValue.indexOf("${"); + + return ofStart >= 0 && sValue.indexOf('}', ofStart) > 0; + } + + /** + * Check if this contains a macro. + * + * @return true iff this contains a macro + */ + public boolean containsMacro() + { + return containsMacro(m_sValue); + } + + // ----- ExternalizableLite interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(DataInput in) + throws IOException + { + m_sValue = ExternalizableHelper.readObject(in); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(DataOutput out) + throws IOException + { + ExternalizableHelper.writeObject(out, m_sValue); + } + + // ----- PortableObject interface --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public void readExternal(PofReader reader) + throws IOException + { + m_sValue = reader.readObject(0); + } + + /** + * {@inheritDoc} + */ + @Override + public void writeExternal(PofWriter writer) + throws IOException + { + writer.writeObject(0, m_sValue); + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return String.format("ValueMacroExpression[value=%s]", m_sValue); + } + + // ----- constants ------------------------------------------------------ + + /** + * Avoid recursive macro expansions that never return. No need for more than 20 macro expansions on + * one value. + */ + public static int MAX_MACRO_EXPANSIONS = 20; + + + // ----- data members --------------------------------------------------- + + /** + * The String value. + */ + private String m_sValue; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/expression/package.html b/prj/coherence-core/src/main/java/com/tangosol/config/expression/package.html new file mode 100644 index 0000000000000..3e0c0b2dcf983 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/expression/package.html @@ -0,0 +1,5 @@ + +Defines classes and interfaces for representing and evaluating expressions. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/injection/Injector.java b/prj/coherence-core/src/main/java/com/tangosol/config/injection/Injector.java new file mode 100644 index 0000000000000..03bdacbb00708 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/injection/Injector.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.injection; + +import com.tangosol.config.annotation.Injectable; + +import com.tangosol.util.ResourceResolver; + +/** + * An {@link Injector} is responsible for injecting resolved values into Java + * objects. + * + * @see Injectable + * @see ResourceResolver + * + * @author bo 2012.09.17 + * @since Coherence 12.1.2 + */ +public interface Injector + { + /** + * Attempts to inject appropriate values provided by a + * {@link ResourceResolver} into a specified object. + * + * @param object the object in which to inject values + * @param resolver the {@link ResourceResolver} providing values to inject + */ + public T inject(T object, ResourceResolver resolver); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/injection/SimpleInjector.java b/prj/coherence-core/src/main/java/com/tangosol/config/injection/SimpleInjector.java new file mode 100644 index 0000000000000..e16dec251547a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/injection/SimpleInjector.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.injection; + +import com.tangosol.config.annotation.Injectable; + +import com.tangosol.net.CacheFactory; + +import com.tangosol.util.Base; +import com.tangosol.util.ResourceResolver; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * A simple implementation of an {@link Injector} that resolves and + * appropriately calls setter methods annotated with {@link Injectable} with + * required values. + * + * @author bo 2012.09.17 + * @since Coherence 12.1.2 + */ +public class SimpleInjector + implements Injector + { + + // ----- Injector methods ----------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public T inject(T object, ResourceResolver resolver) + { + if (object != null && resolver != null) + { + Class clsObject = object.getClass(); + + for (Method method : clsObject.getMethods()) + { + int modifiers = method.getModifiers(); + + if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && !Modifier.isAbstract(modifiers) + && method.getParameterTypes().length == 1) + { + Class clsResource = method.getParameterTypes()[0]; + + Injectable injectable = method.getAnnotation(Injectable.class); + + if (injectable != null) + { + String sResourceName = injectable.value().trim(); + + Object oResource; + + if (sResourceName.isEmpty()) + { + oResource = resolver.getResource(clsResource); + } + else + { + try + { + oResource = resolver.getResource(clsResource, sResourceName); + } + catch (Exception e) + { + oResource = null; + CacheFactory.log(String.format( + "Failed to lookup resource %s resource-type %s for method %s due to %s", + sResourceName, clsResource.getCanonicalName(), + method, e), Base.LOG_WARN); + } + } + + // null resources are not injectable + if (oResource != null) { + try + { + method.invoke(object, oResource); + } + catch (Exception e) + { + CacheFactory.log(String.format( + "Failed to inject resource %s into %s using method %s due to %s", oResource, object, + method, e), Base.LOG_WARN); + } + } + } + } + } + } + + return object; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/package.html b/prj/coherence-core/src/main/java/com/tangosol/config/package.html new file mode 100644 index 0000000000000..3e07d1408b95a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/package.html @@ -0,0 +1,7 @@ + +Defines commonly required, generic and helper classes, interfaces and +annotations used to simplify the configuration of objects, services, caches and +components. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/AbstractNamespaceHandler.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/AbstractNamespaceHandler.java new file mode 100644 index 0000000000000..85f6b6ac08c29 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/AbstractNamespaceHandler.java @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.run.xml.XmlAttribute; +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.Base; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; + +import java.net.URI; + +import java.util.HashMap; + +/** + * An {@link AbstractNamespaceHandler} provides a base implementation of a {@link NamespaceHandler} + * with support for implicit and explicit registration of {@link ElementProcessor}s and + * {@link AttributeProcessor}s for those xml attributes and elements occurring in the associated xml namespace. + * + * @author bo 2011.06.20 + * @since Coherence 12.1.2 + */ +public abstract class AbstractNamespaceHandler + implements NamespaceHandler + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an {@link AbstractNamespaceHandler}. + */ + public AbstractNamespaceHandler() + { + m_documentPreprocessor = null; + m_mapElementProcessors = new HashMap>(); + m_mapAttributeProcessors = new HashMap>(); + + // attempt to automatically register all Element and Attribute Processors + // that are declared as non-abstract static inner classes + autoRegisterInnerClassProcessors(); + } + + // ----- NamespaceHandler interface ------------------------------------- + + /** + * {@inheritDoc} + */ + public DocumentPreprocessor getDocumentPreprocessor() + { + return m_documentPreprocessor; + } + + /** + * {@inheritDoc} + */ + public AttributeProcessor getAttributeProcessor(XmlAttribute attribute) + { + AttributeProcessor processor = m_mapAttributeProcessors.get(attribute.getQualifiedName().getLocalName()); + + return (processor == null) ? onUnknownAttribute(attribute) : processor; + } + + /** + * {@inheritDoc} + */ + @Override + public ElementProcessor getElementProcessor(XmlElement element) + { + ElementProcessor processor = m_mapElementProcessors.get(element.getQualifiedName().getLocalName()); + + return (processor == null) ? onUnknownElement(element) : processor; + } + + /** + * {@inheritDoc} + */ + @Override + public void onStartNamespace(ProcessingContext context, XmlElement element, String prefix, URI uri) + { + /** + * Override this method to provide specific NamespaceHandler declaration semantics. + */ + } + + /** + * {@inheritDoc} + */ + @Override + public void onEndNamespace(ProcessingContext context, XmlElement element, String prefix, URI uri) + { + /** + * Override this method to provide specific NamespaceHandler disposal semantics + */ + } + + // ----- AbstractNamespaceHandler interface ----------------------------- + + /** + * Sets the {@link DocumentPreprocessor} for the {@link NamespaceHandler}. + * + * @param preprocessor the {@link DocumentPreprocessor} + */ + public void setDocumentPreprocessor(DocumentPreprocessor preprocessor) + { + m_documentPreprocessor = preprocessor; + } + + /** + * Registers the specified processor as an {@link ElementProcessor} or + * {@link AttributeProcessor} (based on the interfaces it implements) + * using the {@link XmlSimpleName} annotation to determine the localName + * of the said processor. + *

+ * Note: The specified class must have a no-args constructor. + * + * @param clzProcessor A class that implements either (or both) + * {@link ElementProcessor} or {@link AttributeProcessor} + * that is annotated with @{@link XmlSimpleName} to + * specify it's localName. + */ + public void registerProcessor(Class clzProcessor) + { + if (clzProcessor.isAnnotationPresent(XmlSimpleName.class) + && (ElementProcessor.class.isAssignableFrom(clzProcessor) + || AttributeProcessor.class.isAssignableFrom(clzProcessor))) + { + try + { + String sLocalName = clzProcessor.getAnnotation(XmlSimpleName.class).value(); + Object oProcessor = clzProcessor.newInstance(); + + if (oProcessor instanceof ElementProcessor) + { + registerProcessor(sLocalName, (ElementProcessor) oProcessor); + } + + if (oProcessor instanceof AttributeProcessor) + { + registerProcessor(sLocalName, (AttributeProcessor) oProcessor); + } + } + catch (InstantiationException e) + { + Base.ensureRuntimeException(e, "Specified processor class requires a no-args constructor."); + } + catch (IllegalAccessException e) + { + Base.ensureRuntimeException(e); + } + } + } + + /** + * Registers an {@link ElementProcessor} for {@link XmlElement}s with a name with in the context of + * the {@link NamespaceHandler} namespace. + * + * @param sLocalName The local name of the {@link XmlElement} to be processed with the {@link ElementProcessor} + * @param processor The {@link ElementProcessor} + */ + public void registerProcessor(String sLocalName, ElementProcessor processor) + { + m_mapElementProcessors.put(sLocalName, processor); + } + + /** + * Registers an {@link AttributeProcessor} for {@link XmlAttribute}s with the specified name. + * + * @param sLocalName The local name of the {@link XmlAttribute} to be processed with the {@link AttributeProcessor} + * @param processor The {@link AttributeProcessor} + */ + public void registerProcessor(String sLocalName, AttributeProcessor processor) + { + m_mapAttributeProcessors.put(sLocalName, processor); + } + + /** + * Registers (internally creates) an appropriate {@link ElementProcessor} for {@link XmlElement}s with + * the specified local name so that they produce the specified type of value when processed. + * + * @param the type of value the registered {@link ElementProcessor} will produce + * @param sLocalName The local name of the {@link XmlElement}s to be associated with the type + * @param clzType The {@link Class} of value that should be produced for the {@link XmlElement} + */ + @SuppressWarnings("unchecked") + public void registerElementType(String sLocalName, Class clzType) + { + if (ElementProcessor.class.isAssignableFrom(clzType)) + { + try + { + ElementProcessor processor = (ElementProcessor) clzType.newInstance(); + + registerProcessor(sLocalName, processor); + } + catch (Exception exception) + { + throw new RuntimeException(String.format("Can't instantiate the ElementProcessor [%s]\n", clzType), + exception); + } + } + else + { + // default to the SimpleElementHandler with specified type + registerProcessor(sLocalName, new SimpleElementProcessor(clzType)); + } + } + + /** + * Registers (internally creates) an appropriate {@link AttributeProcessor} for {@link XmlAttribute}s with + * the specified local name so that they produce the specified type of value when processed. + * + * @param the type of value the registered {@link AttributeProcessor} will produce + * @param sLocalName The local name of the {@link XmlElement}s to be associated with the type + * @param clzType The {@link Class} of value that should be produced for the {@link XmlAttribute} + */ + @SuppressWarnings("unchecked") + public void registerAttributeType(String sLocalName, Class clzType) + { + if (AttributeProcessor.class.isAssignableFrom(clzType)) + { + try + { + AttributeProcessor processor = (AttributeProcessor) clzType.newInstance(); + + registerProcessor(sLocalName, processor); + } + catch (Exception exception) + { + throw new RuntimeException(String.format("Can't instantiate the AttributeProcessor [%s]\n", clzType), + exception); + } + } + else + { + registerProcessor(sLocalName, new SimpleAttributeProcessor(clzType)); + } + } + + /** + * A call-back to handle when an {@link XmlAttribute} is unknown to the {@link NamespaceHandler}. + *

+ * Override this method to provide specialized foreign {@link XmlAttribute} processing. By default, + * null will be returned for unknown {@link XmlAttribute}s. + * + * @param attribute The {@link XmlAttribute} that was unknown. + * + * @return An appropriate {@link AttributeProcessor} that may be used to process the unknown {@link XmlAttribute} + * or null if no special processing should occur. + */ + protected AttributeProcessor onUnknownAttribute(XmlAttribute attribute) + { + // SKIP: by default we don't do anything if the attribute is unknown + return null; + } + + /** + * A call-back to handle when an {@link XmlElement} is unknown to the {@link NamespaceHandler}. + *

+ * Override this method to provide specialized foreign {@link XmlElement} processing. + * By default, unknown {@link XmlElement} will return an {@link ElementProcessor} that when attempting + * to process the said element, will throw a {@link ConfigurationException}. + * + * @param element The {@link XmlElement} that was unknown. + * + * @return An appropriate {@link ElementProcessor} that may be used to process the unknown {@link XmlElement} + * or null if no special processing should occur. + */ + protected ElementProcessor onUnknownElement(XmlElement element) + { + // SKIP: by default we don't do anything if the element is unknown + return null; + } + + /** + * Obtains the {@link AttributeProcessor} registered with the specified + * localName (in the namespace). + * + * @param localName the name of the {@link AttributeProcessor} to return + * + * @return the {@link AttributeProcessor} or null if not found + */ + public AttributeProcessor getAttributeProcessor(String localName) + { + return m_mapAttributeProcessors.get(localName); + } + + /** + * Obtains the {@link ElementProcessor} registered with the specified + * localName (in the namespace). + * + * @param localName the name of the {@link ElementProcessor} to return + * + * @return the {@link ElementProcessor} or null if not found + */ + public ElementProcessor getElementProcessor(String localName) + { + return m_mapElementProcessors.get(localName); + } + + /** + * Using reflection, automatically locates and registers each of the inner-class {@link ElementProcessor} + * and {@link AttributeProcessor} definitions with the {@link NamespaceHandler} namespace. + */ + void autoRegisterInnerClassProcessors() + { + for (Class clzDeclared : getClass().getDeclaredClasses()) + { + if (!clzDeclared.isInterface() && !clzDeclared.isAnnotation()) + { + String sProcessorName = getProcessorName(clzDeclared); + + if (sProcessorName != null && !sProcessorName.isEmpty() + && (ElementProcessor.class.isAssignableFrom(clzDeclared) + || AttributeProcessor.class.isAssignableFrom(clzDeclared))) + { + try + { + Object oProcessor; + + if (Modifier.isStatic(clzDeclared.getModifiers())) + { + // static inner class + oProcessor = clzDeclared.newInstance(); + } + else + { + // non-static inner class + Constructor constructor = clzDeclared.getConstructor(new Class[] {getClass()}); + + oProcessor = constructor.newInstance(new Object[] {this}); + } + + if (ElementProcessor.class.isAssignableFrom(clzDeclared)) + { + registerProcessor(sProcessorName, (ElementProcessor) oProcessor); + } + + if (AttributeProcessor.class.isAssignableFrom(clzDeclared)) + { + registerProcessor(sProcessorName, (AttributeProcessor) oProcessor); + } + } + catch (Exception exception) + { + throw new RuntimeException(String.format("Can't instantiate the Processor [%s]\n", + clzDeclared), exception); + } + } + } + } + } + + /** + * Determine xml element/attribute name of the specified {@link ElementProcessor} + * / {@link AttributeProcessor} non-abstract {@link Class} that has + * been annotated by {@link XmlSimpleName}. + *

+ * A name for the processor is only returned if the specified class either + * implements the {@link ElementProcessor} or {@link AttributeProcessor} interfaces + * and is non-abstract with an {@link XmlSimpleName} annotation. Should any + * of these conditions fail, null is returned. + * + * @param clzProcessor The {@link Class} from which to determine an appropriate name. + * + * @return The name of the processor + */ + String getProcessorName(Class clzProcessor) + { + if (clzProcessor != null && clzProcessor.isAnnotationPresent(XmlSimpleName.class)) + { + // use the name as specified by the annotation + String sProcessorName = ((XmlSimpleName) clzProcessor.getAnnotation(XmlSimpleName.class)).value(); + + // now ensure that the class is non-abstract, static with a no-args constructor + int nModifiers = clzProcessor.getModifiers(); + + if (Modifier.isAbstract(nModifiers) || clzProcessor.isAnnotation() || clzProcessor.isInterface()) + { + return null; + } + else + { + return sProcessorName; + } + } + else + { + return null; + } + } + + // ----- data members --------------------------------------------------- + + /** + * The registered {@link DocumentPreprocessor} for the {@link NamespaceHandler}. + */ + private DocumentPreprocessor m_documentPreprocessor; + + /** + * The registered {@link AttributeProcessor}(s) for the {@link NamespaceHandler}. + */ + private HashMap> m_mapAttributeProcessors; + + /** + * The registered {@link ElementProcessor}(s) for the {@link NamespaceHandler}. + */ + private HashMap> m_mapElementProcessors; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/AttributeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/AttributeProcessor.java new file mode 100644 index 0000000000000..bcb70cd8eb078 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/AttributeProcessor.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.run.xml.XmlAttribute; + +/** + * An {@link AttributeProcessor} is responsible for processing {@link XmlAttribute} content + * to return a strongly-typed value. + * + * @param the type of value that will be returned by the {@link AttributeProcessor} + * + * @author bo 2011.06.14 + * @since Coherence 12.1.2 + */ +public interface AttributeProcessor + { + /** + * Process an {@link XmlAttribute} and return a specific type of value. + * + * @param context the {@link ProcessingContext} in which the {@link XmlAttribute} is being processed + * @param attribute the {@link XmlAttribute} to be processed + * + * @throws ConfigurationException when a configuration problem was encountered + * + * @return a value of type T + */ + public T process(ProcessingContext context, XmlAttribute attribute) + throws ConfigurationException; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/ConditionalElementProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/ConditionalElementProcessor.java new file mode 100644 index 0000000000000..e66fbd2c0a95d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/ConditionalElementProcessor.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link ConditionalElementProcessor} is an {@link ElementProcessor} that supports conditionally + * processing {@link XmlElement}s. Unlike a regular {@link ElementProcessor}, when a {@link ProcessingContext} + * encounters a {@link ConditionalElementProcessor}, it will first query the said processor to + * determine if it should process an {@link XmlElement}. + * + * @param the type of value that will be returned by the {@link ConditionalElementProcessor}, + * should an {@link XmlElement} be processed. + * + * @author bo 2013.09.14 + * @since Coherence 12.1.2 + */ +public interface ConditionalElementProcessor + extends ElementProcessor + { + /** + * Determines if the specified {@link XmlElement} should be processed. + * + * @param context the {@link ProcessingContext} in which the {@link XmlElement} is being processed + * @param xmlElement the {@link XmlElement} that would be processed + * + * @throws ConfigurationException when a configuration problem was encountered + * + * @return true if the {@link XmlElement} should be processed + */ + public boolean accepts(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/DefaultProcessingContext.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/DefaultProcessingContext.java new file mode 100644 index 0000000000000..fe8481f502220 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/DefaultProcessingContext.java @@ -0,0 +1,1729 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.annotation.Injectable; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.ExpressionParser; +import com.tangosol.config.expression.LiteralExpression; +import com.tangosol.config.expression.Parameter; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.expression.Value; +import com.tangosol.config.xml.DocumentProcessor.Dependencies; + +import com.tangosol.run.xml.QualifiedName; +import com.tangosol.run.xml.SimpleAttribute; +import com.tangosol.run.xml.XmlAttribute; +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlHelper; +import com.tangosol.run.xml.XmlValue; + +import com.tangosol.util.ClassHelper; +import com.tangosol.util.ExternalizableHelper; +import com.tangosol.util.ResourceRegistry; +import com.tangosol.util.UUID; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import java.net.URI; +import java.net.URISyntaxException; + +import java.text.ParseException; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +import java.util.Map.Entry; + +import java.util.Queue; +import java.util.Set; +import java.util.SortedSet; +import java.util.StringTokenizer; +import java.util.TreeSet; + +/** + * The default implementation of a {@link ProcessingContext}. + * + * @author bo 2011.06.14 + * @since Coherence 12.1.2 + */ +public class DefaultProcessingContext + implements ProcessingContext, AutoCloseable + { + // ----- constructors ---------------------------------------------------- + + /** + * Constructs a root {@link ProcessingContext} using default {@link DocumentProcessor} {@link DocumentProcessor.Dependencies}. + */ + public DefaultProcessingContext() + { + this((Dependencies) null); + } + + /** + * Constructs a root {@link ProcessingContext} with the specified {@link DocumentProcessor} {@link DocumentProcessor.Dependencies}. + * + * @param dependencies the {@link DocumentProcessor.Dependencies} for the {@link ProcessingContext} + */ + public DefaultProcessingContext(Dependencies dependencies) + { + m_dependencies = dependencies == null ? new DocumentProcessor.DefaultDependencies() : dependencies; + m_ctxParent = null; + m_xmlElement = null; + m_mapNamespaceURIsByPrefix = new LinkedHashMap(); + m_mapNamespaceHandlersByURI = new LinkedHashMap(); + m_mapCookiesByType = new HashMap, HashMap>(); + m_mapPropertyPaths = new HashMap(); + m_mapAttributeProcessorsByType = new HashMap, AttributeProcessor>(); + m_mapElementProcessorsByType = new HashMap, ElementProcessor>(); + m_setProcessedChildElements = new HashSet(); + } + + /** + * Constructs a root {@link ProcessingContext} for a given {@link XmlElement} using default DocumentProcessor + * dependencies. + * + * @param xmlElement the {@link XmlElement} for the {@link ProcessingContext} + */ + public DefaultProcessingContext(XmlElement xmlElement) + { + this(new DocumentProcessor.DefaultDependencies()); + m_ctxParent = null; + m_xmlElement = xmlElement; + } + + /** + * Constructs a sub-{@link ProcessingContext} of another {@link ProcessingContext}. + * + * @param ctxParent the parent {@link ProcessingContext} for this {@link ProcessingContext} + * @param xmlElement the {@link XmlElement} for the sub-{@link ProcessingContext} + */ + public DefaultProcessingContext(DefaultProcessingContext ctxParent, XmlElement xmlElement) + { + this(ctxParent.getDependencies()); + m_ctxParent = ctxParent; + m_xmlElement = xmlElement; + } + + /** + * Constructs a root {@link ProcessingContext} for a given {@link XmlElement}. + * + * @param dependencies the {@link DocumentProcessor.Dependencies} for the {@link ProcessingContext} + * @param xmlElement the {@link XmlElement} for the {@link ProcessingContext} + */ + public DefaultProcessingContext(Dependencies dependencies, XmlElement xmlElement) + { + this(dependencies); + m_ctxParent = null; + m_xmlElement = xmlElement; + } + + // ----- ResourceResolver interface ------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public R getResource(Class clsResource) + { + R resource = getCookie(clsResource); + + if (resource == null && getResourceRegistry() != null) + { + resource = getResourceRegistry().getResource(clsResource); + } + + return resource; + } + + /** + * {@inheritDoc} + */ + @Override + public R getResource(Class clsResource, String sResourceName) + { + R resource = getCookie(clsResource, sResourceName); + + if (resource == null && getResourceRegistry() != null) + { + resource = getResourceRegistry().getResource(clsResource, sResourceName); + } + + return resource; + } + + // ----- ProcessingContext interface ------------------------------------ + + /** + * {@inheritDoc} + */ + @Override + public ResourceRegistry getResourceRegistry() + { + return m_dependencies.getResourceRegistry(); + } + + /** + * {@inheritDoc} + */ + @Override + public ParameterResolver getDefaultParameterResolver() + { + return m_dependencies.getDefaultParameterResolver(); + } + + /** + * {@inheritDoc} + */ + @Override + public ClassLoader getContextClassLoader() + { + return m_dependencies.getContextClassLoader(); + } + + /** + * {@inheritDoc} + */ + @Override + public void addCookie(Class clzCookie, String sName, T value) + { + HashMap mapCookiesByName = m_mapCookiesByType.get(clzCookie); + + if (mapCookiesByName == null) + { + mapCookiesByName = new HashMap(); + m_mapCookiesByType.put(clzCookie, mapCookiesByName); + } + + mapCookiesByName.put(sName, value); + } + + /** + * {@inheritDoc} + */ + @Override + public void addCookie(Class clzCookie, T cookie) + { + addCookie(clzCookie, clzCookie.getName(), cookie); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public T getCookie(Class clzCookie, String sName) + { + HashMap mapCookiesByName = m_mapCookiesByType.get(clzCookie); + T cookie = mapCookiesByName == null ? null : (T) mapCookiesByName.get(sName); + + return cookie == null ? (isRootContext() ? null : m_ctxParent.getCookie(clzCookie, sName)) : cookie; + } + + /** + * {@inheritDoc} + */ + @Override + public T getCookie(Class clzCookie) + { + return clzCookie == null ? null : getCookie(clzCookie, clzCookie.getName()); + } + + /** + * {@inheritDoc} + */ + @Override + public void definePropertyPath(String sBeanPropertyName, String sXmlPath) + { + m_mapPropertyPaths.put(sBeanPropertyName, sXmlPath); + } + + /** + * {@inheritDoc} + */ + @Override + public void registerProcessor(Class clzType, AttributeProcessor processor) + { + m_mapAttributeProcessorsByType.put(clzType, processor); + } + + /** + * {@inheritDoc} + */ + @Override + public void registerProcessor(Class clzType, ElementProcessor processor) + { + m_mapElementProcessorsByType.put(clzType, processor); + } + + /** + * {@inheritDoc} + */ + @Override + public void registerAttributeType(Class clzType) + { + registerProcessor(clzType, new SimpleAttributeProcessor(clzType)); + } + + /** + * {@inheritDoc} + */ + @Override + public void registerElementType(Class clzType) + { + registerProcessor(clzType, new SimpleElementProcessor(clzType)); + } + + /** + * {@inheritDoc} + */ + @Override + public ExpressionParser getExpressionParser() + { + return m_dependencies.getExpressionParser(); + } + + /** + * {@inheritDoc} + */ + public Object processDocument(XmlElement xmlElement) + throws ConfigurationException + { + return processElement(xmlElement); + } + + /** + * {@inheritDoc} + */ + public Object processDocumentAt(URI uri) + throws ConfigurationException + { + return processDocument(XmlHelper.loadFileOrResource(uri.toString(), "cache configuration", + m_dependencies.getContextClassLoader())); + } + + /** + * {@inheritDoc} + */ + public Object processDocumentAt(String sLocation) + throws ConfigurationException + { + return processDocument(XmlHelper.loadFileOrResource(sLocation, "cache configuration", + m_dependencies.getContextClassLoader())); + } + + /** + * {@inheritDoc} + */ + public Object processDocument(String sXml) + throws ConfigurationException + { + return processDocument(XmlHelper.loadXml(sXml)); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Object processElement(XmlElement xmlElement) + throws ConfigurationException + { + // NOTE: Seven sequential tasks are required to "process" an XmlElement. These must + // be performed in a specific order as they are dependent on each other. + Object oResult = null; + + // we need a list of XmlAttributes for the XmlElement as we may use these in multiple places. + ArrayList lstAttributes = new ArrayList(xmlElement.getAttributeMap().size()); + + for (String sAttributeName : ((Set) xmlElement.getAttributeMap().keySet())) + { + lstAttributes.add(new SimpleAttribute(xmlElement, sAttributeName, xmlElement.getAttribute(sAttributeName))); + } + + // we need to know if a new namespace was loaded or if this is the root + // element to determine if the element requires preprocessing + boolean fElementRequiresPreprocessing = isRootContext() || xmlElement.getParent() == null; + + // TASK 1: Start a new sub context for the element we're about to process. + DefaultProcessingContext context = new DefaultProcessingContext(this, xmlElement); + + // TASK 2: Ensure that all of NamespaceHandler(s) declared in the element (using xmlns) are defined + // in the sub-context. + for (XmlAttribute attribute : lstAttributes) + { + QualifiedName qnAttribute = attribute.getQualifiedName(); + + // only process "xmlns" declarations + if (qnAttribute.getLocalName().equals("xmlns")) + { + String sURI = attribute.getXmlValue().getString(); + + try + { + // ensure that the declared Xml Namespaces are available in the context of this element. + context.ensureNamespaceHandler(qnAttribute.getPrefix(), new URI(sURI)); + + // a new namespace was loaded so we must perform pre-processing + fElementRequiresPreprocessing = true; + } + catch (URISyntaxException uriSyntaxException) + { + throw new ConfigurationException(String.format("Invalid URI '%s' specified for Xml Namespace '%s'", + sURI, qnAttribute.getPrefix()), "You must specify a valid URI for the Xml Namespace.", + uriSyntaxException); + } + } + } + + // TASK 3: Pre-process the element using all visible NamespaceHandler + // DocumentPreprocessors until no more pre-precessing is required + if (fElementRequiresPreprocessing) + { + Iterable namespaceHandlers = context.getNamespaceHandlers(); + boolean fRevisit; + + do + { + fRevisit = false; + + for (NamespaceHandler namespaceHandler : namespaceHandlers) + { + DocumentPreprocessor preprocessor = namespaceHandler.getDocumentPreprocessor(); + + if (preprocessor != null) + { + fRevisit = fRevisit || preprocessor.preprocess(context, xmlElement); + + if (fRevisit) + { + break; + } + } + } + } + while (fRevisit); + } + + // TASK 4: Find an appropriate ElementProcessor for the element we're about to process + QualifiedName qnElement = xmlElement.getQualifiedName(); + NamespaceHandler nsElement = context.getNamespaceHandler(qnElement.getPrefix()); + ElementProcessor procElement = nsElement == null ? null : nsElement.getElementProcessor(xmlElement); + + if (nsElement == null) + { + throw new ConfigurationException(String.format( + "A NamespaceHandler could not be located for the namespace [%s] in the element [%s]", + qnElement.getPrefix(), + xmlElement), "A NamespaceHandler implementation for the namespace must be defined."); + } + else if (procElement == null) + { + throw new ConfigurationException(String.format("An ElementProcessor could not be located for the element [%s]", + qnElement), "The specified element is unknown to the NamespaceHandler implementation. " + "Perhaps the xml element is foreign to the Xml Namespace?"); + } + else + { + // TASK 5: Process all of the xml attributes declared in the element. + for (XmlAttribute attribute : lstAttributes) + { + QualifiedName qnAttribute = attribute.getQualifiedName(); + NamespaceHandler nsAttribute; + + // locate the AttributeProcessor for the XmlAttribute using the appropriate NamespaceHandler + if (qnAttribute.hasPrefix()) + { + // when an attribute is defined in a specific namespace, use the namespace of the attribute + nsAttribute = getNamespaceHandler(qnAttribute.getPrefix()); + } + else + { + // when an attribute is not defined in a specific namespace, use the namespace of the element. + nsAttribute = nsElement; + } + + AttributeProcessor procAttribute = nsAttribute == null + ? null : nsAttribute.getAttributeProcessor(attribute); + + if (nsAttribute == null || procAttribute == null) + { + // SKIP: when we don't have an NamespaceHandler or AttributeProcessor for the attribute + // we simply ignore the request because the dependency injection framework may/can try to access + // the content directly. + } + else + { + procAttribute.process(context, attribute); + } + } + + // TASK 6: Use the located ElementProcessor to process the element in it's context. + ConditionalElementProcessor procConditional = procElement instanceof ConditionalElementProcessor + ? ((ConditionalElementProcessor) procElement) : null; + + if (procConditional == null || (procConditional != null && procConditional.accepts(context, xmlElement))) + { + oResult = procElement.process(context, xmlElement); + } + } + + // TASK 7: Terminate the current context as it's now out of scope. + context.terminate(); + + // remember that we've processed this element so that if a call to + // processRemainingElements is made, we can skip this one + m_setProcessedChildElements.add(xmlElement); + + return oResult; + } + + /** + * {@inheritDoc} + */ + public Object processElement(String sXml) + throws ConfigurationException + { + return processElement(XmlHelper.loadXml(sXml)); + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public T processOnlyElementOf(XmlElement xmlElement) + throws ConfigurationException + { + // the xmlElement must only have a single child + if (xmlElement.getElementList().size() == 1) + { + // process the child element + return (T) processElement((XmlElement) xmlElement.getElementList().get(0)); + } + else + { + // expected only a single element in custom-provider + throw new ConfigurationException(String.format("Only a single element is permitted in the %s element.", + xmlElement), String.format("Please consult the documentation regarding use of the '%s' namespace", + new QualifiedName(xmlElement).getPrefix())); + } + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public Map processElementsOf(XmlElement xmlElement) + throws ConfigurationException + { + // process all of the children of the xmlElement + LinkedHashMap mapResult = new LinkedHashMap(); + + for (Iterator children = xmlElement.getElementList().iterator(); children.hasNext(); ) + { + XmlElement xmlChild = children.next(); + String sId = xmlChild.getAttributeMap().containsKey("id") + ? xmlChild.getAttribute("id").getString() : new UUID().toString(); + + if (sId.trim().length() == 0) + { + sId = new UUID().toString(); + } + + mapResult.put(sId, processElement(xmlChild)); + } + + return mapResult; + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public Map processForeignElementsOf(XmlElement xmlElement) + throws ConfigurationException + { + String sPrefix = xmlElement.getQualifiedName().getPrefix(); + + // process all of the children of the xmlElement + LinkedHashMap mapResult = new LinkedHashMap(); + + for (Iterator children = xmlElement.getElementList().iterator(); children.hasNext(); ) + { + XmlElement xmlChild = children.next(); + + if (!xmlChild.getQualifiedName().getPrefix().equals(sPrefix)) + { + String sId = xmlChild.getAttributeMap().containsKey("id") + ? xmlChild.getAttribute("id").getString() : new UUID().toString(); + + if (sId.trim().length() == 0) + { + sId = new UUID().toString(); + } + + mapResult.put(sId, processElement(xmlChild)); + } + } + + return mapResult; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public Map processRemainingElementsOf(XmlElement xmlElement) + throws ConfigurationException + { + // process all of the children of the xmlElement that aren't in the set of elements we've already processed + LinkedHashMap mapResult = new LinkedHashMap(); + + for (Iterator children = xmlElement.getElementList().iterator(); children.hasNext(); ) + { + XmlElement xmlChild = children.next(); + + if (!m_setProcessedChildElements.contains(xmlChild)) + { + String sId = xmlChild.getAttributeMap().containsKey("id") + ? xmlChild.getAttribute("id").getString() : new UUID().toString(); + + if (sId.trim().length() == 0) + { + sId = new UUID().toString(); + } + + mapResult.put(sId, processElement(xmlChild)); + } + } + + return mapResult; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + public T processRemainingElementOf(XmlElement xmlElement) + throws ConfigurationException + { + Map mapResults = processRemainingElementsOf(xmlElement); + int cSize = mapResults.size(); + + if (cSize == 1) + { + return (T) mapResults.values().iterator().next(); + } + else + { + throw new ConfigurationException(String.format( + "Expected a single remaining element to process in %s after processing the elements %s but there were %d elements remaining", + xmlElement, xmlElement.getQualifiedName(), m_setProcessedChildElements.size()), String.format( + "The ElementProcessor implementation for %s makes an incorrect assumption about the number of remaining elements.", + xmlElement.getQualifiedName())); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isPropertyDefined(String sPropertyName, XmlElement xmlParent) + throws ConfigurationException + { + if (sPropertyName == null || xmlParent == null) + { + return false; + } + + // assume that a property in "this context" (ie: .) is always found + boolean fPropertyFound = sPropertyName.equals("."); + + // STEP 1: Attempt to find the property value using an XmlAttribute defined by the XmlElement + if (!fPropertyFound) + { + fPropertyFound = getPropertyAttribute(xmlParent, sPropertyName) != null; + } + + // STEP 2: Attempt to find the property value using content defined by the XmlElement + if (!fPropertyFound) + { + fPropertyFound = getPropertyElement(xmlParent, sPropertyName) != null; + } + + return fPropertyFound; + } + + /** + * {@inheritDoc} + */ + @Override + public B inject(B bean, XmlElement xmlElement) + throws ConfigurationException + { + for (Method method : bean.getClass().getMethods()) + { + Type[] aParameterTypes = method.getGenericParameterTypes(); + + // we can only inject into methods annotated with @Injectable + Injectable annInjectable = method.getAnnotation(Injectable.class); + + if (annInjectable != null) + { + if (aParameterTypes.length == 0 && !method.getReturnType().equals(Void.TYPE)) + { + // perform getter injection (ie: inject into the value returned by the getter) + Object oInjectable = null; + + try + { + // determine the injectable from the getter + oInjectable = method.invoke(bean); + + // we can only inject into an object that's not a bean + if (oInjectable != null && oInjectable != bean) + { + // decide how to locate the XmlElement containing the content to use for injection + String sPropertyName = annInjectable.value(); + + XmlElement xmlProperty; + + if (sPropertyName.isEmpty()) + { + // when no name is specified we (automatically) + // determine the property name based on the method name + sPropertyName = getPropertyName(method); + } + + if (sPropertyName.equals(".")) + { + // when a "." we use "this" context for injection content + xmlProperty = xmlElement; + } + else + { + // find the element based on the name of the property + xmlProperty = getPropertyElement(xmlElement, sPropertyName); + } + + if (xmlProperty != null) + { + inject(oInjectable, xmlProperty); + } + } + } + catch (ConfigurationException e) + { + throw e; + } + catch (Exception e) + { + throw new ConfigurationException(String.format( + "Could not inject a value into the instance '%s' using reflection " + + " produced by the annotated method '%s' of '%s'", oInjectable, method, + bean.getClass().getName()), "Please resolve the causing exception.", e); + } + } + else if (aParameterTypes.length == 1) + { + // perform setter injection (ie: call a setter with a value) + + // use the method name (or @Injectable) to determine the property name + String sPropertyName = getPropertyName(method); + + // use the setter parameter type to determine the property type + + // the type of the property + Type typeProperty = aParameterTypes[0]; + + // the value of the property + Value value = getPropertyValue(sPropertyName, typeProperty, xmlElement, false); + + if (value != null) + { + // use the defined value to inject + Object oPropertyValue = value.get(); + + { + try + { + method.invoke(bean, oPropertyValue); + } + catch (Exception e) + { + throw new ConfigurationException(String.format( + "Could not inject the property '%s' using reflection " + + "with the annotated method '%s' of '%s'", sPropertyName, method, + bean.getClass().getName()), "Please resolve the causing exception.", e); + } + } + } + } + } + } + + return bean; + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + @Override + public T getMandatoryProperty(String sPath, Type typeProperty, XmlElement xmlParent) + throws ConfigurationException + { + Value value = getPropertyValue(sPath, typeProperty, xmlParent, true); + + if (value == null) + { + // when we can't find a value as an attribute or element we must throw an exception. + throw new ConfigurationException(String.format( + "The expected property [%s] is not defined in element [%s].", sPath, xmlParent), String.format( + "Please consult the documentation for the use of the %s namespace", + m_xmlElement.getQualifiedName().getPrefix())); + + } + else + { + return (T) value.get(); + } + } + + /** + * {@inheritDoc} + */ + @SuppressWarnings("unchecked") + @Override + public T getOptionalProperty(String sPropertyName, Type typeProperty, T defaultValue, XmlElement xmlElement) + throws ConfigurationException + { + Value value = getPropertyValue(sPropertyName, typeProperty, xmlElement, true); + + if (value == null) + { + return defaultValue; + } + else + { + return (T) value.get(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public NamespaceHandler ensureNamespaceHandler(String sPrefix, NamespaceHandler handler) + throws ConfigurationException + { + URI uri = m_mapNamespaceURIsByPrefix.get(sPrefix); + + if (uri == null) + { + try + { + uri = new URI("class://" + handler.getClass().getName()); + } + catch (URISyntaxException e) + { + throw new ConfigurationException(String.format( + "Failed to create a valid URI for the specified namespace class [%s] with prefix [%s]", + handler.getClass().getName(), sPrefix), "The implemented URI encoding is invalid", e); + } + + m_mapNamespaceURIsByPrefix.put(sPrefix, uri); + m_mapNamespaceHandlersByURI.put(uri, handler); + + // call-back the NamespaceHandler + handler.onStartNamespace(this, m_xmlElement, sPrefix, uri); + + return handler; + } + else + { + return m_mapNamespaceHandlersByURI.get(uri); + } + } + + /** + * {@inheritDoc} + */ + @Override + @SuppressWarnings("unchecked") + public NamespaceHandler ensureNamespaceHandler(String sPrefix, URI uri) + throws ConfigurationException + { + // determine if the NamespaceHandler for the specified URI is already defined + NamespaceHandler namespaceHandler = getNamespaceHandler(uri); + + if ((namespaceHandler == null) || ((namespaceHandler != null) && !getNamespaceURI(sPrefix).equals(uri))) + { + String sScheme = uri.getScheme(); + + // ensure that we don't already have a NamespaceHandler with the specified prefix in the context + if (m_mapNamespaceURIsByPrefix.containsKey(sPrefix)) + { + throw new ConfigurationException(String.format( + "Duplicate definition for the namespace prefix [%s] and URI [%s] encountered in the element [%s]", + sPrefix, uri, m_xmlElement), "Duplicate definitions of namespaces is not permitted."); + } + else if (sScheme.equals("class")) + { + String sClassName = (uri.getHost() == null) ? uri.getSchemeSpecificPart() : uri.getHost(); + + try + { + Class clzNamespaceHandler = ExternalizableHelper.loadClass(sClassName, + m_dependencies.getContextClassLoader(), null); + + // ensure that the class is a NamespaceHandler + if (NamespaceHandler.class.isAssignableFrom(clzNamespaceHandler)) + { + try + { + // find the no args constructor for the NamespaceHandler + Constructor constructor = + (Constructor) clzNamespaceHandler.getConstructor(); + + // instantiate the NamespaceHandler + namespaceHandler = constructor.newInstance(); + + // register the NamespaceHandler with this context + m_mapNamespaceHandlersByURI.put(uri, namespaceHandler); + + // register the prefix for the NamespaceHandler + m_mapNamespaceURIsByPrefix.put(sPrefix, uri); + + // call-back the NamespaceHandler + namespaceHandler.onStartNamespace(this, m_xmlElement, sPrefix, uri); + + return namespaceHandler; + } + catch (Exception exception) + { + throw new ConfigurationException(String.format("Can't instantiate the NamespaceHandler [%s]\n", + sClassName), "Please ensure that the specified class is public and has a no-args constructor", exception); + } + } + else + { + throw new ConfigurationException(String + .format("The declared class [%s] does not implement the %s interface", sClassName, NamespaceHandler.class + .getName()), "To use a class as a NamespaceHandler it must implement the appropriate interface."); + } + } + catch (ClassNotFoundException e) + { + throw new ConfigurationException(String.format( + "Can't instantiate the NamespaceHandler [%s] as the class is not found\n", + sClassName), "Please ensure that the specified class is an instance of NamespaceHandler interface", e); + } + } + else if (sScheme.equalsIgnoreCase("http") || sScheme.equalsIgnoreCase("https")) + { + // for http/https-based schemes, we assume there is a NamespaceHandler already registered + return getNamespaceHandler(sPrefix); + } + else + { + throw new ConfigurationException(String.format( + "Can't instantiate a suitable NamespaceHandler as the URI [%s] scheme is unknown.\n", + uri), "Please ensure that the specified URI refers to a class that implements the NamespaceHandler interface"); + } + } + else + { + return namespaceHandler; + } + } + + /** + * {@inheritDoc} + */ + @Override + public NamespaceHandler getNamespaceHandler(String sPrefix) + { + URI uri = getNamespaceURI(sPrefix); + + return uri == null ? null : getNamespaceHandler(uri); + } + + /** + * {@inheritDoc} + */ + @Override + public NamespaceHandler getNamespaceHandler(URI uri) + { + NamespaceHandler namespaceHandler = m_mapNamespaceHandlersByURI.get(uri); + + return namespaceHandler == null + ? (isRootContext() ? null : m_ctxParent.getNamespaceHandler(uri)) : namespaceHandler; + } + + /** + * {@inheritDoc} + */ + @Override + public URI getNamespaceURI(String sPrefix) + { + URI uri = m_mapNamespaceURIsByPrefix.get(sPrefix); + + return uri == null ? (isRootContext() ? null : m_ctxParent.getNamespaceURI(sPrefix)) : uri; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable getNamespaceHandlers() + { + LinkedHashMap mapNamespaceHandlersByURI = new LinkedHashMap(); + DefaultProcessingContext ctx = this; + + while (ctx != null) + { + for (Entry e : ctx.m_mapNamespaceHandlersByURI.entrySet()) + { + if (!mapNamespaceHandlersByURI.containsKey(e.getKey())) + { + mapNamespaceHandlersByURI.put(e.getKey(), e.getValue()); + } + } + + ctx = ctx.m_ctxParent; + } + + return mapNamespaceHandlersByURI.values(); + } + + // ----- DefaultProcessingContext methods ------------------------------- + + /** + * Obtains the {@link DocumentProcessor} {@link DocumentProcessor.Dependencies} for the {@link ProcessingContext}. + * + * @return the {@link DocumentProcessor.Dependencies} + */ + public Dependencies getDependencies() + { + return m_dependencies; + } + + /** + * Attempts to resolve the named property of the specified type in the current context and + * if required will parse the specified {@link XmlElement} in order to do so. + * + * @param sPropertyName the name or xml path to the property + * @param typeProperty the required type of the property value + * @param xmlParent the parent element in which the property may be found + * @param fOnlyUsePropertyName when true the specified property name must be + * used resolve the property value. when false + * attempts may be made to resolve the property just the type + * name if the specified property name doesn't resolve a property + * + * @return The {@link Value} representing the property or null if the property + * could not be located + * @throws ConfigurationException if the property was but could not be processed or is + * of the incorrect type + */ + public Value getPropertyValue(String sPropertyName, Type typeProperty, XmlElement xmlParent, + boolean fOnlyUsePropertyName) + throws ConfigurationException + { + if (sPropertyName == null) + { + throw new NullPointerException("Property Name can't be null"); + } + + if (typeProperty == null) + { + throw new NullPointerException("Property Type can't be null"); + } + + if (xmlParent == null) + { + throw new NullPointerException("XmlElement in which to locate a property can't be null"); + } + + // assume we haven't resolved the property value + Object oValue = null; + boolean fValueResolved = false; + + // determine concrete class for the property + Class clzProperty = ClassHelper.getClass(typeProperty); + + // STEP 1: Attempt to resolve the property value using an XmlAttribute defined by the XmlElement + if (!fValueResolved && !sPropertyName.equals(".")) + { + XmlAttribute xmlValue = getPropertyAttribute(xmlParent, sPropertyName); + + if (xmlValue != null) + { + // no matter what happens now we assume we can and have resolved the property value + fValueResolved = true; + + // determine the AttributeProcessor to process the XmlAttribute content into the property value + NamespaceHandler nsAttribute = getNamespaceHandler(xmlValue.getQualifiedName().getPrefix()); + AttributeProcessor procAttribute = nsAttribute == null + ? null : nsAttribute.getAttributeProcessor(xmlValue); + + // when there is no AttributeProcessor, attempt to locate a type-based one + if (procAttribute == null) + { + procAttribute = getAttributeProcessor(clzProperty); + } + + if (procAttribute == null) + { + // when we can't find a suitable AttributeProcessor, assume we can use the attribute as a String + oValue = xmlValue.getXmlValue().getString(); + } + else + { + oValue = procAttribute.process(this, xmlValue); + } + } + } + + // STEP 2: Attempt to resolve the property value using content defined by the XmlElement + if (!fValueResolved) + { + XmlElement xmlValue = sPropertyName.equals(".") ? xmlParent : getPropertyElement(xmlParent, sPropertyName); + + if (xmlValue != null) + { + // remember that we've processed this element (when it's not the parent!) + if (xmlValue != xmlParent) + { + m_setProcessedChildElements.add(xmlValue); + } + + // determine the ElementProcessor to process the XmlElement content into the property value + NamespaceHandler nsElement = getNamespaceHandler(xmlValue.getQualifiedName().getPrefix()); + ElementProcessor procElement = nsElement == null ? null : nsElement.getElementProcessor(xmlValue); + + // when there is no ElementProcessor, attempt to locate a type-based one + if (procElement == null) + { + procElement = getElementProcessor(clzProperty); + } + + // when we can't find a suitable ElementProcessor, try to resolve the value another way + // (perhaps it's a collection, an array or just plain xml) + if (procElement == null) + { + // determine if we are dealing with a collection (or array) of some type? + Class clzComponent = ClassHelper.getComponentType(typeProperty); + + // the style of processing is somewhat dependent on the number of children in the element + int cChildren = xmlValue.getElementList().size(); + + if (clzComponent == null) + { + if (cChildren == 0) + { + // when there is no processor for the element and the element value is simply an XmlValue + // assume we can use the value as a String (if it's not empty) + if (!XmlHelper.isEmpty(xmlValue)) + { + oValue = xmlValue.getString(); + + // we have now resolved the property value + fValueResolved = true; + } + } + else if (cChildren == 1) + { + // when there is no processor for the property but the property value contains a single + // element, process the element itself + oValue = processOnlyElementOf(xmlValue); + + // we have now resolved the property value + fValueResolved = true; + } + else + { + // when there is no processor and there's a collection, we assume the required value is + // just the element (xml). + oValue = xmlValue; + + // we have now resolved the property value + fValueResolved = true; + } + } + else + { + // create a collection from the element based on the children + + // we have now resolved the property value + fValueResolved = true; + + // instantiate the collection for the property (or something that is assignment compatible) + boolean fArray = clzProperty.isArray(); + + if (fArray) + { + oValue = Array.newInstance(clzComponent, cChildren); + } + else if (clzProperty.isInterface()) + { + if (clzProperty.equals(List.class)) + { + oValue = new ArrayList(cChildren); + } + else if (clzProperty.equals(Set.class)) + { + oValue = new LinkedHashSet(cChildren); + } + else if (clzProperty.equals(Queue.class)) + { + oValue = new ArrayDeque(cChildren); + } + else if (clzProperty.equals(SortedSet.class)) + { + oValue = new TreeSet(); + } + else + { + throw new ConfigurationException(String.format( + "Unsupported collection type [%s] encountered with the property [%s] in [%s]", + typeProperty, sPropertyName, + xmlValue), "The specified interface type is not supported. Please use a more specific type."); + } + } + else + { + try + { + oValue = clzProperty.newInstance(); + } + catch (Exception e) + { + throw new ConfigurationException(String.format( + "Failed to instantiate the required type [%s] for the property [%s] in [%s]", + typeProperty, sPropertyName, + xmlValue), "Ensure that the specified type has a public no-args constructor", e); + } + } + + // process each of the children, adding them to the collection and ensuring they + // are of the correct type + int idx = 0; + + for (Iterator children = xmlValue.getElementList().iterator(); children.hasNext(); ) + { + XmlElement xmlChild = children.next(); + + Object oChild = processElement(xmlChild); + + // ensure the child value is compatible with the component type + if (clzComponent.isInstance(oChild)) + { + if (fArray) + { + ((Object[]) oValue)[idx] = oChild; + } + else + { + ((Collection) oValue).add(oChild); + } + } + else + { + throw new ConfigurationException(String.format("Incompatible types" + + "The value [%s] is not assignable to a collection of type [%s]" + + "as expected by the property [%s] in [%s]", oChild, clzComponent, sPropertyName, + xmlValue), "Please ensure assignment compatible values are provided"); + } + + idx++; + } + } + } + else + { + ConditionalElementProcessor procConditional = procElement instanceof ConditionalElementProcessor + ? ((ConditionalElementProcessor) procElement) + : null; + + DefaultProcessingContext ctxValue = new DefaultProcessingContext(this, xmlValue); + + if (procConditional == null + || (procConditional != null && procConditional.accepts(ctxValue, xmlValue))) + { + oValue = procElement.process(ctxValue, xmlValue); + + // we have now resolved the property value + fValueResolved = true; + } + + ctxValue.terminate(); + } + } + } + + // STEP 3: Attempt to resolve the property value using a Cookie defined by the ProcessingContext + if (!fValueResolved) + { + oValue = getCookie(clzProperty, sPropertyName); + + if (oValue == null && !fOnlyUsePropertyName) + { + oValue = getCookie(clzProperty); + } + + fValueResolved = oValue != null; + } + + // STEP 4: Attempt to resolve the property value using the ResourceRegistry defined by the ProcessingContext + if (!fValueResolved) + { + ResourceRegistry registry = getResourceRegistry(); + + if (registry != null) + { + oValue = registry.getResource(clzProperty, sPropertyName); + + if (oValue == null && !fOnlyUsePropertyName) + { + oValue = registry.getResource(clzProperty); + } + + fValueResolved = oValue != null; + } + } + + // STEP 5: Perform necessary type checking and value coersion + if (fValueResolved) + { + // attempt to convert the raw value into the required type + if (oValue == null) + { + // we assume null is compatible with all (object-based) types + return new Value(); + } + else if (clzProperty.isInstance(oValue)) + { + // assignable types don't need converting + return new Value(oValue); + } + else if (clzProperty.equals(Expression.class)) + { + // determine the resulting type of the expression (assume it's just Object.class) + Class clzResultType = Object.class; + + if (typeProperty instanceof ParameterizedType) + { + ParameterizedType typeParameterized = (ParameterizedType) typeProperty; + Type typeRaw = typeParameterized.getRawType(); + + if (typeRaw instanceof Class) + { + clzResultType = ClassHelper.getClass(typeParameterized.getActualTypeArguments()[0]); + } + } + + // we need to build an expression using the value + if (oValue instanceof String) + { + ExpressionParser parser = m_dependencies.getExpressionParser(); + + if (parser == null) + { + throw new ConfigurationException(String.format( + "Failed to parse the expression [%s] defined for the property [%s] in [%s] as an ExpressionParser is not defined.", + oValue, sPropertyName, + xmlParent), "An ExpressionParser must be set for the DocumentProcessor."); + } + else + { + // attempt to parse the expression + try + { + return new Value(parser.parse((String) oValue, clzResultType)); + } + catch (ParseException e) + { + throw new ConfigurationException(String.format( + "Failed to parse the expression [%s] defined for the property [%s] in [%s].", oValue, + sPropertyName, xmlParent), "Please correct the expression syntax error.", e); + } + } + } + else + { + // use a LiteralExpression to represent non-string values + return new Value(new LiteralExpression(oValue)); + } + } + else + { + // use a Value as a means to convert the raw into the required value + try + { + Value value = oValue instanceof Value ? (Value) oValue : new Value(oValue); + + return new Value(value.as(clzProperty)); + } + catch (Exception e) + { + throw new ConfigurationException(String.format( + "Failed to convert the property [%s] value [%s] into the required type [%s] in [%s].", + sPropertyName, oValue, typeProperty, + xmlParent), "The namespace implementation for the property will need to programmatically configure the said property.", e); + } + } + } + else + { + return null; + } + } + + /** + * Locates the {@link XmlElement} relative to the specified base {@link XmlElement} using a '/'-delimited path + *

+ * The format of the path parameter is based on a subset of the XPath specification, supporting: + *

    + *
  • Use a leading '/' to specify the root element + *
  • Use '/' to specify a child path delimiter + *
  • Use '..' to specify a parent + *
+ *

+ * Elements of the path may optionally be fully qualified in a namespace. When they are not qualified the + * specified prefix is used as their qualifier. + *

+ * If multiple child elements exist that have the specified name, then the behavior of this method is undefined, and + * it is permitted to return any one of the matching elements, to return null, or to throw an arbitrary runtime + * exception. + * + * @param xmlElement the base {@link XmlElement} from which the locate the desired element + * @param sPath the path to follow from the base {@link XmlElement} to find the desired {@link XmlElement} + * @param sPrefix the namespace prefix to use for path elements that are not fully qualified + * + * @return the {@link XmlElement} with the specified path or null if no such a {@link XmlElement} exists + */ + private XmlElement getElementAt(XmlElement xmlElement, String sPath, String sPrefix) + { + // determine the starting point for the search + XmlElement xml; + + // strip leading and trailing white spaces from the path as they have no meaning in xml paths + sPath = sPath == null ? "" : sPath.trim(); + + if (sPath.startsWith("/")) + { + // a leading / means the root of the base element + xml = xmlElement.getRoot(); + } + else + { + // anything else (including an empty path) means starting at the base element + xml = xmlElement; + } + + // tokenize the path based on the path separator + StringTokenizer tokens = new StringTokenizer(sPath, "/"); + + // apply each of the path elements in the tokenized path + while (xml != null && tokens.hasMoreTokens()) + { + String sName = tokens.nextToken().trim(); + + if (sName.equals("..")) + { + xml = xml.getParent(); + + if (xml == null) + { + // skip: if we get to a null parent, the element is not defined + } + } + else + { + // determine the QualifiedName of the element + QualifiedName qName = sName.contains(":") + ? new QualifiedName(sName) : new QualifiedName(sPrefix, sName); + + // locate the child with the qualified name + xml = xml.getElement(qName.getName()); + } + } + + return xml; + } + + /** + * Obtains the property content defined as an {@link XmlAttribute} based on a path relative to a specified + * base {@link XmlElement}. + *

+ * This is the primary method by which {@link XmlAttribute}-based content for properties is located. Typically the + * provided path is simply the name of the attribute. However there are circumstances where it may be a path, in + * which case the last defined path element (name) in the path (after a / or ..) is used as the attribute name. + * + * @param xmlElement the {@link XmlElement} from which the locate the content + * @param sPath the path to the property content relative to the base {@link XmlElement} + * + * @return an {@link XmlAttribute} representing the content or null if the content is not found. + */ + private XmlAttribute getPropertyAttribute(XmlElement xmlElement, String sPath) + { + sPath = sPath == null ? null : sPath.trim(); + + if (sPath == null || sPath.isEmpty()) + { + return null; + } + else + { + // determine the attribute name and parent element given the path + XmlElement xmlParent; + String sAttributeName; + int iAttribute = Math.max(sPath.lastIndexOf("/"), sPath.lastIndexOf("..")); + + if (iAttribute >= 0) + { + sAttributeName = sPath.substring(iAttribute + (sPath.charAt(iAttribute) == '/' ? 1 : 2)); + sPath = sPath.substring(0, iAttribute); + xmlParent = getElementAt(xmlElement, sPath.substring(0, iAttribute), + xmlElement.getQualifiedName().getPrefix()); + } + else + { + sAttributeName = sPath; + xmlParent = m_xmlElement; + } + + // determine the attribute value + XmlValue value; + + if (xmlParent == null) + { + value = null; + } + else + { + value = xmlParent.getAttribute(sAttributeName); + } + + return value == null ? null : new SimpleAttribute(xmlParent, sAttributeName, value); + } + } + + /** + * Obtains the property content defined as an {@link XmlElement} based on a path relative to a specified + * base {@link XmlElement}. + *

+ * This is the primary method by which {@link XmlElement}-based content for properties is located. + * + * @param xmlElement the {@link XmlElement} from which the locate the content + * @param sPath the path to the property content relative to the base {@link XmlElement} + * + * @return an {@link XmlElement} representing the content or null if the content is not found. + */ + private XmlElement getPropertyElement(XmlElement xmlElement, String sPath) + { + return getElementAt(xmlElement, sPath, xmlElement.getQualifiedName().getPrefix()); + } + + /** + * Obtain the {@link AttributeProcessor} defined for the specific type. + * + * @param clzType the type of {@link AttributeProcessor} we're looking for + * + * @return an {@link AttributeProcessor} or null if not found + */ + @SuppressWarnings("unchecked") + AttributeProcessor getAttributeProcessor(Class clzType) + { + AttributeProcessor procAttribute = (AttributeProcessor) m_mapAttributeProcessorsByType.get(clzType); + + if (procAttribute == null) + { + return isRootContext() ? null : m_ctxParent.getAttributeProcessor(clzType); + } + else + { + return procAttribute; + } + } + + /** + * Obtain the {@link ElementProcessor} defined for the specific type. + * + * @param clzType the type of {@link ElementProcessor} we're looking for + * + * @return an {@link ElementProcessor} or null if not found + */ + @SuppressWarnings("unchecked") + ElementProcessor getElementProcessor(Class clzType) + { + ElementProcessor procElement = (ElementProcessor) m_mapElementProcessorsByType.get(clzType); + + if (procElement == null) + { + return isRootContext() ? null : m_ctxParent.getElementProcessor(clzType); + } + else + { + return procElement; + } + } + + /** + * Determines if the {@link ProcessingContext} is the root. ie: has no parent. + * + * @return true if the {@link ProcessingContext} is the root scope, false otherwise + */ + public boolean isRootContext() + { + return m_ctxParent == null; + } + + /** + * Obtains the property name for a method, either from the @Injectable + * annotation or the method name. + * + * @param method the method which will used to form the property name + * + * @return the property name + */ + String getPropertyName(Method method) + { + StringBuilder sbPropertyName = new StringBuilder(); + + if (method.isAnnotationPresent(Injectable.class) && !method.getAnnotation(Injectable.class).value().isEmpty()) + { + sbPropertyName.append((method.getAnnotation(Injectable.class)).value()); + } + else + { + // convert method name from camelCase to hyphen format (Example setModelName/getModelName to model-name) + String sn = method.getName().substring("set".length()); + + if (!sn.isEmpty()) + { + sbPropertyName.append(sn.substring(0, 1).toLowerCase()); + } + + // insert "-" and convert to lower case when encounter upper case + for (int i = 1; i < sn.length(); i++) + { + if (Character.isUpperCase(sn.charAt(i))) + { + sbPropertyName.append("-"); + sbPropertyName.append(sn.substring(i, i + 1).toLowerCase()); + } + else + { + sbPropertyName.append(sn.substring(i, i + 1)); + } + } + } + + // use the property path if one is defined + String sName = sbPropertyName.toString(); + String sPropertyPath = m_mapPropertyPaths.get(sName); + + return sPropertyPath == null ? sName : sPropertyPath; + } + + /** + * Terminates and end's all of the {@link NamespaceHandler}s established in the {@link ProcessingContext}. + */ + void terminate() + { + // dispose each of the NamespaceHandler(s) loaded in this context + for (String sPrefix : m_mapNamespaceURIsByPrefix.keySet()) + { + URI uri = m_mapNamespaceURIsByPrefix.get(sPrefix); + NamespaceHandler handler = m_mapNamespaceHandlersByURI.get(uri); + + // ignore then case where we have a non-class uri + if (handler != null) + { + handler.onEndNamespace(this, m_xmlElement, sPrefix, uri); + } + } + } + + // ----- AutoCloseable interface ---------------------------------------- + + @Override + public void close() + { + terminate(); + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link DocumentProcessor} {@link DocumentProcessor.Dependencies} to be when processing content in + * this {@link ProcessingContext} implementation. + */ + private Dependencies m_dependencies; + + /** + * The parent {@link ProcessingContext}. + */ + private DefaultProcessingContext m_ctxParent; + + /** + * The map of {@link AttributeProcessor}s by type for this {@link ProcessingContext}. + */ + private HashMap, AttributeProcessor> m_mapAttributeProcessorsByType; + + /** + * The cookies defined in the {@link ProcessingContext} by type and name. + */ + private HashMap, HashMap> m_mapCookiesByType; + + /** + * The map of {@link ElementProcessor}s by type for this {@link ProcessingContext}. + */ + private HashMap, ElementProcessor> m_mapElementProcessorsByType; + + /** + * The {@link NamespaceHandler}s for the xml namespaces registered in this {@link ProcessingContext}. + */ + private LinkedHashMap m_mapNamespaceHandlersByURI; + + /** + * The xml namespaces registered in this {@link ProcessingContext}. A mapping from prefix to URIs + */ + private LinkedHashMap m_mapNamespaceURIsByPrefix; + + /** + * A mapping of Property Names to an {@link XmlElement} paths. This is used to + * override the default behavior used to certain locate property values for this {@link ProcessingContext}. + */ + private HashMap m_mapPropertyPaths; + + /** + * The set of child {@link XmlElement} names with in the {@link #m_xmlElement} that have been processed thus + * far by the {@link ProcessingContext}. + */ + private HashSet m_setProcessedChildElements; + + /** + * The {@link XmlElement} for which this {@link ProcessingContext} was created. + */ + private XmlElement m_xmlElement; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/DocumentElementPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/DocumentElementPreprocessor.java new file mode 100644 index 0000000000000..c3ad1ea249ab4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/DocumentElementPreprocessor.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.run.xml.XmlElement; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link DocumentElementPreprocessor} is a {@link DocumentPreprocessor} + * that is designed to operate with one or more {@link ElementPreprocessor}s. + * + * @author bo 2012.03.12 + * @since Coherence 12.1.2 + */ +public class DocumentElementPreprocessor + implements DocumentPreprocessor + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a {@link DocumentElementPreprocessor}. + */ + public DocumentElementPreprocessor() + { + m_lstElementPreprocessors = new ArrayList(); + } + + // ----- DocumentElementPreprocessor methods ---------------------------- + + /** + * Adds an {@link ElementPreprocessor} to the {@link DocumentElementPreprocessor}. + * + * @param preprocessor the {@link ElementPreprocessor} to add + * + * @return the {@link DocumentElementPreprocessor} (this) to support + * fluent-style calls + */ + public DocumentElementPreprocessor addElementPreprocessor(ElementPreprocessor preprocessor) + { + m_lstElementPreprocessors.add(preprocessor); + + return this; + } + + /** + * Pre-process the specified {@link XmlElement} using the provided {@link ElementPreprocessor}. + * + * @param context the {@link ProcessingContext} in which the pre-processing will occur + * @param xmlElement the {@link XmlElement} to preprocess + * @param preprocessor the {@link ElementPreprocessor} + * + * @return true if the {@link ElementPreprocessor} recommended that the + * {@link XmlElement} should be re-preprocessed, or false otherwise + */ + @SuppressWarnings("unchecked") + private boolean preprocess(ProcessingContext context, XmlElement xmlElement, ElementPreprocessor preprocessor) + { + if (xmlElement == null || preprocessor == null) + { + return false; + } + else + { + // pre-process the element itself + boolean fRevisit = preprocessor.preprocess(context, xmlElement); + + // recursively pre-process the children of the element + List lstElements = (List) xmlElement.getElementList(); + + if (lstElements.size() > 0) + { + for (XmlElement xmlChild : lstElements) + { + fRevisit = fRevisit || preprocess(context, xmlChild, preprocessor); + } + } + + return fRevisit; + } + } + + // ----- DocumentPreprocessor interface --------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public boolean preprocess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException + { + if (xmlElement == null || m_lstElementPreprocessors.isEmpty()) + { + return false; + } + else + { + boolean fRevisited = false; + boolean fRevisit; + + // pre-process the document with all of the element pre-processors + // until non-require re-preprocessing + do + { + fRevisit = false; + + for (ElementPreprocessor preprocessor : m_lstElementPreprocessors) + { + fRevisit = fRevisit || preprocess(context, xmlElement, preprocessor); + + if (fRevisit) + { + fRevisited = true; + break; + } + } + } + while (fRevisit); + + return fRevisited; + } + } + + // ----- ElementPreprocessor interface ---------------------------------- + + /** + * An {@link ElementPreprocessor} provides a mechanism to examine and optionally + * mutate an {@link XmlElement} prior to it being processed by a + * {@link ElementProcessor}. {@link ElementPreprocessor}s are designed to be + * used when a number of similar {@link XmlElement}s in a document need + * to be pre-processed, ie: on an element-by-element basis, instead of an + * entire document being processed. + *

+ * Rule 1: Implementations of this interface must remain + * stateless with respect to the {@link XmlElement}s or + * {@link ProcessingContext} that they are passed. That is, no state should + * be retained relating to either of these concepts for each method call. + *

+ * Rule 2: No assumptions can be made as to the number of times + * an {@link ElementPreprocessor} may be called for a particular document or + * element, simply because other {@link ElementPreprocessor}s may request + * "re-pre-processing". + *

+ * Violating either of these two rules may likely result in unpredictable + * application behavior. + */ + public interface ElementPreprocessor + { + /** + * Process an {@link XmlElement}, optionally mutating it (or it's children) if required. + *

+ * Note: An implementation of this interface should avoid + * attempting to traverse child {@link XmlElement}s. If you wish to + * manually traverse or change the entire document, you should instead use + * a {@link DocumentPreprocessor}. + * + * @param context the {@link ProcessingContext} in which the pre-processing is occurring + * @param element the {@link XmlElement} to preprocess + * + * @return true if the specified {@link XmlElement} should be + * re-preprocessed by this and other {@link ElementPreprocessor}s + * due to the {@link XmlElement} being modified, false + * otherwise. + * + * @throws ConfigurationException if during pre-processing of the {@link XmlElement} a configuration + * issue was discovered (or if pre-processing fails for some reason) + */ + boolean preprocess(ProcessingContext context, XmlElement element) + throws ConfigurationException; + } + + // ----- data members --------------------------------------------------- + + /** + * The list of {@link ElementPreprocessor}s to apply in order when + * processing a document. + */ + private ArrayList m_lstElementPreprocessors; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/DocumentPreprocessor.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/DocumentPreprocessor.java new file mode 100644 index 0000000000000..8ac80be43daef --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/DocumentPreprocessor.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.run.xml.XmlDocument; +import com.tangosol.run.xml.XmlElement; + +/** + * A {@link DocumentPreprocessor} provides a mechanism to pre-process an + * {@link XmlElement}, representing part or all of an {@link XmlDocument} + * prior to the said {@link XmlElement} being processes using configured + * {@link ElementProcessor}s. + *

+ * Rule 1: Implementations of this interface must remain + * stateless with respect to the {@link XmlElement}s or + * {@link ProcessingContext} that they are passed. That is, no state should + * be retained relating to either of these concepts for each method call. + *

+ * Rule 2: No assumptions can be made as to the number of times + * a {@link DocumentPreprocessor} may be called for a particular document or + * element, simply because other {@link DocumentPreprocessor}s may request + * "re-pre-processing". + *

+ * Violating either of these two rules may likely result in unpredictable + * application behavior. + * + * @author bo 2012.03.12 + * @since Coherence 12.1.2 + */ +public interface DocumentPreprocessor + { + /** + * Performs pre-processing of the an {@link XmlElement}, optionally mutating + * it (or it's children) as required. + *

+ * Implementations of this interface may traverse and/or perform any + * mutations on the specified {@link XmlElement}. + *

+ * Note: It is illegal to modify an {@link XmlElement} + * outside the scope of the provided {@link XmlElement}. eg: Attempting + * to modify any of the parents of the provided {@link XmlElement} + * may result in undefined and unexpected behavior. Only mutations of the + * {@link XmlElement} itself or children is permitted. + * + * @param context the {@link ProcessingContext} in which the + * {@link XmlElement} is being pre-processed + * @param xmlElement the {@link XmlElement} to pre-process + * + * @return true if the specified {@link XmlElement} should be + * reconsidered either by this or other {@link DocumentPreprocessor}s + * for re-preprocessing due to mutations on the {@link XmlElement}, + * false otherwise. + * + * @throws ConfigurationException if during pre-processing of the + * {@link XmlElement} a configuration issue was + * discovered (or if pre-processing fails for some reason) + */ + public boolean preprocess(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/DocumentProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/DocumentProcessor.java new file mode 100644 index 0000000000000..5e235f747cd9f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/DocumentProcessor.java @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.Expression; +import com.tangosol.config.expression.ExpressionParser; +import com.tangosol.config.expression.ParameterResolver; +import com.tangosol.config.expression.SystemPropertyParameterResolver; + +import com.tangosol.run.xml.XmlDocument; +import com.tangosol.run.xml.XmlDocumentReference; +import com.tangosol.run.xml.XmlHelper; + +import com.tangosol.util.Base; +import com.tangosol.util.ResourceRegistry; + +/** + * A {@link DocumentProcessor} is responsible for processing in an {@link XmlDocument} to produce a resulting + * configured resource. + *

+ * During the processing of the {@link XmlDocument}, the provided {@link ResourceRegistry} may be + * accessed/mutated. + * + * @author bo 2011.06.15 + * @since Coherence 12.1.2 + */ +public class DocumentProcessor + { + // ----- constructors ---------------------------------------------------- + + /** + * Construct a {@link DocumentProcessor}. + * + * @param dependencies the {@link Dependencies} for the {@link DocumentProcessor} + */ + public DocumentProcessor(Dependencies dependencies) + { + m_dependencies = dependencies; + } + + // ----- DocumentProcessor methods -------------------------------------- + + /** + * Processes the {@link XmlDocument} located at the specified {@link XmlDocumentReference}. + * + * @param the resource type + * @param refDocument the {@link XmlDocumentReference} + * @param aOverrides reference overrides + * + * @return a configured resource based on processing the root element (and children when required) + * of the {@link XmlDocument} specified by the {@link XmlDocumentReference} + * + * @throws ConfigurationException when a configuration problem was encountered + */ + @SuppressWarnings("unchecked") + public T process(XmlDocumentReference refDocument, XmlDocumentReference... aOverrides) + throws ConfigurationException + { + // load the xml document + XmlDocument xmlDocument = refDocument.getXmlDocument(); + + // apply overrides + for (XmlDocumentReference refOverride : aOverrides) + { + XmlDocument xmlOverride = refOverride.getXmlDocument(); + + XmlHelper.overrideElement(xmlDocument, xmlOverride); + } + + // establish the root processing context + DefaultProcessingContext context = new DefaultProcessingContext(m_dependencies, xmlDocument); + + // add the default namespace handler + NamespaceHandler handler = m_dependencies.getDefaultNamespaceHandler(); + + if (handler != null) + { + context.ensureNamespaceHandler("", handler); + } + + // process the document + Object oResult = context.processDocument(xmlDocument); + + // terminate and tidy up the created context. + context.terminate(); + + return (T) oResult; + } + + // ----- Dependencies interface ----------------------------------------- + + /** + * The {@link Dependencies} of {@link DocumentProcessor}s. + */ + public static interface Dependencies + { + /** + * Obtains the {@link ResourceRegistry} for the {@link DocumentProcessor}. + * + * @return a {@link ResourceRegistry} + */ + ResourceRegistry getResourceRegistry(); + + /** + * Obtains the {@link ClassLoader} to use for dynamically loading classes during processing. + * + * @return the {@link ClassLoader} + */ + ClassLoader getContextClassLoader(); + + /** + * Obtains the {@link ExpressionParser} to use for parsing {@link Expression}s during document processing. + * + * @return the {@link ExpressionParser} + */ + ExpressionParser getExpressionParser(); + + /** + * The {@link NamespaceHandler} for the default (ie: unspecified) xml namespace. + * + * @return the default {@link NamespaceHandler} + */ + NamespaceHandler getDefaultNamespaceHandler(); + + /** + * Obtains the default {@link ParameterResolver} that may be used for + * resolving externally defined configuration parameters, like those + * from the operating system or container. This {@link ParameterResolver} + * is used when one is not provide or one is required during parsing + * and processing the document. + * + * @return the default {@link ParameterResolver} + */ + ParameterResolver getDefaultParameterResolver(); + } + + // ----- DefaultDependencies class -------------------------------------- + + /** + * The {@link DefaultDependencies} is the default implementation of the + * {@link DocumentProcessor} {@link Dependencies} interface. + */ + public static class DefaultDependencies + implements Dependencies + { + // ----- constructors ----------------------------------------------- + + /** + * Constructs a {@link DefaultDependencies}. + */ + public DefaultDependencies() + { + m_namespaceHandler = null; + m_resourceRegistry = null; + m_contextClassLoader = Base.ensureClassLoader(null); + m_exprParser = null; + m_parameterResolver = new SystemPropertyParameterResolver(); + } + + /** + * Constructs a {@link DefaultDependencies} with a default {@link NamespaceHandler}. + * + * @param handler the default {@link NamespaceHandler} + */ + public DefaultDependencies(NamespaceHandler handler) + { + this(); + m_namespaceHandler = handler; + } + + // ----- Dependencies methods --------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public ResourceRegistry getResourceRegistry() + { + return m_resourceRegistry; + } + + /** + * {@inheritDoc} + */ + @Override + public ClassLoader getContextClassLoader() + { + return m_contextClassLoader; + } + + /** + * {@inheritDoc} + */ + @Override + public NamespaceHandler getDefaultNamespaceHandler() + { + return m_namespaceHandler; + } + + /** + * {@inheritDoc} + */ + @Override + public ExpressionParser getExpressionParser() + { + return m_exprParser; + } + + /** + * {@inheritDoc} + */ + @Override + public ParameterResolver getDefaultParameterResolver() + { + return m_parameterResolver; + } + + // ----- DefaultDependencies methods -------------------------------- + + /** + * Sets the {@link ResourceRegistry} that will be used when processing a document. + * + * @param registry the {@link ResourceRegistry} + * + * @return the {@link DefaultDependencies} so that fluent-method-chaining may be used + */ + public DefaultDependencies setResourceRegistry(ResourceRegistry registry) + { + m_resourceRegistry = registry; + + return this; + } + + /** + * Sets the {@link ClassLoader} that will be used to dynamically load classes. + * + * @param classLoader the {@link ClassLoader} + * + * @return the {@link DefaultDependencies} so that fluent-method-chaining may be used + */ + public DefaultDependencies setClassLoader(ClassLoader classLoader) + { + m_contextClassLoader = classLoader; + + return this; + } + + /** + * Sets the {@link NamespaceHandler} for the default namespace of documents to be processed + * + * @param handler the default {@link NamespaceHandler} + * + * @return the {@link DefaultDependencies} so that fluent-method-chaining may be used + */ + public DefaultDependencies setDefaultNamespaceHandler(NamespaceHandler handler) + { + m_namespaceHandler = handler; + + return this; + } + + /** + * Sets the {@link ExpressionParser} to use for parsing {@link Expression}s during document processing. + * + * @param parser the {@link ExpressionParser} + * + * @return the {@link DefaultDependencies} so that fluent-method-chaining may be used + */ + public DefaultDependencies setExpressionParser(ExpressionParser parser) + { + m_exprParser = parser; + + return this; + } + + /** + * Sets the default {@link ParameterResolver} to use for resolving + * externally defined (ie: operating system/container) level parameters. + * + * @param parameterResolver the {@link ParameterResolver} + * + * @return the {@link DefaultDependencies} so that fluent-method-chaining may be used + */ + public DefaultDependencies setDefaultParameterResolver(ParameterResolver parameterResolver) + { + m_parameterResolver = parameterResolver; + + return this; + } + + // ----- data members ----------------------------------------------- + + /** + * The {@link ResourceRegistry} in which to define or access external resources. + */ + private ResourceRegistry m_resourceRegistry; + + /** + * The {@link ClassLoader} from which dynamically requested classes should be loaded. + */ + private ClassLoader m_contextClassLoader; + + /** + * The {@link ExpressionParser} to use for parsing expressions in the document + */ + private ExpressionParser m_exprParser; + + /** + * The {@link NamespaceHandler} for the default xml namespace. + */ + private NamespaceHandler m_namespaceHandler; + + /** + * The default {@link ParameterResolver}. + */ + private ParameterResolver m_parameterResolver; + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Dependencies} for this {@link DocumentProcessor}. + */ + private Dependencies m_dependencies; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/ElementProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/ElementProcessor.java new file mode 100644 index 0000000000000..daf4479dcb30a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/ElementProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.run.xml.XmlElement; + +/** + * An {@link ElementProcessor} is responsible for processing {@link XmlElement} content + * to return a strongly-typed value. + * + * @param the type of value that will be returned by the {@link ElementProcessor} + * + * @author bo 2011.06.14 + * @since Coherence 12.1.2 + */ +public interface ElementProcessor + { + /** + * Process an {@link XmlElement} to return a specific type of value. + * + * @param context the {@link ProcessingContext} in which the + * {@link XmlElement} is being processed + * @param xmlElement the {@link XmlElement} to process + * + * @throws ConfigurationException when a configuration problem was encountered + * + * @return a value of type T + */ + public T process(ProcessingContext context, XmlElement xmlElement) + throws ConfigurationException; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/NamespaceHandler.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/NamespaceHandler.java new file mode 100644 index 0000000000000..d7abc97b86265 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/NamespaceHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.run.xml.XmlAttribute; +import com.tangosol.run.xml.XmlElement; + +import java.net.URI; + +/** + * A {@link NamespaceHandler} is responsible for defining the + * {@link DocumentPreprocessor}, {@link ElementProcessor}s and + * {@link AttributeProcessor}s + * required for processing xml content belonging to a specific xml namespace + * used in an xml document. + * + * @see DocumentPreprocessor + * @see ElementProcessor + * @see AttributeProcessor + * @see DocumentProcessor + * @see AbstractNamespaceHandler + * + * @author bo 2011.06.14 + * @since Coherence 12.1.2 + */ +public interface NamespaceHandler + { + /** + * Obtains the {@link DocumentPreprocessor} that must be applied to the + * {@link XmlElement} (ie: document) in which the {@link NamespaceHandler} + * is defined, prior to {@link XmlElement}s and {@link XmlAttribute}s + * being processed defined by this {@link NamespaceHandler}. + * + * @return the {@link DocumentPreprocessor} or null if + * one is not required or defined for the {@link NamespaceHandler} + */ + DocumentPreprocessor getDocumentPreprocessor(); + + /** + * Obtains the {@link AttributeProcessor} that is suitable for processing the specified {@link XmlAttribute} + * in the xml namespace associated with this {@link NamespaceHandler}. + * + * @param attribute the {@link XmlAttribute} + * + * @return the {@link AttributeProcessor} or null if a suitable {@link AttributeProcessor} could + * not be found + */ + AttributeProcessor getAttributeProcessor(XmlAttribute attribute); + + /** + * Obtains the {@link ElementProcessor} that is suitable for processing the specified {@link XmlElement} + * in the xml namespace associated with this {@link NamespaceHandler}. + * + * @param element the {@link XmlElement} + * + * @return the {@link ElementProcessor} or null if a suitable {@link ElementProcessor} could + * not be found + */ + ElementProcessor getElementProcessor(XmlElement element); + + /** + * Called when the xml namespace associated with the {@link NamespaceHandler} is first encountered in an xml + * document. + * + * @param context the document {@link ProcessingContext} in which the xml namespace was encountered + * @param element the {@link XmlElement} in which the xml namespace was encountered + * @param sPrefix the prefix of the declared xml namespace + * @param uri the {@link URI} of the declared xml namespace + */ + void onStartNamespace(ProcessingContext context, XmlElement element, String sPrefix, URI uri); + + /** + * Called when the xml namespace associated with the {@link NamespaceHandler} is last encountered in an xml document. + * + * @param context the document {@link ProcessingContext} in which the xml namespace was encountered + * @param element the {@link XmlElement} in which the xml namespace was encountered + * @param sPrefix the prefix of the declared xml namespace + * @param uri the {@link URI} of the declared xml namespace + */ + void onEndNamespace(ProcessingContext context, XmlElement element, String sPrefix, URI uri); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/ProcessingContext.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/ProcessingContext.java new file mode 100644 index 0000000000000..7e819f507f429 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/ProcessingContext.java @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; +import com.tangosol.config.expression.ExpressionParser; +import com.tangosol.config.expression.ParameterResolver; + +import com.tangosol.run.xml.XmlAttribute; +import com.tangosol.run.xml.XmlDocument; +import com.tangosol.run.xml.XmlElement; + +import com.tangosol.util.ResourceRegistry; +import com.tangosol.util.ResourceResolver; + +import java.lang.reflect.Type; + +import java.net.URI; + +import java.util.Map; + +/** + * A {@link ProcessingContext} provides contextual information concerning the processing of content in an xml document. + *

+ * {@link ProcessingContext}s additionally provide mechanisms to: + *

    + *
  1. Request processing of other, typically sub xml content, like child {@link XmlElement}s. + * + *
  2. Access the {@link ResourceRegistry} provided to the {@link DocumentProcessor} associated with the + * {@link XmlDocument} being processed. + * + *
  3. Access the {@link ParameterResolver} provided to the {@link DocumentProcessor} associated with the + * {@link XmlDocument} being processed. + * + *
  4. Define additional contextual state (such as cookies) that may be used for injecting values into beans + * during processing. + * + *
  5. Define specialized {@link ElementProcessor}s and {@link AttributeProcessor}s that may be used for + * processing and injecting beans in the {@link ProcessingContext}. + *
+ * + * @author bo 2011.06.15 + * @since Coherence 12.1.2 + */ +public interface ProcessingContext + extends ResourceResolver + { + /** + * Obtains the {@link ResourceRegistry} associated with the {@link ProcessingContext}. + * + * @return a {@link ResourceRegistry} + */ + public ResourceRegistry getResourceRegistry(); + + /** + * Obtains the {@link ClassLoader} to use for loading classes in the {@link ProcessingContext}. + * + * @return {@link ClassLoader} + */ + public ClassLoader getContextClassLoader(); + + /** + * Obtains the {@link ParameterResolver} to use for resolving parameters + * defined externally to the document being processed (ie: the + * operating system or container) + * + * @return the default {@link ParameterResolver}. + */ + public ParameterResolver getDefaultParameterResolver(); + + /** + * Adds the specified named and typed cookie to the {@link ProcessingContext}. + *

+ * If a cookie with the same name and type exists in the {@link ProcessingContext}, it will be replaced by the + * specified cookie. If a cookie with the same name and type has been defined in an outer {@link ProcessingContext}, + * the specified cookie will hide the cookie defined in the outer {@link ProcessingContext}. + * + * @param the type of the cookie + * @param clzCookie the class of the cookie + * @param sCookieName the name of the cookie + * @param cookie the cookie value + */ + public void addCookie(Class clzCookie, String sCookieName, T cookie); + + /** + * Adds the specified cookie to the {@link ProcessingContext}. + *

+ * If a cookie of the same type and name (being the name of the class of + * the said cookie) is already registered with the {@link ProcessingContext}, + * it will be replaced by the specified cookie. + *

+ * If a cookie of the same type and name has been defined in an outer + * {@link ProcessingContext}, the specified cookie will hide the cookie + * defined in the outer {@link ProcessingContext}. + *

+ * This method is equivalent to calling: + * addCookie(clz, clz.getName(), cookie); + * + * @param the type of the cookie + * @param clzCookie the class of the cookie + * @param cookie the cookie value + */ + public void addCookie(Class clzCookie, T cookie); + + /** + * Locates and returns the cookie with the specified type and name. + *

+ * Locating the cookie involves searching the current + * {@link ProcessingContext} for a matching cookie. If one is not found, + * the search continues with outer {@link ProcessingContext}s until a + * cookie is either located or there are no more {@link ProcessingContext}s, + * in which case null is returned. + * + * @param the type of the cookie + * @param clzCookie the class of the cookie + * @param sCookieName the name of the cookie + * + * @return the cookie or null if not defined + */ + public T getCookie(Class clzCookie, String sCookieName); + + /** + * Locates and returns the cookie with the specified type. + *

+ * Locating the cookie involves searching the current + * {@link ProcessingContext} for a matching cookie. If one is not found, + * the search continues with outer {@link ProcessingContext}s until a + * cookie is either located or there are no more {@link ProcessingContext}s, + * in which case null is returned. + *

+ * This method is equivalent to calling: + * getCookie(clz, clz.getName()); + * + * @param the type of the cookie + * @param clzCookie the class of the cookie + * + * @return the cookie or null if not defined + */ + public T getCookie(Class clzCookie); + + /** + * Defines the xml path to locate a specific Java Bean property with in an {@link XmlElement} in the + * {@link ProcessingContext}. + *

+ * This method allows "alias" paths for Java Bean properties to be defined so that {@link #inject(Object, XmlElement)} + * calls may resolve property values correctly. + *

+ * This is an advanced feature. Typically this is only used when: + *

    + *
  1. A Java Bean property name does not match a named value, {@link XmlElement} or {@link XmlAttribute} + * with in a {@link ProcessingContext}. + *
  2. A Java Bean property can not be located with in the {@link ProcessingContext}. + *
+ * + * @param sBeanPropertyName the property name of the bean + * @param sXmlPath the xmlPath to the property value + */ + public void definePropertyPath(String sBeanPropertyName, String sXmlPath); + + /** + * Registers an {@link AttributeProcessor} that may be used to process specific types of values + * contained in {@link XmlAttribute}s with in the {@link ProcessingContext}. + *

+ * When an {@link AttributeProcessor} isn't provided by the associated {@link NamespaceHandler} for an + * {@link XmlAttribute}, an attempt is made to use a type specific {@link AttributeProcessor} to process an + * {@link XmlAttribute} for injection (with {@link #inject(Object, XmlElement)}). + * + * @param the type + * @param clzType the {@link Class} type + * @param processor the {@link AttributeProcessor} for the type + */ + public void registerProcessor(Class clzType, AttributeProcessor processor); + + /** + * Registers an {@link ElementProcessor} that may be used to process specific types of values + * contained in {@link XmlElement}s with in the {@link ProcessingContext}. + *

+ * When an {@link ElementProcessor} isn't provided by the associated {@link NamespaceHandler} for an + * {@link XmlElement}, an attempt is made to use a type specific {@link ElementProcessor} to process an + * {@link XmlElement} for injection (with {@link #inject(Object, XmlElement)}). + * + * @param the type + * @param clzType the {@link Class} type + * @param processor the {@link ElementProcessor} for the type + */ + public void registerProcessor(Class clzType, ElementProcessor processor); + + /** + * Automatically creates and registers an {@link AttributeProcessor} for the specified type. + *

+ * Note: This assumes the type supports a {@link String}-based or {@link XmlAttribute}-based constructor. + * + * @param the type + * @param clzType the type for which to create and register an {@link AttributeProcessor} + */ + public void registerAttributeType(Class clzType); + + /** + * Automatically creates and registers an {@link ElementProcessor} for the specified type. + *

+ * Note: This assumes the type supports a {@link String}-based or {@link XmlElement}-based constructor. + * + * @param the type + * @param clzType the type for which to create and register an {@link ElementProcessor} + */ + public void registerElementType(Class clzType); + + /** + * Obtains the configured {@link ExpressionParser} for this {@link ProcessingContext}. + * + * @return the {@link ExpressionParser} + */ + public ExpressionParser getExpressionParser(); + + /** + * Ensures that an {@link NamespaceHandler} with the specified {@link URI} is available for use in the + * {@link ProcessingContext} with the specified prefix. If a {@link NamespaceHandler} with the specified + * prefix and {@link URI} is not defined by the {@link ProcessingContext}, one is instantiated, registered + * and returned. + * + * @param sPrefix the prefix of the Xml Namespace to use for the {@link NamespaceHandler} + * @param uri the {@link URI} detailing the location of the {@link NamespaceHandler}. Typically this + * will be a java class URI, specified as "class://fully.qualified.class.name" + * + * @return an instance of the {@link NamespaceHandler} that is suitable for processing the prefix and {@link URI} + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public NamespaceHandler ensureNamespaceHandler(String sPrefix, URI uri); + + /** + * Ensures that the specified {@link NamespaceHandler} for the specified prefix is defined in this + * {@link ProcessingContext}. + *

+ * If a {@link NamespaceHandler} for the prefix does not exist in the {@link ProcessingContext}, it is added. + * Otherwise the existing {@link NamespaceHandler} for the prefix is returned. + * + * @param sPrefix the prefix of the xml Namespace to be associated with the {@link NamespaceHandler} + * @param handler the {@link NamespaceHandler} + * + * @return the registered {@link NamespaceHandler} for the {@link ProcessingContext} + */ + public NamespaceHandler ensureNamespaceHandler(String sPrefix, NamespaceHandler handler); + + /** + * Obtains the {@link NamespaceHandler} that is capable of processing the namespace defined with the specified + * {@link URI}. + * + * @param uri the Xml Namespace {@link URI} of the {@link NamespaceHandler} to locate + * + * @return null if a {@link NamespaceHandler} could not be located for the specified {@link URI} + */ + public NamespaceHandler getNamespaceHandler(URI uri); + + /** + * Obtains the {@link NamespaceHandler} which is capable of processing the namespace with the specified prefix. + * + * @param sPrefix the prefix of the xml namespace + * + * @return null if a {@link NamespaceHandler} could not be located for the specified prefix + */ + public NamespaceHandler getNamespaceHandler(String sPrefix); + + /** + * Obtains the {@link URI} that is associated with the specified prefix. + * + * @param sPrefix the XML namespace prefix of the {@link URI} to locate + * + * @return null if a {@link URI} could not be located for the specified {@link URI} + */ + public URI getNamespaceURI(String sPrefix); + + /** + * Obtains the {@link NamespaceHandler}s that are currently in scope + * for this {@link ProcessingContext}. + * + * @return An {@link Iterable} over the {@link NamespaceHandler}s in scope. + */ + public Iterable getNamespaceHandlers(); + + /** + * Request that the document specified by the URI/filename + * (containing the root of an XmlDocument) + * be processed with appropriate {@link NamespaceHandler}s. + *

+ * Should the document root contain any unrecognized xml namespaces, + * an attempt will be made to load appropriate {@link NamespaceHandler}s + * that of which will be used to process said elements in the document. + * + * @param uri the {@link URI} of the XmlDocument to process + * + * @return the result of the processing the root element of the document + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public Object processDocumentAt(URI uri) + throws ConfigurationException; + + /** + * Request that the document specified by the URI/filename + * (containing the root of an XmlDocument) + * be processed with appropriate {@link NamespaceHandler}s. + *

+ * Should the document root contain any unrecognized xml namespaces, + * an attempt will be made to load appropriate {@link NamespaceHandler}s + * that of which will be used to process said elements in the document. + * + * @param sLocation the URI/filename of the XmlDocument to process + * + * @return the result of the processing the root element of the document + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public Object processDocumentAt(String sLocation) + throws ConfigurationException; + + /** + * Request that the specified {@link XmlElement} + * (representing the root of an XmlDocument) + * be processed with appropriate {@link NamespaceHandler}s. + *

+ * Should the document root contain any unrecognized xml namespaces, + * an attempt will be made to load appropriate {@link NamespaceHandler}s + * that of which will be used to process said elements in the document. + * + * @param xmlElement the root {@link XmlElement} of the XmlDocument to process + * + * @return the result of the processing the root element of the document + * represented by the {@link XmlElement} + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public Object processDocument(XmlElement xmlElement) + throws ConfigurationException; + + /** + * Request that the specified xml string + * (representing an xml document) be processed with appropriate + * {@link NamespaceHandler}s. + *

+ * Should the document root contain any unrecognized xml namespaces, + * an attempt will be made to load appropriate {@link NamespaceHandler}s + * that of which will be used to process said elements in the document. + * + * @param sXml a string containing an xml document to process + * + * @return the result of processing the root element of the document + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public Object processDocument(String sXml) + throws ConfigurationException; + + /** + * Request the specified {@link XmlElement} to be processed with an appropriate {@link NamespaceHandler} + * known by the {@link ProcessingContext} or outer {@link ProcessingContext}s. + *

+ * Note: Should the element contain any unrecognized xml namespaces, an attempt will be made to load + * appropriate {@link NamespaceHandler}s that of which will be used to process said elements. + * + * @param xmlElement the {@link XmlElement} to process + * + * @return the result of processing the {@link XmlElement} with an + * appropriate {@link ElementProcessor} + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public Object processElement(XmlElement xmlElement) + throws ConfigurationException; + + /** + * Request the specified xml string be processed with an appropriate {@link NamespaceHandler} + * known by the {@link ProcessingContext}. + *

+ * Note: Should the element contain any unrecognized xml namespaces, an attempt will be made to load + * appropriate {@link NamespaceHandler}s that of which will be used to process said elements. + * + * @param sXml the xml to process + * + * @return the result of processing the root element of the specified xml + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public Object processElement(String sXml) + throws ConfigurationException; + + /** + * Request that all of the child elements contained with in the specified {@link XmlElement} be + * processed using appropriate {@link NamespaceHandler}s known by the {@link ProcessingContext}. + *

+ * This is a convenience method to aid in the processing of all children of an {@link XmlElement}. The keys + * of the returned {@link Map} represent the id attributes each child {@link XmlElement}. If an {@link XmlElement} + * does not have a specified id attribute, a UUID is generated in it's place. + * + * @param xmlElement the parent {@link XmlElement} of the children to process + * + * @return a {@link Map} from identifiable child {@link XmlElement}s (with id="..." attributes) + * and their corresponding processed values + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public Map processElementsOf(XmlElement xmlElement) + throws ConfigurationException; + + /** + * Request that all of the child elements contained within the specified {@link XmlElement} that do not belong + * to the namespace of the said {@link XmlElement} are processed using appropriate processes. + *

+ * This is a convenience method to aid in the processing of all children of an {@link XmlElement}. The keys + * of the returned {@link Map} represent the id attributes each child {@link XmlElement}. If an {@link XmlElement} + * does not have a specified id attribute, a UUID is generated in it's place. + * + * @param xmlElement the parent {@link XmlElement} of the children to process + * + * @return a {@link Map} from identifiable child {@link XmlElement}s (with id="..." attributes) + * and their corresponding processed values + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public Map processForeignElementsOf(XmlElement xmlElement) + throws ConfigurationException; + + /** + * Request that the only child element contained within the {@link XmlElement} is processed + * using an appropriate {@link NamespaceHandler} known by the {@link ProcessingContext}. + * + * @param the type + * @param xmlElement the {@link XmlElement} in which the child is defined + * + * @return the result of processing the child element + * + * @throws ConfigurationException when a configuration problem was encountered, + * especially if there is zero or more than one child + */ + public T processOnlyElementOf(XmlElement xmlElement) + throws ConfigurationException; + + /** + * Request that the last remaining unprocessed child element contained within the specified {@link XmlElement} + * is processed using an appropriate {@link ElementProcessor}. + *

+ * This is a convenience method to aid in the processing of an unprocessed child {@link XmlElement} of an element. + * + * @param the type + * @param xmlElement the parent {@link XmlElement} of the unprocessed child to process + * + * @return the result of processing the child element + * + * @throws ConfigurationException if there are zero or more than one unprocessed child in the element, or + * if some other {@link ConfigurationException} occurred + */ + public T processRemainingElementOf(XmlElement xmlElement) + throws ConfigurationException; + + /** + * Request that the last remaining unprocessed children contained within the specified {@link XmlElement} + * are processed using appropriate {@link ElementProcessor}s. + *

+ * This is a convenience method to aid in the processing of an unprocessed child {@link XmlElement}s of an element. + * The keys of the returned {@link Map} represent the id attributes each child {@link XmlElement}. If an + * {@link XmlElement} does not have a specified id attribute, a UUID is generated in it's place. + * + * @param xmlElement the parent {@link XmlElement} of the unprocessed children to process + * + * @return a {@link Map} from identifiable child {@link XmlElement}s (with id="..." attributes) + * and their corresponding processed values + * + * @throws ConfigurationException when a configuration problem was encountered + */ + public Map processRemainingElementsOf(XmlElement xmlElement) + throws ConfigurationException; + + /** + * Given the information available in the {@link ProcessingContext}, including the cookies, the + * {@link ResourceRegistry} the {@link XmlElement}, its + * {@link XmlAttribute}s and/or children, inject appropriately named and typed values into the specified + * bean (using setter injection). + *

+ * The order in which values are located for injection is as follows; attributed defined by the element, child + * elements defined by the element, alternative paths to values defined in the {@link ProcessingContext}, cookies + * defined by the {@link ProcessingContext} and finally the {@link ResourceRegistry} associated with the + * {@link ProcessingContext}. + * + * @param the bean type + * @param bean the bean to be injected + * @param xmlElement the {@link XmlElement} from which values will be derived for injection into the bean + * + * @return the provided bean but with properties set based on the available values in the {@link ProcessingContext} + * + * @throws ConfigurationException if a configuration is not valid + */ + public B inject(B bean, XmlElement xmlElement) + throws ConfigurationException; + + /** + * Determines if the specified property is defined at the path relative to the specified {@link XmlElement}. + * + * @param sPath the path to the property + * @param xmlElement the {@link XmlElement} in which the property should be searched + * + * @return true if the property is defined, false otherwise + * + * @throws ConfigurationException if a configuration is not valid + */ + public boolean isPropertyDefined(String sPath, XmlElement xmlElement) + throws ConfigurationException; + + /** + * Obtains the strongly typed value for the property defined at the path relative to the specified + * {@link XmlElement}. If the property is not defined or is of the incorrect type, a {@link ConfigurationException} + * is thrown. + * + * @param the type of the property + * @param sPath the path to the property + * @param typeProperty the type of the property + * @param xmlElement the {@link XmlElement} containing the properties for the object + * + * @return the property value + * + * @throws ConfigurationException if a configuration is not valid, the property can't be located or is of the + * wrong type + */ + public T getMandatoryProperty(String sPath, Type typeProperty, XmlElement xmlElement) + throws ConfigurationException; + + /** + * Obtains the strongly typed value for the property defined at the path relative to the specified + * {@link XmlElement}. If the property is not defined, the defaultValue is returned. + * + * @param the type of the property + * @param sPath the path to the property + * @param typeProperty the type of the property + * @param defaultValue the value to return if the property is not found + * @param xmlElement the {@link XmlElement} containing the properties for the object + * + * @return the property value + * + * @throws ConfigurationException if a configuration is not valid + */ + public T getOptionalProperty(String sPath, Type typeProperty, T defaultValue, XmlElement xmlElement) + throws ConfigurationException; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/SimpleAttributeProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/SimpleAttributeProcessor.java new file mode 100644 index 0000000000000..9c857f143c29c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/SimpleAttributeProcessor.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.run.xml.XmlAttribute; +import com.tangosol.run.xml.XmlValue; + +import java.lang.reflect.Constructor; + +/** + * A {@link SimpleAttributeProcessor} is a simple {@link AttributeProcessor} implementation that will construct, + * initialize (via constructor injection) and return a specific type of object based on information in + * an {@link XmlAttribute}. + * + * @author dr 2011.05.14 + * @author bo 2011.06.22 + * @since Coherence 12.1.2 + */ +public class SimpleAttributeProcessor + implements AttributeProcessor + { + // ----- constructor ---------------------------------------------------- + + /** + * Construct a {@link SimpleAttributeProcessor}. + * + * @param clzAttribute the {@link Class} of the attribute value to be returned + */ + public SimpleAttributeProcessor(Class clzAttribute) + { + m_clzAttribute = clzAttribute; + } + + // ----- AttributeProcessor interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public T process(ProcessingContext context, XmlAttribute attribute) + throws ConfigurationException + { + try + { + // is the type an Enum? + if (m_clzAttribute.isEnum()) + { + // determine the value as a string + String sValue = attribute.getXmlValue().getString(); + + try + { + return (T) Enum.valueOf((Class) m_clzAttribute, sValue); + } + catch (Exception exception) + { + // the enum is unknown/unsupported + throw new ClassCastException(String.format("The specified Enum value '%s' is unknown.", sValue)); + } + } + + // attempt to construct the instance with an XmlAttribute + try + { + Constructor constructor = m_clzAttribute.getConstructor(XmlAttribute.class); + + return constructor.newInstance(attribute); + } + catch (NoSuchMethodException e) + { + // SKIP: nothing to do here as we'll try a different constructor + } + + // attempt to construct the instance with an XmlAttribute XmlValue + try + { + Constructor constructor = m_clzAttribute.getConstructor(XmlValue.class); + + return constructor.newInstance(attribute.getXmlValue()); + } + catch (NoSuchMethodException e) + { + // SKIP: nothing to do here as we'll try a different constructor + } + + // attempt to construct the instance with the String representation of the XmlValue + try + { + Constructor constructor = m_clzAttribute.getConstructor(String.class); + + return constructor.newInstance(attribute.getXmlValue().getString()); + } + catch (NoSuchMethodException e) + { + throw new ConfigurationException(String.format("Can't instantiate the required class [%s] using the attribute [%s]", + m_clzAttribute, + attribute), "The required class does not provide a public single argument constructor for either String, XmlAttribute or XmlValue type values."); + } + } + catch (ConfigurationException e) + { + throw e; + } + catch (Exception exception) + { + throw new ConfigurationException(String.format("Can't instantiate the required class [%s] using the attribute [%s]", + m_clzAttribute, + attribute), "The required class must define a public single argument constructor for String, XmlAttribute or XmlValue type values", exception); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Class} of the value returned by the {@link AttributeProcessor}. + */ + private Class m_clzAttribute; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/SimpleElementProcessor.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/SimpleElementProcessor.java new file mode 100644 index 0000000000000..a3dd72079a018 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/SimpleElementProcessor.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import com.tangosol.config.ConfigurationException; + +import com.tangosol.run.xml.XmlElement; +import com.tangosol.run.xml.XmlValue; + +import java.lang.reflect.Constructor; + +/** + * A {@link SimpleElementProcessor} is an {@link ElementProcessor} implementation that will construct, + * initialize (via constructor injection) and return a specific type of object based on information in + * an {@link XmlElement}. + * + * @author dr 2011.05.14 + * @author bo 2011.06.23 + * @since Coherence 12.1.2 + */ +public class SimpleElementProcessor + implements ElementProcessor + { + // ----- constructor ---------------------------------------------------- + + /** + * Construct a {@link SimpleElementProcessor}. + * + * @param clzElement The {@link Class} of objects returned by the {@link ElementProcessor} + */ + public SimpleElementProcessor(Class clzElement) + { + m_clzElement = clzElement; + } + + // ----- ElementProcessor interface ----------------------------------- + + /** + * {@inheritDoc} + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public T process(ProcessingContext context, XmlElement element) + throws ConfigurationException + { + try + { + // is the type an Enum? + if (m_clzElement.isEnum()) + { + // determine the value as a string + String sValue = element.getString(); + + try + { + return (T) Enum.valueOf((Class) m_clzElement, sValue); + } + catch (Exception exception) + { + // the enum is unknown/unsupported + throw new ClassCastException(String.format("The specified Enum value '%s' is unknown.", sValue)); + } + } + + // attempt to construct the instance with an XmlValue + try + { + Constructor constructor = m_clzElement.getConstructor(XmlValue.class); + + return constructor.newInstance(element); + } + catch (NoSuchMethodException e) + { + // SKIP: nothing to do here as we'll try a different constructor + } + + // attempt to construct the instance with an XmlElement + try + { + Constructor constructor = m_clzElement.getConstructor(XmlElement.class); + + return constructor.newInstance(element); + } + catch (NoSuchMethodException e) + { + // SKIP: nothing to do here as we'll try a different constructor + } + + // attempt to construct the instance with the String representation of the XmlElement + try + { + Constructor constructor = m_clzElement.getConstructor(String.class); + + return constructor.newInstance(element.toString()); + } + catch (NoSuchMethodException e) + { + // SKIP: nothing to do here as we'll try a different constructor + } + + // when everything else has failed attempt to construct the instance using setter injection + // starting with a no-args constructor. + return context.inject(m_clzElement.newInstance(), element); + } + catch (ConfigurationException e) + { + throw e; + } + catch (Exception exception) + { + throw new ConfigurationException(String.format("Can't instantiate the required class [%s] using the element [%s]", + m_clzElement, + element), "The required class does not provide a public no args constructor or a single argument constructor for either String, XmlValue or XmlElement type values.", exception); + } + } + + // ----- data members --------------------------------------------------- + + /** + * The {@link Class} of the value returned by the {@link ElementProcessor}. + */ + private Class m_clzElement; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/XmlSimpleName.java b/prj/coherence-core/src/main/java/com/tangosol/config/xml/XmlSimpleName.java new file mode 100644 index 0000000000000..d8f666f1c0ee2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/XmlSimpleName.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.config.xml; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Denotes that a class is associated with the processing of a + * specifically named Xml element or attribute. + *

+ * A simple name, also often called local name, is the part of + * an Xml element or attribute name without the specified Xml namespace. + *

+ * For example: The simple name of the Xml element <example:h1> is "h1". + * The simple name of the Xml element <example> is "example". + *

+ * Typically an {@link XmlSimpleName} annotation is used to identify the + * Xml elements/attributes that {@link ElementProcessor}s/{@link AttributeProcessor}s + * can process with in a {@link NamespaceHandler}. + * + * @author bo 2012.09.14 + * @since Coherence 12.1.2 + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface XmlSimpleName + { + /** + * The simple name of the Xml element or attribute being associated + * with the class for processing. + * + * @return the simple name of an Xml element or attribute + */ + String value(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/config/xml/package.html b/prj/coherence-core/src/main/java/com/tangosol/config/xml/package.html new file mode 100644 index 0000000000000..652a0467b4e3d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/config/xml/package.html @@ -0,0 +1,6 @@ + +Defines classes and interfaces for processing Xml documents and building object +models, like configuration, based on said documents though the use of injection. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aaload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aaload.java new file mode 100644 index 0000000000000..3b49903156042 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aaload.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The AALOAD simple op pushes a reference element from an array onto the +* stack. +*

+* JASM op         :  AALOAD (0x32)
+* JVM byte code(s):  AALOAD (0x32)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Aaload extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Aaload() + { + super(AALOAD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Aaload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aastore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aastore.java new file mode 100644 index 0000000000000..e0c952f69e7e5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aastore.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The AASTORE simple op stores a reference array element. +*

+* JASM op         :  AASTORE (0x53)
+* JVM byte code(s):  AASTORE (0x53)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Aastore extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Aastore() + { + super(AASTORE); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Aastore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AbstractAnnotationsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AbstractAnnotationsAttribute.java new file mode 100644 index 0000000000000..cb5e3927811cd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AbstractAnnotationsAttribute.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Iterator; +import java.util.Vector; + + +/** +* Represents a Java Virtual Machine annotations attribute +* ("RuntimeVisibleAnnotations", "RuntimeInvisibleAnnotations", +* "RuntimeVisibleTypeAnnotations", "RuntimeInvisibleTypeAnnotations"). +*

+* Note: the similarity at the attribute level between type and non-type annotations +* is significant, thus we use the same mechanism to both disassemble and assemble. +* The distinction is in the definition of the 'annotation' and 'type_annotation' +* structure that we allow to be polymorphically defined by sub classes. +*

+* The annotation attributes are defined by the JVM 5 spec and the type annotations +* by the JVM 8 spec as: +*

+*

+*   {RuntimeVisibleAnnotations     | RuntimeInvisibleAnnotations}_attribute
+*    RuntimeVisibleTypeAnnotations | RuntimeInvisibleTypeAnnotations}_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u2 num_annotations;
+*       (annotation | type_annotation) annotations[num_annotations]
+*       }
+* 
+* +* @author rhl 2008.09.23 +* +* @see RuntimeVisibleAnnotationsAttribute +* @see RuntimeInvisibleAnnotationsAttribute +* @see RuntimeVisibleTypeAnnotationsAttribute +* @see RuntimeInvisibleTypeAnnotationsAttribute +*/ +public abstract class AbstractAnnotationsAttribute + extends Attribute + implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an annotations attribute. + * + * @param context the JVM structure containing the attribute + */ + protected AbstractAnnotationsAttribute(VMStructure context, + String sAttr) + { + super(context, sAttr); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + int cAnnotation = stream.readUnsignedShort(); + for (int i = 0; i < cAnnotation; i++) + { + Annotation annotation = instantiateAnnotation(); + annotation.disassemble(stream, pool); + m_listAnnotation.addElement(annotation); + } + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + for (Iterator iter = m_listAnnotation.iterator(); + iter.hasNext(); ) + { + ((Annotation) iter.next()).preassemble(pool); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + + int cBytes = 2; + for (Iterator iter = m_listAnnotation.iterator(); iter.hasNext(); ) + { + cBytes += ((Annotation) iter.next()).getSize(); + } + + stream.writeInt(cBytes); + stream.writeShort(m_listAnnotation.size()); + for (Iterator iter = m_listAnnotation.iterator(); iter.hasNext(); ) + { + ((Annotation) iter.next()).assemble(stream, pool); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + + sb.append(getName() + " for "+ getContext().getIdentity()); + sb.append("\n{"); + for(Iterator iter = getAnnotations(); iter.hasNext(); ) + { + sb.append("\n ").append(iter.next()); + } + sb.append("\n}"); + return sb.toString(); + } + + // ----- accessors ------------------------------------------------------ + + + /** + * Get an Iterator of the annotations in this attribute. + * + * @return the annotations + */ + public Iterator getAnnotations() + { + return m_listAnnotation.iterator(); + } + + /** + * Add an annotation to this attribute. + * + * @param annotation the annotation + */ + public void addAnnotation(Annotation annotation) + { + m_listAnnotation.addElement(annotation); + m_fModified = true; + } + + /** + * Clear the annotations in this attribute. + */ + public void clearAnnotations() + { + m_listAnnotation.clear(); + m_fModified = true; + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + for (Iterator iter = m_listAnnotation.iterator(); iter.hasNext(); ) + { + if (((Annotation) iter.next()).isModified()) + { + return true; + } + } + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + /** + * Return a new instance of an {@link Annotation}. + * + * @return a new instance of an Annotation + */ + protected Annotation instantiateAnnotation() + { + return new Annotation(); + } + + // ----- data members --------------------------------------------------- + + /** + * The annotations. + */ + private Vector m_listAnnotation = new Vector(); + + /** + * Has the attribute been modified? + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AbstractLocalVariableTableAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AbstractLocalVariableTableAttribute.java new file mode 100644 index 0000000000000..ea7c279402475 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AbstractLocalVariableTableAttribute.java @@ -0,0 +1,654 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.TreeSet; +import java.util.Iterator; + + +/** +* Represents a Java Virtual Machine byte-code "LocalVariableTable" or +* "LocalVariableTypeTable attribute which cross-references between +* byte-code variable references by index and source file variable +* references by name; also includes scope information of which portion +* of the code the variable "exists" in. +* +* @version 0.50, 05/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class AbstractLocalVariableTableAttribute + extends Attribute + implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a LocalVariableTable attribute. + * + * @param context the JVM structure containing the attribute + */ + protected AbstractLocalVariableTableAttribute(VMStructure context, + String sAttr) + { + super(context, sAttr); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + clear(); + + stream.readInt(); + + int count = stream.readUnsignedShort(); + for (int i = 0; i < count; ++i) + { + Range range = new Range(); + range.disassemble(stream, pool); + add(range); + } + + resetModified(); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + super.preassemble(pool); + + for (Range range = m_first; range != null; range = range.getNext()) + { + range.preassemble(pool); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(2 + 10 * m_count); + stream.writeShort(m_count); + + if (m_count > 0) + { + Set set = new TreeSet(); + for (Range range = m_first; range != null; range = range.getNext()) + { + set.add(range); + } + + for (Iterator iter = set.iterator(); iter.hasNext(); ) + { + Range range = (Range) iter.next(); + range.assemble(stream, pool); + } + } + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Discard any range information. + */ + public void clear() + { + m_first = null; + m_last = null; + m_fModified = true; + } + + /** + * Determine if there is no variable range information. + * + * @return true if there is no range information, false otherwise + */ + public boolean isEmpty() + { + return m_first == null; + } + + /** + * Add the specified variable definite assignment range. + * + * @param decl + * @param opInit + * @param opStop + */ + protected void add(OpDeclare decl, Op opInit, Op opStop) + { + add(new Range(decl, opInit, opStop)); + } + + /** + * Add the specified range information. + */ + protected void add(Range range) + { + if (m_first == null) + { + m_first = range; + m_last = range; + } + else + { + m_last.setNext(range); + m_last = range; + } + + ++m_count; + m_fModified = true; + } + + /** + * Enumerate the range information. + * + * @return an enumeration of Range objects + */ + public Enumeration ranges() + { + return new Enumeration() + { + public boolean hasMoreElements() + { + return cur != null; + } + + public Object nextElement() + { + if (cur == null) + { + throw new NoSuchElementException(); + } + + Range range = cur; + cur = range.getNext(); + return range; + } + + private Range cur = m_first; + }; + } + + + // ----- Object methods ------------------------------------------------- + + /** + * Provide a debug output of this attribute. + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + + sb.append(getName() + " for method ") + .append(((Method) ((CodeAttribute) getContext()).getContext()).getIdentity()); + + for (Enumeration enmr = ranges(); enmr.hasMoreElements(); ) + { + sb.append("\n ") + .append(enmr.nextElement()); + } + + return sb.toString(); + } + + + // ----- data members --------------------------------------------------- + + /** + * Tracks modification to this object. + */ + private boolean m_fModified; + + /** + * The first range. + */ + private Range m_first; + + /** + * The last range. + */ + private Range m_last; + + /** + * The count of variable definite assignment range objects. + */ + private int m_count; + + + // ----- inner classes -------------------------------------------------- + + /** + * Represents a piece of debugging information that cross-references a + * range of Java Virtual Machine byte-code with a variable which has a + * value within that range. + * + * @version 0.50, 07/20/98, assembler/dis-assembler + * @author Cameron Purdy + */ + public static class Range extends VMStructure implements Constants, Comparable + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a Range object. Used during disassembly. + */ + protected Range() + { + } + + /** + * Construct a Range object. Used during assembly. + * + * @param decl the variable with a definite value in the range + * @param opInit the first op in the range + * @param opStop the first op past the range + */ + protected Range(OpDeclare decl, Op opInit, Op opStop) + { + if (decl == null || opInit == null || opStop == null) + { + String sClass = + "AbstractLocalVariableTableAttribute" + ".Range"; + throw new IllegalArgumentException(sClass + + ": All parameters required!"); + } + + m_decl = decl; + m_opInit = opInit; + m_opStop = opStop; + + m_utfName = new UtfConstant(decl.getVariableName()); + m_utfType = new UtfConstant(decl.getSignature()); + + // only variables with debugging information can be registered + // in the local variable table + if (m_utfName == null || m_utfType == null) + { + String sClass = + "AbstractLocalVariableTableAttribute" + ".Range"; + throw new IllegalArgumentException(sClass + + ": Variable is missing debug information!"); + } + } + + + // ----- VMStructure operations ------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_of = stream.readUnsignedShort(); + m_cb = stream.readUnsignedShort(); + m_utfName = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + m_utfType = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + m_iVar = stream.readUnsignedShort(); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utfName); + pool.registerConstant(m_utfType); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(getOffset()); + stream.writeShort(getLength()); + stream.writeShort(pool.findConstant(m_utfName)); + stream.writeShort(pool.findConstant(m_utfType)); + stream.writeShort(getSlot()); + } + + + // ----- accessors -------------------------------------------------- + + /** + * Get the byte code offset ("pc") of the range. + * + * @return the byte code offset of the range which the variable has a + * definite value + */ + protected int getOffset() + { + return (m_opInit == null ? m_of : m_opInit.getOffset()); + } + + /** + * Get the byte code length ("pc") of the range. + * + * @return the length in bytes of the range which the variable has a + * definite value + */ + protected int getLength() + { + return (m_opStop == null ? m_cb : m_opStop.getOffset() - m_opInit.getOffset()); + } + + /** + * Get the op which starts the range. + * + * @return the op which starts the range + */ + protected Op getInit() + { + return m_opInit; + } + + /** + * Set the op which starts the range. Used during disassembly. + * + * @param op the op which starts the range + */ + protected void setInit(Op op) + { + m_opInit = op; + } + + /** + * Get the op which stops the range. + * + * @return the op which stops the range + */ + protected Op getStop() + { + return m_opStop; + } + + /** + * Set the op which stops the range. Used during disassembly. + * + * @param op the op which stops the range + */ + protected void setStop(Op op) + { + m_opStop = op; + } + + /** + * Get the variable declaration. + * + * @return the OpDeclare instance for the range or null if this was + * disassembled + */ + public OpDeclare getVariable() + { + return m_decl; + } + + /** + * Determine the variable name declared for the range. + * + * @return the variable name or null if unavailable + */ + public String getVariableName() + { + return m_utfName.getValue(); + } + + /** + * Determine the variable JVM signature for the range. + * + * @return the variable signature + */ + public String getSignature() + { + return m_utfType.getValue(); + } + + /** + * Determine the variable slot for the variable declared by this op. + * + * @return the variable slot for this variable declaration + */ + public int getSlot() + { + return (m_decl == null ? m_iVar : m_decl.getSlot()); + } + + /** + * Get the next Range object in the linked list. + * + * @return the next Range object + */ + protected Range getNext() + { + return m_next; + } + + /** + * Set the next Range object in the linked list. + * + * @param next the next Range object + */ + protected void setNext(Range next) + { + m_next = next; + } + + + // ----- Comparable interface --------------------------------------- + + /** + * Compares this object with the specified object for order. Returns a + * negative integer, zero, or a positive integer as this object is less + * than, equal to, or greater than the specified object. + * + * @param o the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this object + * is less than, equal to, or greater than the specified object. + */ + public int compareTo(Object o) + { + Range that = (Range) o; + + // first range goes first + int nResult = this.getOffset() - that.getOffset(); + if (nResult == 0) + { + // shorter range goes first + nResult = that.getLength() - this.getLength(); + if (nResult == 0) + { + // otherwise order by name + nResult = this.m_utfName.compareTo(that.m_utfName); + } + } + + return nResult; + } + + + // ----- Object methods --------------------------------------------- + + /** + * Provide a debug output for this Range. + */ + public String toString() + { + Op opInit = getInit(); + Op opStop = getStop(); + + StringBuffer sb = new StringBuffer(); + sb.append("offset=") + .append(getOffset()) + .append(", length=") + .append(getLength()) + .append(", slot=") + .append(getSlot()); + + if (opInit != null) + { + String sClass = opInit.getClass().getName(); + sb.append(", init=") + .append(opInit) + .append(" (") + .append(sClass.substring(sClass.lastIndexOf('.') + 1)) + .append(") @") + .append(opInit.getOffset()); + } + + if (opStop != null) + { + String sClass = opStop.getClass().getName(); + sb.append(", stop=") + .append(opStop) + .append(" (") + .append(sClass.substring(sClass.lastIndexOf('.') + 1)) + .append(") @") + .append(opStop.getOffset()); + } + + sb.append(", var=") + .append(getVariable()); + + return sb.toString(); + } + + + // ----- data members ----------------------------------------------- + + /** + * The Variable. + */ + private OpDeclare m_decl; + + /** + * The first op in the range. + */ + private Op m_opInit; + + /** + * The first op which is not in the range. + */ + private Op m_opStop; + + /** + * The byte code offset (for a disassembled range). + */ + private int m_of; + + /** + * The byte code length (for a disassembled range). + */ + private int m_cb; + + /** + * The variable name (for a disassembled range). + */ + private UtfConstant m_utfName; + + /** + * The variable descriptor (for a disassembled range). + */ + private UtfConstant m_utfType; + + /** + * The variable slot (for a disassembled range). + */ + private int m_iVar; + + /** + * The next range in the linked list. + */ + private Range m_next; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AbstractParameterAnnotationsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AbstractParameterAnnotationsAttribute.java new file mode 100644 index 0000000000000..2ef102386e386 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AbstractParameterAnnotationsAttribute.java @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + + +/** +* Represents a Java Virtual Machine parameter annotations attribute +* ("RuntimeVisibleParameterAnnotations" or +* "RuntimeInvisibleParameterAnnotations") +* +*

+* The parameter annotation attributes are defined by the JDK 1.5 +* documentation as: +*

+*

+*   {RuntimeVisibleParameterAnnotations | RuntimeInvisibleParameterAnnotations}_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u1 num_parameters;
+*           {
+*           u2 num_annotations;
+*           annotation annotations[num_annotations]
+*           } parameter_annotations[num_parameters]
+*       }
+* 
+* +* @author rhl 2008.09.23 +*/ +public abstract class AbstractParameterAnnotationsAttribute + extends Attribute + implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a parameter annotations attribute. + * + * @param context the JVM structure containing the attribute + */ + protected AbstractParameterAnnotationsAttribute(VMStructure context, + String sAttr) + { + super(context, sAttr); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + int cParam = stream.readUnsignedByte(); + for (int i = 0; i < cParam; i++) + { + Parameter parameter = new Parameter(); + parameter.disassemble(stream, pool); + m_listParameter.addElement(parameter); + } + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + for (Iterator iter = m_listParameter.iterator(); + iter.hasNext(); ) + { + ((Parameter) iter.next()).preassemble(pool); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + + int cBytes = 1; + for (Iterator iter = m_listParameter.iterator(); iter.hasNext(); ) + { + cBytes += ((Parameter) iter.next()).getSize(); + } + stream.writeInt(cBytes); + stream.writeByte((byte) m_listParameter.size()); + for (Iterator iter = m_listParameter.iterator(); iter.hasNext(); ) + { + ((Parameter) iter.next()).assemble(stream, pool); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + + sb.append(getName() + " for "+ getContext().getIdentity()); + sb.append("\n{"); + for(Iterator iter = getParameters(); iter.hasNext(); ) + { + sb.append("\n ").append(iter.next()); + } + sb.append("\n}"); + return sb.toString(); + } + + // ----- accessors ------------------------------------------------------ + + + /** + * Get an Iterator of the parameters in this attribute. + * + * @return the parameters + */ + public Iterator getParameters() + { + return m_listParameter.iterator(); + } + + /** + * Add an annotation to this attribute. + * + * @param annotation the annotation + */ + public void addParameter(Parameter parameter) + { + m_listParameter.addElement(parameter); + m_fModified = true; + } + + /** + * Clear the parameters in this attribute. + */ + public void clearParameters() + { + m_listParameter.clear(); + m_fModified = true; + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + for (Iterator iter = m_listParameter.iterator(); iter.hasNext(); ) + { + if (((Parameter) iter.next()).isModified()) + { + return true; + } + } + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- data members --------------------------------------------------- + + /** + * The parameters. + */ + private Vector m_listParameter = new Vector(); + + /** + * Has the attribute been modified? + */ + private boolean m_fModified; + + + // ----- inner classes -------------------------------------------------- + + /** + * Represents "Parameter" structure of the + * "RuntimeVisibleParameterAnnotations" and + * "RuntimeInvisibleParameterAnnotations" attributes. + */ + public static class Parameter extends VMStructure implements Constants + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a Parameter object. + */ + protected Parameter() + { + } + + + // ----- VMStructure operations ------------------------------------- + + /** + * The disassembly process reads the structure from the passed + * input stream and uses the constant pool to dereference any + * constant references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + int cAnnotation = stream.readUnsignedShort(); + for (int i = 0; i < cAnnotation; i++) + { + Annotation annotation = new Annotation(); + annotation.disassemble(stream, pool); + m_listAnnotation.addElement(annotation); + } + } + + /** + * The pre-assembly step collects the necessary entries for the + * constant pool. During this step, all constants used by this + * VM structure and any sub-structures are registered with (but + * not yet bound by position in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + for (Iterator iter = m_listAnnotation.iterator(); + iter.hasNext(); ) + { + ((Annotation) iter.next()).preassemble(pool); + } + } + + /** + * The assembly process assembles and writes the structure to + * the passed output stream, resolving any dependencies using + * the passed constant pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(m_listAnnotation.size()); + for (Iterator iter = m_listAnnotation.iterator(); iter.hasNext(); ) + { + ((Annotation) iter.next()).assemble(stream, pool); + } + } + + // ----- Object operations ------------------------------------------ + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + + sb.append(" ").append("Parameter"); + sb.append("\n {"); + for(Iterator iter = getAnnotations(); iter.hasNext(); ) + { + sb.append("\n ").append(iter.next()); + } + sb.append("\n }"); + return sb.toString(); + } + + // ----- accessors -------------------------------------------------- + + + /** + * Get an Iterator of the annotations in this parameter. + * + * @return the annotations + */ + public Iterator getAnnotations() + { + return m_listAnnotation.iterator(); + } + + /** + * Add an annotation to this parameter. + * + * @param annotation the annotation + */ + public void addAnnotation(Annotation annotation) + { + m_listAnnotation.addElement(annotation); + m_fModified = true; + } + + /** + * Clear the annotations in this parameter. + */ + public void clearAnnotations() + { + m_listAnnotation.clear(); + m_fModified = true; + } + + /** + * Determine if the parameter has been modified. + * + * @return true if the parameter has been modified + */ + public boolean isModified() + { + for (Iterator iter = m_listAnnotation.iterator(); iter.hasNext(); ) + { + if (((Annotation) iter.next()).isModified()) + { + return true; + } + } + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + /** + * Get the assembled size in bytes of this parameter structure. + */ + protected int getSize() + { + int cBytes = 2; /* num_annotations */ + for (Iterator iter = m_listAnnotation.iterator(); iter.hasNext(); ) + { + cBytes += ((Annotation) iter.next()).getSize(); + } + return cBytes; + } + + // ----- data members ----------------------------------------------- + /** + * The annotations. + */ + private Vector m_listAnnotation = new Vector(); + + /** + * Has the attribute been modified? + */ + private boolean m_fModified; + + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AccessFlags.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AccessFlags.java new file mode 100644 index 0000000000000..4c78abf98d6a5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AccessFlags.java @@ -0,0 +1,763 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + + +/** +* The AccessFlags class represents the unsigned short value containing +* various bit flags referred to as "access_flags" by the JVM specification. +* These flags include accessibility (public, private, and protected), and +* other attributes that apply to classes, fields, and methods, such as +* static, native, and interface. +* +* @version 0.10, 03/03/98, based on prototype dis-assembler +* @version 0.50, 05/07/98, modified for assembler +* @author Cameron Purdy +*/ +public class AccessFlags extends VMStructure implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an AccessFlags object. + */ + protected AccessFlags() + { + } + + /** + * Construct an AccessFlags object. + * + * @param nFlags the initial flag values + */ + protected AccessFlags(int nFlags) + { + m_nPrevFlags = m_nFlags = nFlags; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_nFlags = stream.readUnsignedShort(); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + // no references to constants + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(m_nFlags); + } + + /** + * Determine if the VM structure (or any contained VM structure) has been + * modified. + * + * @return true if the VM structure has been modified + */ + public boolean isModified() + { + return m_nFlags != m_nPrevFlags; + } + + /** + * Reset the modified state of the VM structure. + */ + protected void resetModified() + { + m_nPrevFlags = m_nFlags; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + AccessFlags that = (AccessFlags) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_nFlags == that.m_nFlags; + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + /** + * Provide a human-readable description of the object. + * + * @return a string describing the object + */ + public String toString() + { + return toString(ACC_ALL); + } + + /** + * Provide a human-readable description of the object. + * + * @param nMask specifies which ACC_ bit flags to display + * + * @return a string describing the object + */ + public String toString(int nMask) + { + StringBuffer sb = new StringBuffer(); + int nAccess = m_nFlags & nMask; + if ((nAccess & ACC_PUBLIC ) != 0) {sb.append(" public"); } + if ((nAccess & ACC_PRIVATE ) != 0) {sb.append(" private"); } + if ((nAccess & ACC_PROTECTED ) != 0) {sb.append(" protected"); } + if ((nAccess & ACC_STATIC ) != 0) {sb.append(" static"); } + if ((nAccess & ACC_FINAL ) != 0) {sb.append(" final"); } + if ((nAccess & ACC_SYNCHRONIZED) != 0) {sb.append(" synchronized");} + if ((nAccess & ACC_VOLATILE ) != 0) {sb.append(" volatile"); } + if ((nAccess & ACC_TRANSIENT ) != 0) {sb.append(" transient"); } + if ((nAccess & ACC_BRIDGE ) != 0) {sb.append(" (bridge)"); } + if ((nAccess & ACC_VARARGS ) != 0) {sb.append(" (varargs)"); } + if ((nAccess & ACC_STRICT ) != 0) {sb.append(" strictfp"); } + if ((nAccess & ACC_NATIVE ) != 0) {sb.append(" native"); } + if ((nAccess & ACC_INTERFACE ) != 0) {sb.append(" interface"); } + if ((nAccess & ACC_ABSTRACT ) != 0) {sb.append(" abstract"); } + if ((nAccess & ACC_SYNTHETIC ) != 0) {sb.append(" (synthetic)"); } + if ((nAccess & ACC_ANNOTATION ) != 0) {sb.append(" (annotation)");} + if ((nAccess & ACC_ENUM ) != 0) {sb.append(" (enum)"); } + return (sb.length() == 0 ? "" : sb.toString().substring(1)); + } + + + // ----- accessors ------------------------------------------------------ + + // ----- access_flags + + /** + * Get the value of the access_flags structure. + * + * @return the access_flags bit values + */ + public int getFlags() + { + return m_nFlags; + } + + /** + * Set the value of the access_flags structure. + * + * @param nFlags the access_flags bit values + */ + public void setFlags(int nFlags) + { + m_nFlags = nFlags & ACC_ALL; + } + + + // ----- interface + + /** + * Determine if the interface attribute is set. + * + * @return true if interface + */ + public boolean isInterface() + { + return (m_nFlags & ACC_INTERFACE) != 0; + } + + /** + * Set the interface attribute. + * + * @param fInterface true to set to interface, false to set to class + */ + public void setInterface(boolean fInterface) + { + if (fInterface) + { + m_nFlags |= ACC_INTERFACE; + } + else + { + m_nFlags &= ~ACC_INTERFACE; + } + } + + + // ----- access + + /** + * Get the class/method/field accessibility value. + * + * @return one of ACC_PUBLIC, ACC_PROTECTED, ACC_PRIVATE, or ACC_PACKAGE + */ + public int getAccess() + { + return m_nFlags & ACC_ACCESS_MASK; + } + + /** + * Set the class/method/field accessibility value. + * + * @param nAccess should be one of ACC_PUBLIC, ACC_PROTECTED, + * ACC_PRIVATE, or ACC_PACKAGE + */ + public void setAccess(int nAccess) + { + m_nFlags = m_nFlags & ~ACC_ACCESS_MASK | nAccess & ACC_ACCESS_MASK; + } + + /** + * Determine if the accessibility is public. + * + * @return true if the accessibility is public + */ + public boolean isPublic() + { + return (m_nFlags & ACC_PUBLIC) != 0; + } + + /** + * Set the accessibility to public. + */ + public void setPublic() + { + setAccess(ACC_PUBLIC); + } + + /** + * Determine if the accessibility is protected. + * + * @return true if the accessibility is protected + */ + public boolean isProtected() + { + return (m_nFlags & ACC_PROTECTED) != 0; + } + + /** + * Set the accessibility to protected. + */ + public void setProtected() + { + setAccess(ACC_PROTECTED); + } + + /** + * Determine if the accessibility is package private. + * + * @return true if the accessibility is package private + */ + public boolean isPackage() + { + return (m_nFlags & ACC_ACCESS_MASK) == 0; + } + + /** + * Set the accessibility to package private. + */ + public void setPackage() + { + setAccess(ACC_PACKAGE); + } + + /** + * Determine if the accessibility is private. + * + * @return true if the accessibility is private + */ + public boolean isPrivate() + { + return (m_nFlags & ACC_PRIVATE) != 0; + } + + /** + * Set the accessibility to private. + */ + public void setPrivate() + { + setAccess(ACC_PRIVATE); + } + + + // ----- abstract + + /** + * Determine if the abstract attribute is set. + * + * @return true if abstract + */ + public boolean isAbstract() + { + return (m_nFlags & ACC_ABSTRACT) != 0; + } + + /** + * Set the abstract attribute. + * + * @param fAbstract true to set to abstract, false to set to concrete + */ + public void setAbstract(boolean fAbstract) + { + if (fAbstract) + { + m_nFlags |= ACC_ABSTRACT; + } + else + { + m_nFlags &= ~ACC_ABSTRACT; + } + } + + // ----- synthetic + + /** + * Determine if the synthetic attribute is set. + * + * @return true if a synthetic type + */ + public boolean isSynthetic() + { + return (m_nFlags & ACC_SYNTHETIC) != 0; + } + + /** + * Set the synthetic attribute. + * + * @param fSynthetic true to set to synthetic, false to set to + * non-synthetic class/interface type + */ + public void setSynthetic(boolean fSynthetic) + { + if (fSynthetic) + { + m_nFlags |= ACC_SYNTHETIC; + } + else + { + m_nFlags &= ~ACC_SYNTHETIC; + } + } + + // ----- bridge + + /** + * Determine if the bridge attribute is set. + * + * @return true if a bridge method + */ + public boolean isBridge() + { + return (m_nFlags & ACC_BRIDGE) != 0; + } + + /** + * Set the bridge attribute. + * + * @param fBridge true to set to bridge, false to set to + * non-bridge method + */ + public void setBridge(boolean fBridge) + { + if (fBridge) + { + m_nFlags |= ACC_BRIDGE; + } + else + { + m_nFlags &= ~ACC_BRIDGE; + } + } + + // ----- varargs + + /** + * Determine if the varargs attribute is set. + * + * @return true if a varargs method + */ + public boolean isVarArgs() + { + return (m_nFlags & ACC_VARARGS) != 0; + } + + /** + * Set the varargs attribute. + * + * @param fVarArgs true to set to varargs method, false to + * set to non-varargs method + */ + public void setVarArgs(boolean fVarArgs) + { + if (fVarArgs) + { + m_nFlags |= ACC_VARARGS; + } + else + { + m_nFlags &= ~ACC_VARARGS; + } + } + + // ----- strict + + /** + * Determine if the strict attribute is set. + * + * @return true if a strict method + */ + public boolean isStrict() + { + return (m_nFlags & ACC_STRICT) != 0; + } + + /** + * Set the strict attribute. + * + * @param fStrict true to set to a strictfp method, false to + * set to non-strictfp method + */ + public void setStrict(boolean fStrict) + { + if (fStrict) + { + m_nFlags |= ACC_STRICT; + } + else + { + m_nFlags &= ~ACC_STRICT; + } + } + + + // ----- annotation + + /** + * Determine if the annotation attribute is set. + * + * @return true if an annotation type + */ + public boolean isAnnotation() + { + return (m_nFlags & ACC_ANNOTATION) != 0; + } + + /** + * Set the annotation attribute. + * + * @param fAnnotation true to set to annotation type, false to set to + * class/interface type + */ + public void setAnnotation(boolean fAnnotation) + { + if (fAnnotation) + { + m_nFlags |= ACC_ANNOTATION; + } + else + { + m_nFlags &= ~ACC_ANNOTATION; + } + } + + // ----- enum + + /** + * Determine if the enum attribute is set. + * + * @return true if an enum type + */ + public boolean isEnum() + { + return (m_nFlags & ACC_ENUM) != 0; + } + + /** + * Set the enum attribute. + * + * @param fEnum true to set to enum type, false to set to + * class/interface type + */ + public void setEnum(boolean fEnum) + { + if (fEnum) + { + m_nFlags |= ACC_ENUM; + } + else + { + m_nFlags &= ~ACC_ENUM; + } + } + + // ----- static + + /** + * Determine the static attribute value. + * + * @return true if static + */ + public boolean isStatic() + { + return (m_nFlags & ACC_STATIC) != 0; + } + + /** + * Set the static attribute. + * + * @param fStatic true to set to static, false to set to instance + */ + public void setStatic(boolean fStatic) + { + if (fStatic) + { + m_nFlags |= ACC_STATIC; + } + else + { + m_nFlags &= ~ACC_STATIC; + } + } + + + // ----- final + + /** + * Determine the final attribute value. + * + * @return true if final + */ + public boolean isFinal() + { + return (m_nFlags & ACC_FINAL) != 0; + } + + /** + * Set the final attribute. + * + * @param fFinal true to set to final, false to set to derivable + */ + public void setFinal(boolean fFinal) + { + if (fFinal) + { + m_nFlags |= ACC_FINAL; + } + else + { + m_nFlags &= ~ACC_FINAL; + } + } + + + // ----- synchronized + + /** + * Determine if the synchronized attribute is set. + * + * @return true if synchronized + */ + public boolean isSynchronized() + { + return ((m_nFlags & ACC_SYNCHRONIZED) != 0); + } + + /** + * Set the synchronized attribute. + * + * @param fSync true to set to synchronized, false otherwise + */ + public void setSynchronized(boolean fSync) + { + if (fSync) + { + m_nFlags |= ACC_SYNCHRONIZED; + } + else + { + m_nFlags &= ~ACC_SYNCHRONIZED; + } + } + + + // ----- native + + /** + * Determine if the native attribute is set. + * + * @return true if native + */ + public boolean isNative() + { + return (m_nFlags & ACC_NATIVE) != 0; + } + + /** + * Set the native attribute. + * + * @param fNative true to set to native, false to set to Java + */ + public void setNative(boolean fNative) + { + if (fNative) + { + m_nFlags |= ACC_NATIVE; + } + else + { + m_nFlags &= ~ACC_NATIVE; + } + } + + + // ----- volatile + + /** + * Determine if the volatile attribute is set. + * + * @return true if volatile + */ + public boolean isVolatile() + { + return (m_nFlags & ACC_VOLATILE) != 0; + } + + /** + * Set the volatile attribute. + * + * @param fVolatile true to set to volatile, false otherwise + */ + public void setVolatile(boolean fVolatile) + { + if (fVolatile) + { + m_nFlags |= ACC_VOLATILE; + } + else + { + m_nFlags &= ~ACC_VOLATILE; + } + } + + + // ----- transient + + /** + * Determine if the transient attribute is set. + * + * @return true if transient + */ + public boolean isTransient() + { + return (m_nFlags & ACC_TRANSIENT) != 0; + } + + /** + * Set the transient attribute. + * + * @param fTransient true to set to transient, false to set to persistent + */ + public void setTransient(boolean fTransient) + { + if (fTransient) + { + m_nFlags |= ACC_TRANSIENT; + } + else + { + m_nFlags &= ~ACC_TRANSIENT; + } + } + + // ----- validation + + /** + * Return whether at least the provided bit mask of access flags is set. + * + * @param nMask a bit-mask of the access flags that should be set + * + * @return whether all bits in the provided bit-mask are set + */ + protected boolean isMaskSet(int nMask) + { + return (m_nFlags & nMask) != 0; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "AccessFlags"; + + /** + * The access_flags bit values. + */ + private int m_nFlags; + + /** + * Tracks whether the flags have been modified. + */ + private int m_nPrevFlags; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aconst.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aconst.java new file mode 100644 index 0000000000000..e464367b748a2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aconst.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataOutput; + + +/** +* The ACONST variable-size op pushes a reference constant onto the stack. +*

+* JASM op         :  ACONST      (0xe9)
+* JVM byte code(s):  ACONST_NULL (0x01)
+*                    LDC         (0x12??)
+*                    LDC_W       (0x13????)
+* Details         :  The only non-null reference constants are of the string
+*                    or class type (StringConstant or ClassConstant).
+*                    This usage of ClassConstant was introduced in JDK 1.5
+*                    (Classfile version 49.0).
+* 
+* +* @version 0.50, 06/11/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Aconst extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Aconst() + { + super(ACONST); + } + + /** + * Construct the op. + * + * @param s a reference constant to push; a String or null + */ + public Aconst(String s) + { + this(s == null ? null : new StringConstant(s)); + } + + /** + * Construct the op. + * + * @param constant a reference constant to push; a ClassConstant or null + */ + public Aconst(ClassConstant constant) + { + super(ACONST); + m_constant = constant; + } + + /** + * Construct the op. + * + * @param constant a reference constant to push; a StringConstant or null + */ + public Aconst(StringConstant constant) + { + super(ACONST); + m_constant = constant; + } + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + String sConst = (m_constant == null ? "null" : m_constant.format()); + return format(null, getName() + ' ' + sConst, null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + String sConst = (m_constant == null ? "null" : m_constant.format()); + return getName() + ' ' + sConst; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + Constant constant = m_constant; + if (constant != null) + { + pool.registerConstant(constant); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + Constant constant = m_constant; + if (constant == null) + { + stream.writeByte(ACONST_NULL); + } + else + { + int iConst = pool.findConstant(constant); + if (iConst <= 0xFF) + { + stream.writeByte(LDC); + stream.writeByte(iConst); + } + else + { + stream.writeByte(LDC_W); + stream.writeShort(iConst); + } + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + Constant constant = m_constant; + if (constant == null) + { + setSize(1); + } + else if (pool.findConstant(constant) <= 0xFF) + { + setSize(2); + } + else + { + setSize(3); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Aconst"; + + /** + * The constant loaded by this op, or null. + */ + private Constant m_constant; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aload.java new file mode 100644 index 0000000000000..570d79e1a5f8b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Aload.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ALOAD variable-size op pushes a reference variable (one word) onto the +* stack. +*

+* JASM op         :  ALOAD    (0x19)
+* JVM byte code(s):  ALOAD    (0x19)
+*                    ALOAD_0  (0x2a)
+*                    ALOAD_1  (0x2b)
+*                    ALOAD_2  (0x2c)
+*                    ALOAD_3  (0x2d)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Aload extends OpLoad implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to push + */ + public Aload(Avar var) + { + super(ALOAD, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Aload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Anewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Anewarray.java new file mode 100644 index 0000000000000..b43d3928c945e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Anewarray.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ANEWARRAY op creates an array of references of a type specified by the +* ClassConstant. +*

+* JASM op         :  ANEWARRAY    (0xbd)
+* JVM byte code(s):  ANEWARRAY    (0xbd)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Anewarray extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the ClassConstant + */ + public Anewarray(ClassConstant constant) + { + super(ANEWARRAY, constant); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Anewarray"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Annotation.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Annotation.java new file mode 100644 index 0000000000000..95a62a7bacc75 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Annotation.java @@ -0,0 +1,1099 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Vector; + + +/** +* Represents a Java Virtual Machine Annotation structure in +* "RuntimeVisibleAnnotations", "RuntimeInvsibleAnnotations", +* "RuntimeVisibleParameterAnnotations", +* "RuntimeInvisibleParameterAnnotations", and "AnnotationDefault" +* attributes. +* +*

+* The Annotation structure is defined by the JDK 1.5 documentation as: +*

+*

+*   Annotation
+*       {
+*       u2 type_index; 
+*       u2 num_element_value_pairs; 
+*           {
+*           u2 element_name_index; 
+*           element_value value; 
+*           } element_value_pairs[num_element_value_pairs] 
+*       }
+*
+*   element_value
+*       {
+*       u1 tag;
+*       union
+*           {
+*           u2 const_value_index; 
+*               { 
+*               u2 type_name_index; 
+*               u2 const_name_index; 
+*               } enum_const_value; 
+*           u2 class_info_index; 
+*           annotation annotation_value; 
+*               { 
+*               u2 num_values; 
+*               element_value values[num_values]; 
+*               } array_value; 
+*           } value; 
+*       }
+* 
+* +* @author rhl 2008.09.23 +*/ +public class Annotation + extends VMStructure + implements Constants + { + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_utfType = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + int cElementValue = stream.readUnsignedShort(); + for (int i = 0; i < cElementValue; i++) + { + UtfConstant utfElementName; + AbstractElementValue elementValue; + + utfElementName = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + elementValue = AbstractElementValue.loadElementValue(stream, pool); + + m_mapElementValue.put(utfElementName, elementValue); + } + } + + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utfType); + + for (Iterator iter = m_mapElementValue.entrySet().iterator(); + iter.hasNext();) + { + Map.Entry entry = (Map.Entry) iter.next(); + ((UtfConstant) entry.getKey()).preassemble(pool); + ((AbstractElementValue) entry.getValue()).preassemble(pool); + } + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(m_utfType)); + stream.writeShort(m_mapElementValue.size()); + for (Iterator iter = m_mapElementValue.entrySet().iterator(); + iter.hasNext();) + { + Map.Entry entry = (Map.Entry) iter.next(); + stream.writeShort(pool.findConstant((UtfConstant) entry.getKey())); + ((AbstractElementValue) entry.getValue()).assemble(stream, pool); + } + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + for (Iterator iter = m_mapElementValue.values().iterator(); + iter.hasNext(); ) + { + if (((AbstractElementValue) iter.next()).isModified()) + { + return true; + } + } + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- AbstractAnnotation operations ---------------------------------- + + /** + * Get the assembled size in bytes of this annotation structure. + */ + public int getSize() + { + int cBytes = 0; + + cBytes += 2; /* type_index */ + cBytes += 2; /* num_element_value_pairs */ + + for (Iterator iter = m_mapElementValue.values().iterator(); + iter.hasNext(); ) + { + cBytes += 2; /* element_name_index */ + cBytes += ((AbstractElementValue) iter.next()).getSize(); + } + return cBytes; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the type of this annotation. + */ + public UtfConstant getAnnotationType() + { + return m_utfType; + } + + public void setAnnotationType(UtfConstant utfType) + { + m_utfType = utfType; + m_fModified = true; + } + + /** + * Set an element value in this annotation structure. + */ + public void setElementValue(UtfConstant utfElementName, + AbstractElementValue elementValue) + { + m_mapElementValue.put(utfElementName, elementValue); + m_fModified = true; + } + + /** + * Get the element value associated with the element name in this + * annotation structure, or null if the element does not exist. + */ + public AbstractElementValue getElementValue(UtfConstant utfElementName) + { + return (AbstractElementValue) m_mapElementValue.get(utfElementName); + } + + /** + * Get an Iterator of the element names in this annotation structure. + */ + public Iterator getElementNames() + { + return m_mapElementValue.keySet().iterator(); + } + + /** + * Clear the element values. + */ + public void clearElementValues() + { + m_mapElementValue.clear(); + m_fModified = true; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Annotation"; + + /** + * The type of the annotation. + */ + private UtfConstant m_utfType; + + /** + * The element-values. + */ + private HashMap m_mapElementValue = new HashMap(); + + /** + * Has the annotation been modified? + */ + private boolean m_fModified; + + + // ----- inner class: AbstractElementValue ------------------------------ + + /** + * Represents an element_value structure used by annotation-related + * attributes. + */ + public static abstract class AbstractElementValue + extends VMStructure implements Constants + { + // ----- constructors ----------------------------------------------- + + protected AbstractElementValue(char cTag) + { + m_cTag = cTag; + } + + protected static AbstractElementValue loadElementValue(DataInput stream, + ConstantPool pool) + throws IOException + { + AbstractElementValue elementValue = null; + char cTag = (char) stream.readByte(); + + switch (cTag) + { + case TAGTYPE_BYTE: + case TAGTYPE_CHAR: + case TAGTYPE_DOUBLE: + case TAGTYPE_FLOAT: + case TAGTYPE_INT: + case TAGTYPE_LONG: + case TAGTYPE_SHORT: + case TAGTYPE_BOOLEAN: + case TAGTYPE_STRING: + { + /* primitive or string value */ + elementValue = new ConstantElementValue(cTag); + break; + } + case TAGTYPE_ENUM: + { + /* enum value */ + elementValue = new EnumElementValue(); + break; + } + case TAGTYPE_CLASS: + { + /* class value */ + elementValue = new ClassElementValue(); + break; + } + case TAGTYPE_ANNOTATION: + { + /* annotation value */ + elementValue = new AnnotationElementValue(); + break; + } + case TAGTYPE_ARRAY: + { + /* array value */ + elementValue = new ArrayElementValue(); + break; + } + default: + { + throw new IllegalArgumentException(CLASS + ".loadElementValue: unknown ElementValue tag type " + cTag); + } + } + elementValue.disassemble(stream, pool); + return elementValue; + } + + + // ----- VMStructure operations ------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeByte((byte) m_cTag); + } + + + // ----- accessors -------------------------------------------------- + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + */ + protected void resetModified() + { + m_fModified = false; + } + + /** + * Get the assembled size in bytes of this element value structure. + */ + protected int getSize() + { + return 1; /* tag */ + } + + // ----- constants -------------------------------------------------- + + /* byte element type */ + public static final char TAGTYPE_BYTE = 'B'; + + /* char element type */ + public static final char TAGTYPE_CHAR = 'C'; + + /* double element type */ + public static final char TAGTYPE_DOUBLE = 'D'; + + /* float element type */ + public static final char TAGTYPE_FLOAT = 'F'; + + /* int element type */ + public static final char TAGTYPE_INT = 'I'; + + /* long element type */ + public static final char TAGTYPE_LONG = 'J'; + + /* short element type */ + public static final char TAGTYPE_SHORT = 'S'; + + /* boolean element type */ + public static final char TAGTYPE_BOOLEAN = 'Z'; + + /* String element type */ + public static final char TAGTYPE_STRING = 's'; + + /* enum constant element type */ + public static final char TAGTYPE_ENUM = 'e'; + + /* class element type */ + public static final char TAGTYPE_CLASS = 'c'; + + /* annotation element type */ + public static final char TAGTYPE_ANNOTATION = '@'; + + /* array element type */ + public static final char TAGTYPE_ARRAY = '['; + + // ----- data members ----------------------------------------------- + + /** + * Tracks modification to this object. + */ + protected boolean m_fModified; + + /** + * The ElementValue type. + */ + private char m_cTag; + } + + /** + * Represents a constant element value in an annotation structure. + */ + public static class ConstantElementValue extends AbstractElementValue + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a ConstantElementValue object. Used during disassembly. + */ + protected ConstantElementValue(char cType) + { + super(cType); + } + + /** + * Construct a ConstantElementValue object. + */ + public ConstantElementValue(char cType, Constant constValue) + { + super(cType); + m_constValue = constValue; + } + + // ----- accessors -------------------------------------------------- + + /** + * Get the constant value + */ + public Constant getConstantValue() + { + return m_constValue; + } + + /** + * Set the constant value + */ + public void setConstantValue(Constant constValue) + { + m_constValue = constValue; + m_fModified = true; + } + + /** + * Get the assembled size in bytes of this element value structure. + */ + protected int getSize() + { + return 2 + super.getSize(); /* const_value_index */ + } + + // ----- VMStructure operations ------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_constValue = (Constant) pool.getConstant(stream.readUnsignedShort()); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_constValue); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeShort(pool.findConstant(m_constValue)); + } + + // ----- data members ----------------------------------------------- + + /** + * The constant value. + */ + private Constant m_constValue; + } + + /** + * Represents a class element value in an annotation structure. + */ + public static class ClassElementValue extends AbstractElementValue + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a ClassElementValue object. Used during disassembly. + */ + protected ClassElementValue() + { + super(TAGTYPE_CLASS); + } + + /** + * Construct a ClassElementValue object. + */ + public ClassElementValue(UtfConstant utfClassType) + { + super(TAGTYPE_CLASS); + m_utfClassType = utfClassType; + } + + // ----- accessors -------------------------------------------------- + + /** + * Get the class type + */ + public UtfConstant getClassType() + { + return m_utfClassType; + } + + /** + * Set the class type + */ + public void setClassType(UtfConstant utfClassType) + { + m_utfClassType = utfClassType; + m_fModified = true; + } + + /** + * Get the assembled size in bytes of this element value structure. + */ + protected int getSize() + { + return 2 + super.getSize(); /* class_info_index */ + } + + // ----- VMStructure operations ------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_utfClassType = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utfClassType); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeShort(pool.findConstant(m_utfClassType)); + } + + // ----- data members ----------------------------------------------- + + /** + * The class type . + */ + private UtfConstant m_utfClassType; + } + + + /** + * Represents an enum value in an annotation structure. + */ + public static class EnumElementValue extends AbstractElementValue + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a EnumElementValue object. Used during disassembly. + */ + protected EnumElementValue() + { + super(TAGTYPE_ENUM); + } + + /** + * Construct a EnumElementValue object. + */ + public EnumElementValue(UtfConstant utfEnumName, UtfConstant utfEnumType) + { + super(TAGTYPE_ENUM); + m_utfEnumName = utfEnumName; + m_utfEnumType = utfEnumType; + } + + // ----- accessors -------------------------------------------------- + + /** + * Get the enum name. + */ + public UtfConstant getEnumName() + { + return m_utfEnumName; + } + + /** + * Set the enum name. + */ + public void setEnumName(UtfConstant utfEnumName) + { + m_utfEnumName = utfEnumName; + m_fModified = true; + } + + /** + * Get the enum type. + */ + public UtfConstant getEnumType() + { + return m_utfEnumType; + } + + /** + * Set the enum type. + */ + public void setEnumType(UtfConstant utfEnumType) + { + m_utfEnumType = utfEnumType; + m_fModified = true; + } + + /** + * Get the assembled size in bytes of this element value structure. + */ + protected int getSize() + { + return 4 + super.getSize(); /* type_name_index, const_name_index */ + } + + // ----- VMStructure operations ------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_utfEnumType = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + m_utfEnumName = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utfEnumType); + pool.registerConstant(m_utfEnumName); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeShort(pool.findConstant(m_utfEnumType)); + stream.writeShort(pool.findConstant(m_utfEnumName)); + } + + // ----- data members ----------------------------------------------- + + /** + * The enum name. + */ + private UtfConstant m_utfEnumName; + + /** + * The enum type. + */ + private UtfConstant m_utfEnumType; + } + + /** + * Represents an annotation element value in an annotation structure. + */ + public static class AnnotationElementValue extends AbstractElementValue + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a AnnotationElementValue object. Used during disassembly. + */ + protected AnnotationElementValue() + { + super(TAGTYPE_ANNOTATION); + } + + /** + * Construct a AnnotationElementValue object. + */ + public AnnotationElementValue(Annotation annotationValue) + { + super(TAGTYPE_ANNOTATION); + m_annotationValue = annotationValue; + } + + // ----- accessors -------------------------------------------------- + + /** + * Get the annotation. + */ + public Annotation getAnnotation() + { + return m_annotationValue; + } + + /** + * Set the annotation. + */ + public void setAnnotation(Annotation annotationValue) + { + m_annotationValue = annotationValue; + m_fModified = true; + } + + /** + * Get the assembled size in bytes of this element value structure. + */ + protected int getSize() + { + return m_annotationValue.getSize() + super.getSize(); + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return super.isModified() || m_annotationValue.isModified(); + } + + // ----- VMStructure operations ------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_annotationValue = new Annotation(); + m_annotationValue.disassemble(stream, pool); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + m_annotationValue.preassemble(pool); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + m_annotationValue.assemble(stream, pool); + } + + // ----- data members ----------------------------------------------- + + /** + * The annotation value. + */ + private Annotation m_annotationValue; + } + + + /** + * Represents an array element value in an annotation structure. + */ + public static class ArrayElementValue extends AbstractElementValue + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a ArrayElementValue object. Used during disassembly. + */ + protected ArrayElementValue() + { + super(TAGTYPE_ARRAY); + } + + /** + * Construct a ArrayElementValue object. + */ + public ArrayElementValue(List listElement) + { + super(TAGTYPE_ARRAY); + m_listElement = new Vector(listElement); + } + + // ----- accessors -------------------------------------------------- + + /** + * Get the list of elements. + */ + public Iterator getElements() + { + return m_listElement.iterator(); + } + + /** + * Add elementValue to the list of elements. + */ + public void add(AbstractElementValue elementValue) + { + m_listElement.addElement(elementValue); + m_fModified = true; + } + + /** + * Clear the list of elements. + */ + public void clear() + { + m_listElement.clear(); + m_fModified = true; + } + + /** + * Set the list of elements. + */ + public void setElements(List listElement) + { + m_listElement.clear(); + m_listElement.addAll(listElement); + m_fModified = true; + } + + /** + * Get the assembled size in bytes of this element value structure. + */ + protected int getSize() + { + int cBytes = super.getSize(); + cBytes += 2; /* num_values */ + for (Iterator iter = m_listElement.iterator(); iter.hasNext();) + { + cBytes += ((AbstractElementValue) iter.next()).getSize(); + } + return cBytes; + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + for (Iterator iter = m_listElement.iterator(); iter.hasNext();) + { + if (((AbstractElementValue) iter.next()).isModified()) + { + return true; + } + } + return super.isModified(); + } + + // ----- VMStructure operations ------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + int cElement = stream.readUnsignedShort(); + for (int i = 0; i < cElement; i++) + { + AbstractElementValue elementValue = + AbstractElementValue.loadElementValue(stream, pool); + m_listElement.addElement(elementValue); + } + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + for (Iterator iter = m_listElement.iterator(); iter.hasNext();) + { + ((AbstractElementValue) iter.next()).preassemble(pool); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_listElement.size()); + for (Iterator iter = m_listElement.iterator(); iter.hasNext();) + { + ((AbstractElementValue) iter.next()).assemble(stream, pool); + } + } + + // ----- data members ----------------------------------------------- + + /** + * The list of element values. + */ + private Vector m_listElement = new Vector(); + } + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AnnotationDefaultAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AnnotationDefaultAttribute.java new file mode 100644 index 0000000000000..f5759af33512f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/AnnotationDefaultAttribute.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a Java Virtual Machine "AnnotationDefault" attribute which +* specifies the class/method/field signature. +* +*

+* The Signature Attribute is defined by the JDK 1.5 documentation as: +*

+*

+*   AnnotationDefault_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       element_value default_value;
+*       }
+* 
+* +* @author rhl 2008.09.23 +*/ +public class AnnotationDefaultAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a annotation default attribute. + * + * @param context the JVM structure containing the attribute + */ + protected AnnotationDefaultAttribute(VMStructure context) + { + super(context, ATTR_ANNOTDEFAULT); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + m_elementValue = Annotation.AbstractElementValue.loadElementValue(stream, pool); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + m_elementValue.preassemble(pool); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(m_elementValue.getSize()); + m_elementValue.assemble(stream, pool); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return super.getName() + '=' + m_elementValue; + } + + + // ----- accessors ------------------------------------------------------ + + + /** + * Determine the default element value. + * + * @return the default element value + */ + public Annotation.AbstractElementValue getElementValue() + { + return m_elementValue; + } + + /** + * Set the default element value. + * + * @param elementValue the signature + */ + public void setElementValue(Annotation.AbstractElementValue elementValue) + { + m_elementValue = elementValue; + m_fModified = true; + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified || m_elementValue.isModified(); + } + + /** + * Reset the modified state of the VM structure. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- data members --------------------------------------------------- + + /** + * The default element value. + */ + private Annotation.AbstractElementValue m_elementValue; + + /** + * Has the attribute been modified? + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Areturn.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Areturn.java new file mode 100644 index 0000000000000..77a6fb74543b4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Areturn.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ARETURN simple op returns a reference value from the method. +*

+* JASM op         :  ARETURN  (0xb0)
+* JVM byte code(s):  ARETURN  (0xb0)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Areturn extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Areturn() + { + super(ARETURN); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Areturn"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Arraylength.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Arraylength.java new file mode 100644 index 0000000000000..8c480b383b9c9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Arraylength.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ARRAYLENGTH simple op calculates the size of the array which is +* located on the stack. +*

+* JASM op         :  ARRAYLENGTH  (0xbe)
+* JVM byte code(s):  ARRAYLENGTH  (0xbe)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Arraylength extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Arraylength() + { + super(ARRAYLENGTH); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Arraylength"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Astore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Astore.java new file mode 100644 index 0000000000000..bf56294a70506 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Astore.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ASTORE variable-size op stores a reference variable. +*

+* JASM op         :  ASTORE    (0x3a)
+* JVM byte code(s):  ASTORE    (0x3a)
+*                    ASTORE_0  (0x4b)
+*                    ASTORE_1  (0x4c)
+*                    ASTORE_2  (0x4d)
+*                    ASTORE_3  (0x4e)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Astore extends OpStore implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to pop + */ + public Astore(Avar var) + { + super(ASTORE, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Astore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Athrow.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Athrow.java new file mode 100644 index 0000000000000..7c5c5fe23d3c7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Athrow.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ATHROW simple op throws the exception which is located on the stack. +*

+* JASM op         :  ATHROW  (0xbf)
+* JVM byte code(s):  ATHROW  (0xbf)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Athrow extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Athrow() + { + super(ATHROW); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Athrow"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Attribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Attribute.java new file mode 100644 index 0000000000000..b3144fb8a89ad --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Attribute.java @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Arrays; + + +/** +* Represents a Java Virtual Machine Attribute structure as defined by the +* Java Virtual Machine (JVM) Specification. +* +*
+*   attribute_info
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u1 info[attribute_length];
+*       }
+* 
+* +* @version 0.50, 05/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Attribute extends VMStructure implements Constants, Comparable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct with a name. + * + * @param context the JVM structure containing the attribute + * @param sAttr the attribute name. + */ + protected Attribute(VMStructure context, String sAttr) + { + this(context, new UtfConstant(sAttr)); + } + + /** + * Construct with a name. + * + * @param context the JVM structure containing the attribute + * @param utf the constant containing the attribute name. + */ + protected Attribute(VMStructure context, UtfConstant utf) + { + m_utf = utf; + m_ab = NO_BYTES; + m_abOrig = NO_BYTES; + m_context = context; + } + + + // ----- Attribute operations ------------------------------------------- + + /** + * Based on the name of the attribute, which is encountered first in the + * stream, construct the correct attribute class and disassemble it. + * + * @param context the VM structure which contains this attribute; this + * is needed since different attributes are valid only + * within certain contexts + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected static Attribute loadAttribute(VMStructure context, DataInput stream, ConstantPool pool) + throws IOException + { + UtfConstant utf = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + String sAttr = utf.getValue(); + Attribute attr = null; + + // determine if the attribute is known + if (context instanceof Method) + { + if (sAttr.equals(ATTR_CODE)) + { + attr = new CodeAttribute(context); + } + else if (sAttr.equals(ATTR_EXCEPTIONS)) + { + attr = new ExceptionsAttribute(context); + } + else if (sAttr.equals(ATTR_DEPRECATED)) + { + attr = new DeprecatedAttribute(context); + } + else if (sAttr.equals(ATTR_SYNTHETIC)) + { + attr = new SyntheticAttribute(context); + } + else if (sAttr.equals(ATTR_RTVISANNOT)) + { + attr = new RuntimeVisibleAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_RTINVISANNOT)) + { + attr = new RuntimeInvisibleAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_RTVISPARAMANNOT)) + { + attr = new RuntimeVisibleParameterAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_RTINVISPARAMANNOT)) + { + attr = new RuntimeInvisibleParameterAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_ANNOTDEFAULT)) + { + attr = new AnnotationDefaultAttribute(context); + } + else if (sAttr.equals(ATTR_SIGNATURE)) + { + attr = new SignatureAttribute(context); + } + else if (sAttr.equals(ATTR_RTVISTANNOT)) + { + attr = new RuntimeVisibleTypeAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_RTINVISTANNOT)) + { + attr = new RuntimeInvisibleTypeAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_METHODPARAMS)) + { + attr = new MethodParametersAttribute(context); + } + } + else if (context instanceof CodeAttribute) + { + if (sAttr.equals(ATTR_LINENUMBERS)) + { + attr = new LineNumberTableAttribute(context); + } + else if (sAttr.equals(ATTR_VARIABLES)) + { + attr = new LocalVariableTableAttribute(context); + } + else if (sAttr.equals(ATTR_VARIABLETYPES)) + { + attr = new LocalVariableTypeTableAttribute(context); + } + else if (sAttr.equals(ATTR_STACKMAPTABLE)) + { + attr = new StackMapTableAttribute(context); + } + else if (sAttr.equals(ATTR_RTVISTANNOT)) + { + attr = new RuntimeVisibleTypeAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_RTINVISTANNOT)) + { + attr = new RuntimeInvisibleTypeAnnotationsAttribute(context); + } + } + else if (context instanceof Field) + { + if (sAttr.equals(ATTR_CONSTANT)) + { + attr = new ConstantValueAttribute(context); + } + else if (sAttr.equals(ATTR_DEPRECATED)) + { + attr = new DeprecatedAttribute(context); + } + else if (sAttr.equals(ATTR_SYNTHETIC)) + { + attr = new SyntheticAttribute(context); + } + else if (sAttr.equals(ATTR_RTVISANNOT)) + { + attr = new RuntimeVisibleAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_RTINVISANNOT)) + { + attr = new RuntimeInvisibleAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_SIGNATURE)) + { + attr = new SignatureAttribute(context); + } + else if (sAttr.equals(ATTR_RTVISTANNOT)) + { + attr = new RuntimeVisibleTypeAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_RTINVISTANNOT)) + { + attr = new RuntimeInvisibleTypeAnnotationsAttribute(context); + } + } + else if (context instanceof ClassFile) + { + if (sAttr.equals(ATTR_FILENAME)) + { + attr = new SourceFileAttribute(context); + } + else if (sAttr.equals(ATTR_DEPRECATED)) + { + attr = new DeprecatedAttribute(context); + } + else if (sAttr.equals(ATTR_SYNTHETIC)) + { + attr = new SyntheticAttribute(context); + } + else if (sAttr.equals(ATTR_INNERCLASSES)) + { + attr = new InnerClassesAttribute(context); + } + else if (sAttr.equals(ATTR_ENCMETHOD)) + { + attr = new EnclosingMethodAttribute(context); + } + else if (sAttr.equals(ATTR_RTVISANNOT)) + { + attr = new RuntimeVisibleAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_RTINVISANNOT)) + { + attr = new RuntimeInvisibleAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_BOOTSTRAPMETHODS)) + { + attr = new BootstrapMethodsAttribute(context); + } + else if (sAttr.equals(ATTR_RTVISTANNOT)) + { + attr = new RuntimeVisibleTypeAnnotationsAttribute(context); + } + else if (sAttr.equals(ATTR_RTINVISTANNOT)) + { + attr = new RuntimeInvisibleTypeAnnotationsAttribute(context); + } + } + + // unknown attribute + if (attr == null) + { + attr = new Attribute(context, utf); + pool.setOrderImportant(true); + } + + attr.disassemble(stream, pool); + return attr; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + *

+ * This method must be overridden or supplemented by sub-classes which + * do not maintain the attribute as binary. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + int cb = stream.readInt(); + byte[] ab = new byte[cb]; + stream.readFully(ab); + m_ab = ab; + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + *

+ * This method must be supplemented by sub-classes which reference any + * additional constants. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utf); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(m_utf)); + stream.writeInt(m_ab.length); + stream.write(m_ab); + } + + /** + * Determine the identity of the VM structure (if applicable). + * + * @return the string identity of the VM structure + */ + public String getIdentity() + { + return m_utf.getValue(); + } + + /** + * Determine if the VM structure (or any contained VM structure) has been + * modified by comparing the binary portion of the attribute. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + * + * @return true if the VM structure has been modified + */ + public boolean isModified() + { + return m_ab != m_abOrig && !Arrays.equals(m_ab, m_abOrig); + } + + /** + * Reset the modified state of the VM structure. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_abOrig = m_ab; + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + Attribute that = (Attribute) obj; + return this.m_utf.compareTo(that.m_utf); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return m_utf.getValue(); + } + + /** + * Compare this object to another object for equality. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + Attribute that = (Attribute) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_utf.equals(that.m_utf) + && Arrays.equals(this.getBytes(), that.getBytes()); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the context of the attribute. + * + * @return the JVM structure containing this attribute + */ + protected VMStructure getContext() + { + return m_context; + } + + /** + * Determine the attribute's name, which is immutable. + * + * @return the attribute name + */ + public String getName() + { + return m_utf.getValue(); + } + + /** + * Get the attribute's name constant. + * + * @return the attribute name constant + */ + public UtfConstant getNameConstant() + { + return m_utf; + } + + /** + * Get the binary portion of the attribute. If the binary is unavailable, + * for example, if a sub-class is unable to produce the binary without the + * constant pool, then null is returned. This typically would occur in an + * attribute that references constants, since the attribute would require + * the constant pool for building the binary. + * + * @return the binary portion of the attribute + */ + public byte[] getBytes() + { + return m_ab; + } + + /** + * Set the binary portion of the attribute. Pass null to clear the binary + * portion. + */ + public void setBytes(byte[] ab) + { + m_ab = (ab == null ? NO_BYTES : ab); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Attribute"; + + /** + * An empty attribute. + */ + private static final byte[] NO_BYTES = new byte[0]; + + /** + * The virtual machine context. + */ + private VMStructure m_context; + + /** + * Attribute name. + */ + private UtfConstant m_utf; + + /** + * Attribute contents. + */ + private byte[] m_ab; + + /** + * Original attribute contents. + */ + private byte[] m_abOrig; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Avar.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Avar.java new file mode 100644 index 0000000000000..a1b682c36ba46 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Avar.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The AVAR pseudo-op declares a reference variable. The variable can +* optionally be named. +*

+* JASM op         :  AVAR  (0xf0)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Avar extends OpDeclare implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Avar() + { + super(AVAR, null, null); + } + + /** + * Construct the op. + * + * @param sName the name of the variable + */ + public Avar(String sName) + { + super(AVAR, sName, null); + } + + /** + * Construct the op. + * + * @param sName the name of the variable + * @param sSig the signature of the reference type + * ('L' + class.replace('.', '/') + ';') + */ + public Avar(String sName, String sSig) + { + super(AVAR, sName, sSig); + } + + /** + * Construct the op. Used by disassembler. + * + * @param iVar the variable index + */ + protected Avar(int iVar) + { + super(AVAR, null, null, iVar); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Avar"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Baload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Baload.java new file mode 100644 index 0000000000000..e4373a1723521 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Baload.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The BALOAD simple op pushes a byte or boolean array element. +*

+* JASM op         :  BALOAD (0x33)
+* JVM byte code(s):  BALOAD (0x33)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Baload extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Baload() + { + super(BALOAD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Baload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Bastore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Bastore.java new file mode 100644 index 0000000000000..a7b44c3d25924 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Bastore.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The BASTORE simple op stores a byte or boolean array element. +*

+* JASM op         :  BASTORE (0x54)
+* JVM byte code(s):  BASTORE (0x54)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Bastore extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Bastore() + { + super(BASTORE); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Bastore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Begin.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Begin.java new file mode 100644 index 0000000000000..4dd2dbe96899d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Begin.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Vector; +import java.util.Enumeration; +import java.util.HashSet; + + +/** +* The BEGIN pseudo op opens a variable scope. +*

+* JASM op         :  BEGIN (0xea)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/17/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Begin extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Begin() + { + super(BEGIN); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Determine if the op is discardable. Begin and End ops are never + * considered discardable since they may come before labels and after + * returns/unconditional branches and since they do not affect execution. + * + * @return false always + */ + protected boolean isDiscardable() + { + return false; + } + + + // ----- accessors ------------------------------------------------------ + + // ----- end of scope + + /** + * Get the End for this Begin. + * + * @return the End op for this scope + */ + protected End getEnd() + { + return m_end; + } + + /** + * Set the End for this Begin. + * + * @param end the end of this scope + */ + protected void setEnd(End end) + { + m_end = end; + } + + + // ----- nested scope + + /** + * Get the begin that this begin is within. + * + * @return the Begin op of the outer scope + */ + protected Begin getOuterScope() + { + return m_outer; + } + + /** + * Set the begin that this begin is within. + * + * @param begin the start of the outer scope + */ + protected void setOuterScope(Begin begin) + { + m_outer = begin; + } + + + // ----- variable declarations + + /** + * Add a variable to this scope. + * + * @param var the variable being declared + */ + protected void addDeclaration(OpDeclare var) + { + m_vectVar.addElement(var); + } + + /** + * Enumerate all variables declared within this immediate scope. + * + * @return an enumeration of this scope's variables + */ + protected Enumeration getDeclarations() + { + return m_vectVar.elements(); + } + + + // ----- variable "pool" + + /** + * Get the variable pool state that existed when this scope was entered. + * + * @return the variable pool + */ + protected int[] getVariablePool() + { + return m_aiVar; + } + + /** + * Set the variable pool state as it exists when this scope is entered. + * + * @param aiVar the variable pool + */ + protected void setVariablePool(int[] aiVar) + { + m_aiVar = aiVar; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Begin"; + + /** + * The End for this Begin. + */ + private End m_end; + + /** + * The Begin from this Begin's outer scope. + */ + private Begin m_outer; + + /** + * All variables declared within this immediate scope. + */ + private Vector m_vectVar = new Vector(); + + /** + * The scope holds on to the entering state of the variable pool for the + * assembler. + */ + private int[] m_aiVar; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Bnewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Bnewarray.java new file mode 100644 index 0000000000000..7abb4f91b4055 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Bnewarray.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The BNEWARRAY pseudo-op instantiates an array of byte. +*

+* JASM op         :  BNEWARRAY    (0xf4)
+* JVM byte code(s):  NEWARRAY     (0xbc)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Bnewarray extends OpArray implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Bnewarray() + { + super(BNEWARRAY); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Bnewarray"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/BootstrapMethodConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/BootstrapMethodConstant.java new file mode 100644 index 0000000000000..f98ac9d73d5ae --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/BootstrapMethodConstant.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.dev.assembler; + +/** +* BootstrapMethodConstant is a convenience subclass of {@link MethodConstant} +* allowing the omission of the well known bootstrap method signature. +*

+* Bootstrap methods are permitted to have wider typed arguments or even be a +* variadic method however BootstrapMethodConstant assumes narrower types. +* +* @author hr 2012.08.06 +*/ +public class BootstrapMethodConstant + extends MethodConstant + { + + // ----- constructors --------------------------------------------------- + + /** + * Constructs a MethodConstant with the common type required by an + * invokedynamic bootstrap method. + * + * @param sClass + * @param sName + */ + public BootstrapMethodConstant(String sClass, String sName) + { + super(sClass, sName, "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/BootstrapMethodsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/BootstrapMethodsAttribute.java new file mode 100644 index 0000000000000..54120ee25e836 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/BootstrapMethodsAttribute.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.dev.assembler; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import java.util.LinkedList; +import java.util.List; + +/** +* Represents a Java Virtual Machine "BootstrapMethods" ClassFile attribute. +* +*

+* The BootstrapMethods Attribute was defined by JDK 1.7 under bytecode +* version 51.0. The structure is defined as: +*

+*

+*   BootstrapMethods_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u2 num_bootstrap_methods;
+*       bootstrap_method entries[num_bootstrap_methods];
+*       }
+* 
+* +* @author hr 2012.08.06 +*/ +public class BootstrapMethodsAttribute + extends Attribute + implements Constants + { + + // ----- constructors --------------------------------------------------- + + /** + * Construct a BootstrapMethodsAttribute under the provided context. + * + * @param context a related VMStructure object + */ + protected BootstrapMethodsAttribute(VMStructure context) + { + super(context, ATTR_BOOTSTRAPMETHODS); + } + + // ----- Attribute methods ---------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); // attribute_length + + List listMethods = m_listMethods = new LinkedList(); + for (int i = 0, cMethods = stream.readUnsignedShort(); i < cMethods; ++i) + { + BootstrapMethod method = new BootstrapMethod(); + method.disassemble(stream, pool); + listMethods.add(method); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void preassemble(ConstantPool pool) + { + super.preassemble(pool); + + for (BootstrapMethod method : m_listMethods) + { + method.preassemble(pool); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + + List listMethods = m_listMethods; + int cLength = 2; + for (BootstrapMethod method : listMethods) + { + cLength += method.size(); + } + stream.writeInt(cLength); + stream.writeShort(listMethods.size()); + for (BootstrapMethod method : listMethods) + { + method.assemble(stream, pool); + } + } + + // ----- accessors ------------------------------------------------------ + + /** + * Returns a mutable list of {@link BootstrapMethod}s. + * + * @return returns a mutable list of {@link BootstrapMethod}s + */ + public List getBootstrapMethods() + { + return m_listMethods; + } + + /** + * Adds a new {@link BootstrapMethod} to this BootstrapMethodsAttribute + * with the provided {@link MethodHandleConstant}. + * + * @param methHandleConstant the MethodHandleConstant to be used by the + * new BootstrapMethod + * + * @return the newly added BootstrapMethod + */ + public BootstrapMethod addBootstrapMethod(MethodHandleConstant methHandleConstant) + { + BootstrapMethod method = new BootstrapMethod(methHandleConstant); + m_listMethods.add(method); + return method; + } + + // ----- inner class: BootstrapMethod ----------------------------------- + + /** + * Represents a Java Virtual Machine bootstrap_method structure within + * the "BootstrapMethods" attribute. + *

+ * The bootstrap_method structure was defined by JDK 1.7 under bytecode + * version 51.0. The structure is defined as: + *

+ *

+    * bootstrap_method
+    *     {
+    *     u2 bootstrap_method_ref;
+    *     u2 num_bootstrap_arguments;
+    *     u2 bootstrap_arguments[num_bootstrap_arguments];
+    *     }
+    * 
+ */ + public class BootstrapMethod + extends VMStructure + implements Constants + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a BootstrapMethod. + */ + protected BootstrapMethod() + { + } + + /** + * Construct a BootstrapMethod with the provided + * {@link MethodHandleConstant}. + * + * @param methHandleConstant the MethodHandleConstant this + * BootstrapMethod is bound to + */ + public BootstrapMethod(MethodHandleConstant methHandleConstant) + { + this(methHandleConstant, null); + } + + /** + * Construct a BootstrapMethod with the provided + * {@link MethodHandleConstant} and a list of arguments. + * + * @param methHandleConstant the MethodHandleConstant this + * BootstrapMethod is bound to + * @param listArgs list of arguments accepted by the + * CallSite returned as a part of the + * invokedynamic linking protocol + */ + public BootstrapMethod(MethodHandleConstant methHandleConstant, List listArgs) + { + m_method = methHandleConstant; + m_listArgs = listArgs == null ? m_listArgs : listArgs; + } + + // ----- VMStructure methods ---------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_method = (MethodHandleConstant) pool.getConstant(stream.readUnsignedShort()); + + List listArgs = m_listArgs; + for (int i = 0, cArgs = stream.readUnsignedShort(); i < cArgs; ++i) + { + listArgs.add(pool.getConstant(stream.readUnsignedShort())); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_method); + for (Constant arg : m_listArgs) + { + pool.registerConstant(arg); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + int iCP = pool.findConstant(m_method); + stream.writeShort(iCP); + + List listArgs = m_listArgs; + stream.writeShort(listArgs.size()); + for (Constant arg : listArgs) + { + stream.writeShort(pool.findConstant(arg)); + } + } + + // ----- accessors -------------------------------------------------- + + /** + * Returns the {@link MethodHandleConstant} this BootstrapMethod is + * bound to. + * + * @return the MethodHandleConstant this BootstrapMethod is + * bound to + */ + public MethodHandleConstant getBootstrapMethod() + { + return m_method; + } + + /** + * Sets the {@link MethodHandleConstant} this BootstrapMethod is + * bound to. + * + * @param method the MethodHandleConstant this BootstrapMethod is + * bound to + */ + public void setBootstrapMethod(MethodHandleConstant method) + { + int nRefKind = method.getKind(); + if (nRefKind != MethodHandleConstant.KIND_REF_INVOKESTATIC && + nRefKind != MethodHandleConstant.KIND_REF_NEWINVOKESPECIAL) + { + throw new IllegalArgumentException("Bootstrap method should be either " + + "invokeStatic or newInvokeSpecial"); + } + m_method = method; + } + + /** + * Returns a mutable list of arguments accepted by the CallSite + * returned as a part of the invokedynamic linking protocol. + * + * @return a mutable list of arguments + */ + public List getArguments() + { + return m_listArgs; + } + + /** + * Returns the byte size of this BootstrapMethod. + * + * @return the byte size of this BootstrapMethod + */ + public int size() + { + return 4 + 2 * m_listArgs.size(); + } + + // ----- data members ----------------------------------------------- + + /** + * The MethodHandleConstant bound to this BootstrapMethod. + */ + private MethodHandleConstant m_method; + + /** + * List of arguments accepted by the CallSite returned as a part of + * the invokedynamic linking protocol. + */ + private List m_listArgs = new LinkedList(); + } + + // ----- data members --------------------------------------------------- + + /** + * List of BootstrapMethod's held by this BootstrapMethods ClassFile + * attribute. + */ + private List m_listMethods = new LinkedList(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Caload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Caload.java new file mode 100644 index 0000000000000..f3fd4e079ad7f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Caload.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The CALOAD simple op pushes a char element from an array onto the +* stack. +*

+* JASM op         :  CALOAD (0x34)
+* JVM byte code(s):  CALOAD (0x34)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Caload extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Caload() + { + super(CALOAD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Caload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Case.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Case.java new file mode 100644 index 0000000000000..5714d31ed22fc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Case.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The CASE op implements a particular switch case. The CASE op is part of a +* SWITCH, TABLESWITCH, or LOOKUPSWITCH op; although it is implemented as a +* separate op, it does not produce any code on its own; that is the +* responsibility of the various switch ops. +*

+* JASM op         :  CASE    (0xfd)
+* JVM byte code(s):  n/a
+* Details         :  Must immediately follow one of the SWITCH variants.
+* 
+* +* @version 0.50, 06/16/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Case extends OpBranch implements Constants, Comparable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param iCase the integer case value + * @param label the label to branch to + */ + public Case(int iCase, Label label) + { + super(CASE, label); + m_iCase = iCase; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + String sName = getName(); + String sCase = String.valueOf(m_iCase); + String sLabel = getLabel().format(); + return format(null, sName + ' ' + sCase + ' ' + sLabel, null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + String sName = getName(); + String sCase = String.valueOf(m_iCase); + String sLabel = String.valueOf(getLabel().getOffset()); + return sName + ' ' + sCase + ": goto " + sLabel; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + setSize(0); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + Case that = (Case) obj; + + int nThis = this.m_iCase; + int nThat = that.m_iCase; + + return (nThis < nThat ? -1 : (nThis > nThat ? +1 : 0)); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Access the integer case value. + * + * @return the integer value of this case + */ + public int getCase() + { + return m_iCase; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Case"; + + /** + * The integer value of this case. + */ + private int m_iCase; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Castore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Castore.java new file mode 100644 index 0000000000000..1129d51060561 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Castore.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The CASTORE simple op stores a char array element. +*

+* JASM op         :  CASTORE (0x55)
+* JVM byte code(s):  CASTORE (0x55)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Castore extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Castore() + { + super(CASTORE); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Castore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Catch.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Catch.java new file mode 100644 index 0000000000000..2d0644711a49f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Catch.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The CATCH pseudo-op closes a guarded section, specifies an exception to +* guard against, and specifies an exception handler. +*

+* JASM op         :  CATCH      (0xff)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/19/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Catch extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param opTry the Try op that started the guarded section that this op + * is closing + * @param clz a class constant to guard against or null to catch all + * @param label the label for the exception handler + */ + public Catch(Try opTry, ClassConstant clz, Label label) + { + super(CATCH); + + if (opTry == null || label == null) + { + throw new IllegalArgumentException(CLASS + + ": The Try op and exception handler label arguments are required!"); + } + + m_opTry = opTry; + m_clz = clz; + m_label = label; + + opTry.addCatch(this); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + String sName = getName(); + String sExcept = (m_clz == null ? "*" : m_clz.format()); + String sLabel = m_label.format(); + return format(null, sName + ' ' + sExcept + ' ' + sLabel, null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + String sName = getName(); + String sExcept = (m_clz == null ? "*" : m_clz.format()); + String sLabel = String.valueOf(m_label.getOffset()); + return sName + ' ' + sExcept + " goto " + sLabel; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_clz); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Determine if the op is reachable; this is only valid after calculating + * the max stack. + * + * @return true if the op was reached by the stack size calculating + * algorithm + */ + protected boolean isReachable() + { + // if try is reachable then keep the catch + return m_opTry.isReachable(); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the Try op corresponding to this op. + * + * @return the Try op + */ + public Try getTry() + { + return m_opTry; + } + + /** + * Get the ClassConstant that this op catches. + * + * @return the exception class guarded against or null if all + */ + public ClassConstant getExceptionClass() + { + return m_clz; + } + + /** + * Get the label for the exception handler. + * + * @return the label for the exception handler + */ + public Label getLabel() + { + return m_label; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Catch"; + + /** + * The Try op corresponding to this Catch op. + */ + private Try m_opTry; + + /** + * The class constant guarded against by this Catch op. + */ + private ClassConstant m_clz; + + /** + * The label of the exception handler. + */ + private Label m_label; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Checkcast.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Checkcast.java new file mode 100644 index 0000000000000..bff6bc7b5b03d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Checkcast.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The CHECKCAST op compares the class of the reference on the stack to the +* specified ClassConstant. +*

+* JASM op         :  CHECKCAST    (0xc0)
+* JVM byte code(s):  CHECKCAST    (0xc0)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Checkcast extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the ClassConstant to cast to + */ + public Checkcast(ClassConstant constant) + { + super(CHECKCAST, constant); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Checkcast"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ClassConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ClassConstant.java new file mode 100644 index 0000000000000..0d55962ef2c4a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ClassConstant.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents the name of a Java Virtual Machine class. This constant type +* references a UtfConstant. +* +* @version 0.50, 05/13/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class ClassConstant extends Constant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected ClassConstant() + { + super(CONSTANT_CLASS); + } + + /** + * Construct a constant whose value is a java string containing the name + * of a Java class. This constructor is a helper which creates a UTF + * constant from the passed string. + * + * @param sClass the class name + */ + public ClassConstant(String sClass) + { + this(new UtfConstant(sClass.replace('.', '/'))); + } + + /** + * Construct a Class constant which references the passed UTF constant. + * + * @param constant the referenced UTF constant which contains the value + * of the Class constant + */ + public ClassConstant(UtfConstant constant) + { + this(); + + if (constant == null) + { + throw new IllegalArgumentException(CLASS + ": Value cannot be null!"); + } + + m_utf = constant; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iRef = stream.readUnsignedShort(); + } + + /** + * Resolve referenced constants. + * + * @param pool the constant pool containing any constant referenced by + * this constant (i.e. referenced by index) + */ + protected void postdisassemble(ConstantPool pool) + { + UtfConstant constUtf = m_utf; + if (constUtf == null) + { + constUtf = m_utf = (UtfConstant) pool.getConstant(m_iRef); + + constUtf.postdisassemble(pool); + } + } + + /** + * Register referenced constants. + * + * @param pool the constant pool to register referenced constants with + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utf); + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeShort(pool.findConstant(m_utf)); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + ClassConstant that = (ClassConstant) obj; + return this.m_utf.compareTo(that.m_utf); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Class)->" + (m_utf == null ? "[" + m_iRef + ']' : m_utf.toString()); + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + String sClass = m_utf.format(); + return sClass.replace('/', '.'); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + ClassConstant that = (ClassConstant) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_utf.equals(that.m_utf); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the class name as it appears in Java source. + * + * @return the class name + */ + public String getJavaName() + { + return m_utf.getValue().replace('/', '.'); + } + + /** + * Get the string value (class name) of the constant. + * + * @return the class name + */ + public String getValue() + { + return m_utf.getValue(); + } + + /** + * Get the UTF constant which holds the class name. + * + * @return the UTF constant referenced from this constant + */ + public UtfConstant getValueConstant() + { + return m_utf; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "ClassConstant"; + + /** + * The UTF constant referenced by this Class constant. + */ + private UtfConstant m_utf; + + /** + * If this has been disassembled (previous to the "postdisassemble" + * invocation), the reference to the UTF constant is still by index, as + * it was in the persistent .class structure. + */ + private int m_iRef; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ClassFile.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ClassFile.java new file mode 100644 index 0000000000000..9b04daae58180 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ClassFile.java @@ -0,0 +1,1907 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.EOFException; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +import java.util.Arrays; +import java.util.Enumeration; + +import com.tangosol.io.ByteArrayReadBuffer; +import com.tangosol.io.ReadBuffer; + +import com.tangosol.util.StringTable; + + +/** +* Represents a Java .class structure (the JVM Class file format). For +* reference purposes, download the JVM specification from +* http://java.sun.com/docs/index.html. +*

+* Description: +*

+* The .class structure is hierachical in nature with a single internally +* shared resource, the constant pool. In other words, with the exception +* of the constant pool, all portions of the .class structure only refer +* to themselves or structures which are contained within themselves. The +* hierarchy is as follows: +*

+*

+*   1) ClassFile
+*       1)  header
+*           1)  magic cookie
+*           2)  expected JVM version
+*       2)  Constant[] (the ConstantPool)
+*       3)  class information
+*           1)  AccessFlags
+*           2)  this class/interface identity
+*           3)  super class/interface identity
+*           4)  implemented interface identity[]
+*       4)  Field[]
+*           1)  AccessFlags
+*           2)  field identity
+*           3)  field data type
+*           4)  Attribute[]
+*               1)  JVM supports a single "ConstantValue" attribute
+*       5)  Method[]
+*           1)  AccessFlags
+*           2)  method identity
+*           3)  method parameter/return signature
+*           4)  Attribute[]
+*               1)  JVM supports a single "Code" attribute
+*                   1)  frame stack area size
+*                   2)  frame local variable area size
+*                   3)  Op[]
+*                   4)  TryCatch[]
+*                       1)  Op "try" range [pc,pc)
+*                       2)  Handler pc
+*                       3)  catchable class identity
+*                   5)  Attribute[] (yes, the "Code" attribute has attributes!)
+*                       1)  JVM supports a single "LineNumberTable" attribute
+*                           - cross-references pc and source line number
+*                             values
+*                       2)  JVM supports a single "LocalVariableTable" attribute
+*                           - cross-references local variable index and local
+*                             variable name/data type values
+*                       3)  JVM supports a single "LocalVariableTypeTable" attribute
+*                           - cross-references local variable index and local
+*                             variable name/data signature values
+*               2)  JVM supports a single "Exceptions" attribute
+*                   1)  declared exception identity[]
+*       6)  Attribute[]
+*           1)  JVM specification defines (but JVM does not support) a single
+*               "SourceFile" attribute
+*               - specifies the OS source file name (without any path)
+* 
+*

+* WARNING!!! Other attributes have been introduced since the specification +* was last updated. These attributes are similar in structure and naming +* conventions to the current attributes and appear to all be related to the +* JDK1.1 "inner class" feature. +*

+* Note: Other attributes than those defined above are legal but ignored +* by both the JVM and any other process examining/manipulating/using a +* .class structure. Custom-defined attributes must follow the package +* and class naming conventions, e.g. "com.tangosol.examples.BreakPointTable". +*

+* Requirements: +*

+*

+*   1)  The role of the class is to support the contruction, reflection,
+*       modification, and transport (i.e. transient storage) of Java
+*       ClassFile (aka ".class file") structures
+*
+*       1)  An assembler will construct ClassFile structures
+*
+*       2)  The Component Definition will create JCS structures and a
+*           disassembler will display detailed information using the
+*           reflection information provided by ClassFile structures
+*
+*       3)  A deployment tool or the class loader will patch up package
+*           names by modifying the ClassFile structure
+*
+*       4)  ClassFile structures provide a type-safe reference type for
+*           compiled classes (typically used for passing a compiled class
+*           as a parameter or as the type of a return value)
+*
+*   2)  Efficiency, especially when the ClassFile structure is used only
+*       for transport, means that the disassembly of .class structures and
+*       the final assembly and linking of accessor-supplied information must
+*       be deferred until necessary
+*
+*   3)  Production of dependency and relationship information which would
+*       enable partial compilation, selective re-compilation, BOM/where-used
+*       and impact analysis
+*
+*   4)  Support for partial, incremental, and method (re-)compilation
+* 
+*

+* WARNING!!! This implementation is not intended to be thread-safe. Do +* not access a single instance of this class (or any of the other classes +* in this package) from more than one thread unless synchronization is +* handled by the caller. +*

+* WARNING!!! This implementation is not intended to be idiot-safe. It is +* the responsibility of the caller to provide legal ClassFile information, +* and not the responsibility of this implementation to detect it. +* +* @version 0.10, 02/05/98, prototype dis-assembler +* @version 0.50, 05/07/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class ClassFile extends VMStructure implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Construct a new ClassFile object. + * + * @param sThis fully qualified name of this class + * @param sSuper name of the super class, or null if there is no + * super class (e.g. java.lang.Object or an interface) + * @param fInterface true means that this is an interface + */ + public ClassFile(String sThis, String sSuper, boolean fInterface) + { + init(); + setLoaded(true); + setName(sThis); + setSuper(sSuper); + setInterface(fInterface); + setSynchronized(true); + } + + /** + * Constructs a ClassFile from a .class structure stored in a byte array. + * + * This constructor is preferred to the DataInput-based constructor. + * + * @param abClazz the byte array containing the .class structure + */ + public ClassFile(byte[] abClazz) + { + setBytes(abClazz); + } + + /** + * Constructs a ClassFile from a stream containing a .class structure. + * + * @param stream the java.io.DataInput stream containing the .class + * structure + */ + public ClassFile(DataInput stream) + throws IOException + { + load(stream); + } + + + // ----- persistence ---------------------------------------------------- + + /** + * Read the .class structure out of the passed stream. + * + * @param stream the object implementing java.io.DataInput and containing + * the .class structure + * + * @throws java.io.IOException if an error (besides EOF) occurs reading + * from the stream + */ + public void load(DataInput stream) + throws IOException + { + final int MAX = 8192; + byte[] abBuf = new byte[MAX]; + ByteArrayOutputStream streamRaw = new ByteArrayOutputStream(MAX); + + int of = 0; + try + { + while (true) + { + byte b = stream.readByte(); + + abBuf[of++] = b; + if (of == MAX) + { + streamRaw.write(abBuf, 0, MAX); + of = 0; + } + } + } + catch (EOFException e) + { + if (of > 0) + { + streamRaw.write(abBuf, 0, of); + } + } + + + setBytes(streamRaw.toByteArray()); + } + + /** + * Write the .class structure into a stream. + * + * @param stream the object implementing java.io.DataOutput to which + * the .class structure will be written + * + * @throws java.io.IOException if an error occurs writing the stream + */ + public void save(DataOutput stream) + throws IOException + { + stream.write(getBytes()); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + *

+ * The structure is defined by the Java Virtual Machine Specification as: + *

+    *   ClassFile
+    *       {
+    *       u4 magic;
+    *       u2 minor_version;
+    *       u2 major_version;
+    *       u2 constant_pool_count;
+    *       cp_info constant_pool[constant_pool_count-1];
+    *       u2 access_flags;
+    *       u2 this_class;
+    *       u2 super_class;
+    *       u2 interfaces_count;
+    *       u2 interfaces[interfaces_count];
+    *       u2 fields_count;
+    *       field_info fields[fields_count];
+    *       u2 methods_count;
+    *       method_info methods[methods_count];
+    *       u2 attributes_count;
+    *       attribute_info attributes[attributes_count];
+    *       }
+    * 
+ * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + // check header + int nCookie = stream.readInt(); + if (nCookie != CLASS_COOKIE) + { + throw new IOException(CLASS + ".disassemble: Class cookie not found!"); + } + + m_nVersionMinor = stream.readUnsignedShort(); + m_nVersionMajor = stream.readUnsignedShort(); + + if ( m_nVersionMajor < VERSION_MAJOR_MIN + || (m_nVersionMajor == VERSION_MAJOR_MIN && + m_nVersionMinor < VERSION_MINOR_MIN) + || m_nVersionMajor > VERSION_MAJOR_MAX + || (m_nVersionMajor == VERSION_MAJOR_MAX && + m_nVersionMinor > VERSION_MINOR_MAX)) + { + throw new IOException(CLASS + ".disassemble: Version (" + m_nVersionMajor + '.' + m_nVersionMinor+ ") not supported!"); + } + + // constant pool + pool.disassemble(stream, pool); + m_pool = pool; + + // access flags + AccessFlags flags = m_flags; + flags.disassemble(stream, pool); + + // identity (this/super) + m_clzName = (ClassConstant) pool.getConstant(stream.readUnsignedShort()); + m_clzSuper = (ClassConstant) pool.getConstant(stream.readUnsignedShort()); + + // interfaces + m_tblInterface.clear(); + int c = stream.readUnsignedShort(); + for (int i = 0; i < c; ++i) + { + ClassConstant clz = (ClassConstant) pool.getConstant(stream.readUnsignedShort()); + m_tblInterface.put(clz.getValue(), clz); + } + + // fields + m_tblField.clear(); + c = stream.readUnsignedShort(); + for (int i = 0; i < c; ++i) + { + Field field = new Field(); + field.disassemble(stream, pool); + m_tblField.put(field.getIdentity(), field); + } + + // methods + m_tblMethod.clear(); + c = stream.readUnsignedShort(); + String sClass = m_clzName.getValue(); + for (int i = 0; i < c; ++i) + { + Method method = new Method(sClass, flags.isInterface()); + method.disassemble(stream, pool); + m_tblMethod.put(method.getIdentity(), method); + } + + // attributes + m_tblAttribute.clear(); + c = stream.readUnsignedShort(); + for (int i = 0; i < c; ++i) + { + Attribute attribute = Attribute.loadAttribute(this, stream, pool); + m_tblAttribute.put(attribute.getIdentity(), attribute); + } + + resetModified(); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + // register constants used by the class + pool.registerConstant(m_clzName); + if (m_clzSuper != null) + { + pool.registerConstant(m_clzSuper); + } + + Enumeration enmr = m_tblInterface.elements(); + while (enmr.hasMoreElements()) + { + pool.registerConstant((ClassConstant) enmr.nextElement()); + } + + // the constant pool doesn't have to register itself + // m_pool.preassemble(pool); + + // register constants used by the fields, methods, and attributes + StringTable[] atbl = CONTAINED_TABLE; + for (int i = 0; i < atbl.length; ++i) + { + StringTable tbl = atbl[i]; + enmr = tbl.elements(); + while (enmr.hasMoreElements()) + { + ((VMStructure) enmr.nextElement()).preassemble(pool); + } + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + *

+ * The structure is defined by the Java Virtual Machine Specification as: + *

+    *   ClassFile
+    *       {
+    *       u4 magic;
+    *       u2 minor_version;
+    *       u2 major_version;
+    *       u2 constant_pool_count;
+    *       cp_info constant_pool[constant_pool_count-1];
+    *       u2 access_flags;
+    *       u2 this_class;
+    *       u2 super_class;
+    *       u2 interfaces_count;
+    *       u2 interfaces[interfaces_count];
+    *       u2 fields_count;
+    *       field_info fields[fields_count];
+    *       u2 methods_count;
+    *       method_info methods[methods_count];
+    *       u2 attributes_count;
+    *       attribute_info attributes[attributes_count];
+    *       }
+    * 
+ * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + // header + int nVersionMajor = m_nVersionMajor; + + stream.writeInt(CLASS_COOKIE); + stream.writeShort(m_nVersionMinor); + stream.writeShort(m_nVersionMajor); + + // constant pool + m_pool.assemble(stream, pool); + + // access flags + if (nVersionMajor >= 52) + { + // as of 52.0 ACC_SUPER should always be set + m_flags.setSynchronized(true); + } + m_flags.assemble(stream, pool); + + // identity (this/super) + stream.writeShort(pool.findConstant(m_clzName)); + stream.writeShort(m_clzSuper == null ? 0 : pool.findConstant(m_clzSuper)); + + // interfaces + stream.writeShort(m_tblInterface.getSize()); + Enumeration enmr = m_tblInterface.elements(); + while (enmr.hasMoreElements()) + { + ClassConstant clz = (ClassConstant) enmr.nextElement(); + stream.writeShort(pool.findConstant(clz)); + } + + // fields, methods, attributes + StringTable[] atbl = CONTAINED_TABLE; + for (int i = 0; i < atbl.length; ++i) + { + StringTable tbl = atbl[i]; + stream.writeShort(tbl.getSize()); + enmr = tbl.elements(); + while (enmr.hasMoreElements()) + { + ((VMStructure) enmr.nextElement()).assemble(stream, pool); + } + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the ClassFile. + * + * @return a string describing the ClassFile + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + + boolean fInterface = isInterface(); + String sMod = m_flags.toString(fInterface ? ACC_INTERFACE : ACC_CLASS); + if (sMod.length() > 0) + { + sb.append(sMod) + .append(' '); + } + + sb.append(fInterface ? "interface " : "class ") + .append(m_clzName.getJavaName()); + + ClassConstant clzSuper = m_clzSuper; + if (clzSuper != null) + { + String sSuper = clzSuper.getValue(); + if (!sSuper.equals(DEFAULT_SUPER)) + { + sb.append(" extends ") + .append(clzSuper.getJavaName()); + } + } + + if (!m_tblInterface.isEmpty()) + { + sb.append(fInterface? " extends " : " implements "); + Enumeration enmr = m_tblInterface.elements(); + boolean fTrailing = false; + while (enmr.hasMoreElements()) + { + if (fTrailing) + { + sb.append(", "); + } + sb.append(((ClassConstant) enmr.nextElement()).getJavaName()); + fTrailing = true; + } + } + + return sb.toString(); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + if (obj instanceof ClassFile) + { + ClassFile that = (ClassFile) obj; + if (this == that) + { + return true; + } + + // equal iff .class structure is identical + byte[] abThis = this.getBytes(); + byte[] abThat = that.getBytes(); + return Arrays.equals(abThis, abThat); + } + return false; + } + + // ----- ClassFile operations ------------------------------------------- + + /** + * Internal initialization. + */ + protected void init() + { + m_fModified = false; + m_fLoaded = false; + m_abClazz = null; + m_clzName = null; + m_clzSuper = null; + m_pool = new ConstantPool(this); + m_flags = new AccessFlags(); + m_tblInterface = new StringTable(); + m_tblField = new StringTable(); + m_tblMethod = new StringTable(); + m_tblAttribute = new StringTable(); + + CONTAINED_TABLE[0] = m_tblField; + CONTAINED_TABLE[1] = m_tblMethod; + CONTAINED_TABLE[2] = m_tblAttribute; + } + + /** + * Using the constant pool resolution feature, change all references to + * the specified package. + * + * @param sPkg dot-delimited package name, not null + */ + public void relocate(String sPkg) + { + if (!sPkg.replace('.', '/').equals(Relocator.PACKAGE)) + { + resolve(new Relocator(sPkg)); + } + } + + /** + * Allow constants in the pool to be replaced by a callback. + * + * @param resolver the callback + */ + public void resolve(Resolver resolver) + { + // get the .class structure + byte[] ab = getBytes(); + + // wipe any disassembled information -- we are going to go directly + // after the constant pool portion of the binary .class structure + if (isLoaded()) + { + init(); + } + + try + { + ByteArrayReadBuffer in = new ByteArrayReadBuffer(ab); + ByteArrayOutputStream out = new ByteArrayOutputStream((int) (ab.length * 1.25)); + ReadBuffer.BufferInput streamIn = in.getBufferInput(); + DataOutput streamOut = new DataOutputStream(out); + + // the portion of the .class structure before the pool + int ofStart = 8; + streamIn.setOffset(ofStart); + out.write(ab, 0, ofStart); + + // the constant pool is in the middle (offset 8 bytes) of the .class + int cConst = streamIn.readUnsignedShort(); + streamOut.writeShort(cConst); + + for (int i = 1; i < cConst; ++i) + { + Constant constant = null; + int nTag = streamIn.readUnsignedByte(); + switch (nTag) + { + case CONSTANT_UTF8: + constant = new UtfConstant(); + break; + case CONSTANT_INTEGER: + constant = new IntConstant(); + break; + case CONSTANT_FLOAT: + constant = new FloatConstant(); + break; + case CONSTANT_LONG: + constant = new LongConstant(); + ++i; // uses two constant slots + break; + case CONSTANT_DOUBLE: + constant = new DoubleConstant(); + ++i; // uses two constant slots + break; + + case CONSTANT_CLASS: + case CONSTANT_STRING: + case CONSTANT_METHODTYPE: + streamOut.writeByte(nTag); + streamOut.writeShort(streamIn.readUnsignedShort()); + break; + + case CONSTANT_FIELDREF: + case CONSTANT_METHODREF: + case CONSTANT_INTERFACEMETHODREF: + case CONSTANT_NAMEANDTYPE: + case CONSTANT_INVOKEDYNAMIC: + streamOut.writeByte(nTag); + streamOut.writeShort(streamIn.readUnsignedShort()); + streamOut.writeShort(streamIn.readUnsignedShort()); + break; + + case CONSTANT_METHODHANDLE: + streamOut.writeByte(nTag); + streamOut.writeByte(streamIn.readUnsignedByte()); + streamOut.writeShort(streamIn.readUnsignedShort()); + break; + + default: + throw new IOException("Invalid constant tag " + nTag); + } + + if (constant != null) + { + constant.disassemble(streamIn, null); + constant = resolver.resolve(constant); + constant.assemble(streamOut, null); + } + } + + // the portion of the .class structure after the pool + int ofStop = streamIn.getOffset(); + out.write(ab, ofStop, ab.length - ofStop); + ab = out.toByteArray(); + } + catch (IOException e) + { + throw new RuntimeException("Illegal .class structure!\n" + e.toString()); + } + + // store the new .class structure + setBytes(ab); + } + + /** + * Write java like descriptions of the byte codes for this class file not + * including attributes for each info structure (class | field | method). + * + * @param out the {@link PrintWriter} to write the text to + */ + public void dump(PrintWriter out) + { + // class header + boolean fInterface = isInterface(); + String sMod = m_flags.toString(fInterface ? ACC_INTERFACE : ACC_CLASS); + if (sMod.length() > 0) + { + out.write(sMod); + out.write(' '); + } + + out.write(fInterface ? "interface " : "class "); + out.write(m_clzName.getJavaName()); + + ClassConstant clzSuper = m_clzSuper; + if (clzSuper != null) + { + String sSuper = clzSuper.getValue(); + if (!sSuper.equals(DEFAULT_SUPER)) + { + out.write(" extends "); + out.write(clzSuper.getJavaName()); + } + } + + if (!m_tblInterface.isEmpty()) + { + out.write(fInterface? " extends " : " implements "); + Enumeration enmr = m_tblInterface.elements(); + boolean fTrailing = false; + while (enmr.hasMoreElements()) + { + if (fTrailing) + { + out.write(", "); + } + out.write(((ClassConstant) enmr.nextElement()).getJavaName()); + fTrailing = true; + } + } + + String sIndent = " "; + + out.println(toString()); + out.println(sIndent + '{'); + + String sBreak = ""; + + // fields + Enumeration enmr = m_tblField.elements(); + while (enmr.hasMoreElements()) + { + Field field = (Field) enmr.nextElement(); + + out.print(sIndent + field.toString()); + + if (field.isConstant()) + { + out.println(" = " + field.getConstantValue() + ';'); + } + else + { + out.println(';'); + } + + sBreak = "\n"; + } + + out.print(sBreak); + + // methods + enmr = m_tblMethod.elements(); + while (enmr.hasMoreElements()) + { + //((Method) enmr.nextElement()).dump(out, sIndent); + out.print(sIndent); + out.println(enmr.nextElement()); + } + + out.println(sIndent + '}'); + } + + + // ----- accessor: classfile version ----------------------------------- + + /** + * Get the Classfile format major version number of this class. + * + * @return the major version number + */ + public int getMajorVersion() + { + return m_nVersionMajor; + } + + /** + * Set the Classfile format major version number of this class. + * + * @param nVersionMajor the major version number + */ + public void setMajorVersion(int nVersionMajor) + { + m_nVersionMajor = nVersionMajor; + setModified(true); + } + + /** + * Get the Classfile format minor version number of this class. + * + * @return the minor version number + */ + public int getMinorVersion() + { + return m_nVersionMinor; + } + + /** + * Set the Classfile format minor version number of this class. + * + * @param nVersionMinor the minor version number + */ + public void setMinorVersion(int nVersionMinor) + { + m_nVersionMinor = nVersionMinor; + setModified(true); + } + + // ----- accessor: loaded ---------------------------------------------- + + /** + * The loaded property refers to the .class structure stored internally + * as a byte array with respect to the information stored in the ClassFile + * object. If the ClassFile is constructed from a byte array or an input + * stream, the information is not expanded immediately in case the reason + * for the creation of the ClassFile object is as a type-safe mechanism + * for passing compiled .class structures. Before any operations can take + * place agains the ClassFile, the .class structure stored in the byte + * array must be expanded. + * + * @return false if the .class structure must be expanded before + * queries/modifications against the ClassFile can commence + * + * @see #ensureLoaded() + */ + protected boolean isLoaded() + { + return m_fLoaded; + } + + /** + * Set or reset the loaded flag on the ClassFile. + * + * @param fLoaded true to set the loaded flag, false to reset it + */ + protected void setLoaded(boolean fLoaded) + { + m_fLoaded = fLoaded; + } + + /** + * Ensure that the ClassFile object is ready to be queried/modified. + */ + protected void ensureLoaded() + { + if (!m_fLoaded) + { + if (m_abClazz != null) + { + ByteArrayInputStream streamRaw = new ByteArrayInputStream(m_abClazz); + DataInput stream = new DataInputStream(streamRaw); + try + { + disassemble(stream, m_pool); + } + catch (IOException e) + { + throw ensureRuntimeException(e); + } + } + + m_fLoaded = true; + } + } + + + // ----- accessor: modified -------------------------------------------- + + /** + * The modified property refers to the information stored in the ClassFile + * object with respect to the potentially cached .class structure stored + * internally as a byte array. If a modification has occurred to the + * ClassFile information such that the .class structure (the byte array) + * must be re-built, the ClassFile is considered to be modified. When the + * .class structure is re-built, the modified flag is reset. + * + * @return true if the ClassFile information has been modified + */ + public boolean isModified() + { + // a class is only modifiable after it is loaded + if (!m_fLoaded) + { + return m_fModified; + } + + if (m_fModified || m_abClazz == null || m_pool.isModified()) + { + return true; + } + + // check all other VM sub-structures + StringTable[] atbl = CONTAINED_TABLE; + for (int i = 0; i < atbl.length; ++i) + { + Enumeration enmr = atbl[i].elements(); + while (enmr.hasMoreElements()) + { + if (((VMStructure) enmr.nextElement()).isModified()) + { + return true; + } + } + } + + return false; + } + + /** + * Set or reset the modified flag on the ClassFile. + * + * @param fModified true to set the modified flag, false to reset it + */ + public void setModified(boolean fModified) + { + if (fModified) + { + m_fModified = fModified; + } + else + { + resetModified(); + } + } + + /** + * Reset the modified flag on the ClassFile. + */ + protected void resetModified() + { + m_pool.resetModified(); + + // reset all other VM sub-structures + StringTable[] atbl = CONTAINED_TABLE; + for (int i = 0; i < atbl.length; ++i) + { + Enumeration enmr = atbl[i].elements(); + while (enmr.hasMoreElements()) + { + ((VMStructure) enmr.nextElement()).resetModified(); + } + } + + m_fModified = false; + } + + + // ----- accessor: .class structure ------------------------------------ + + /** + * Access the .class structure as a byte array. Do not modify the return + * value from this accessor method. + * + * @return the .class structure in the form of a byte array + */ + public byte[] getBytes() + { + if (isModified()) + { + // collect all constants + ConstantPool pool = m_pool; + boolean fOptimize = !pool.isOrderImportant(); + if (fOptimize) + { + if (pool != null) + { + // detach the previous pool from the ClassFile + pool.setClassFile(null); + } + pool = new ConstantPool(this); + } + preassemble(pool); + if (fOptimize) + { + pool.sort(); + } + m_pool = pool; + + // assemble the class into bytes + ByteArrayOutputStream streamRaw = new ByteArrayOutputStream(8192); + DataOutputStream stream = new DataOutputStream(streamRaw); + try + { + assemble(stream, pool); + } + catch (IOException e) + { + throw ensureRuntimeException(e); + } + m_abClazz = streamRaw.toByteArray(); + + resetModified(); + } + + return m_abClazz; + } + + /** + * Supply the class structure as a byte array. + * + * @param abClazz the .class structure in the form of a byte array + */ + public void setBytes(byte[] abClazz) + { + if (abClazz == null) + { + throw new IllegalArgumentException(CLASS + ".setBytes: byte array is required"); + } + + init(); + m_abClazz = abClazz; + setLoaded(false); + } + + + // ----- accessor: .class identity ------------------------------------- + + /** + * Determine the fully qualified name of this class. + * + * @return this class name + */ + public String getName() + { + ensureLoaded(); + return m_clzName.getValue(); + } + + /** + * Set the fully qualified name of this class. + * + * @param sName the new class name + */ + public void setName(String sName) + { + ensureLoaded(); + m_clzName = new ClassConstant(sName); + setModified(true); + } + + /** + * Determine the class constant for this class. + * + * @return this class's class constant + */ + public ClassConstant getClassConstant() + { + ensureLoaded(); + return m_clzName; + } + + + // ----- accessor: .class derivation ----------------------------------- + + /** + * Determine the fully qualified name of the super class. + * + * @return the super class name + */ + public String getSuper() + { + ensureLoaded(); + return (m_clzSuper == null ? (String) null : m_clzSuper.getValue()); + } + + /** + * Set the fully qualified name of the super class. + * + * @param sSuper the new super class name + */ + public void setSuper(String sSuper) + { + ensureLoaded(); + m_clzSuper = (sSuper == null ? (ClassConstant) null : new ClassConstant(sSuper)); + setModified(true); + } + + /** + * Determine the class constant for the super class. + * + * @return the super class's class constant + */ + public ClassConstant getSuperClassConstant() + { + ensureLoaded(); + return m_clzSuper; + } + + + // ----- accessor: implements ------------------------------------------ + + /** + * Add an implemented interface. (For interfaces, this corresponds to the + * Java language "extends" keyword.) + * + * @param sInterface the interface name + */ + public void addImplements(String sInterface) + { + ensureLoaded(); + m_tblInterface.put(sInterface.replace('.', '/'), new ClassConstant(sInterface)); + setModified(true); + } + + /** + * Remove an implemented interface. + * + * @param sInterface the interface name + */ + public void removeImplements(String sInterface) + { + ensureLoaded(); + m_tblInterface.remove(sInterface.replace('.', '/')); + setModified(true); + } + + /** + * Access the set of implemented interfaces. + * + * @return an enumeration of interface names + */ + public Enumeration getImplements() + { + ensureLoaded(); + return m_tblInterface.keys(); + } + + + // ----- accessor: interface ------------------------------------------- + + /** + * Determine if the interface attribute is set. + * + * @return true if interface + */ + public boolean isInterface() + { + ensureLoaded(); + return m_flags.isInterface(); + } + + /** + * Set the interface attribute. + * + * @param fInterface true to set to interface, false to set to class + */ + public void setInterface(boolean fInterface) + { + ensureLoaded(); + m_flags.setInterface(fInterface); + setModified(true); + } + + + // ----- accessor: access ---------------------------------------------- + + /** + * Get the class/method/field accessibility value. + * + * @return one of ACC_PUBLIC, ACC_PROTECTED, ACC_PRIVATE, or ACC_PACKAGE + */ + public int getAccess() + { + ensureLoaded(); + return m_flags.getAccess(); + } + + /** + * Set the class/method/field accessibility value. + * + * @param nAccess should be one of ACC_PUBLIC, ACC_PROTECTED, + * ACC_PRIVATE, or ACC_PACKAGE + */ + public void setAccess(int nAccess) + { + ensureLoaded(); + m_flags.setAccess(nAccess); + setModified(true); + } + + /** + * Determine if the accessibility is public. + * + * @return true if the accessibility is public + */ + public boolean isPublic() + { + ensureLoaded(); + return m_flags.isPublic(); + } + + /** + * Set the accessibility to public. + */ + public void setPublic() + { + ensureLoaded(); + m_flags.setPublic(); + setModified(true); + } + + /** + * Determine if the accessibility is protected. + * + * @return true if the accessibility is protected + */ + public boolean isProtected() + { + ensureLoaded(); + return m_flags.isProtected(); + } + + /** + * Set the accessibility to protected. + */ + public void setProtected() + { + ensureLoaded(); + m_flags.setProtected(); + setModified(true); + } + + /** + * Determine if the accessibility is package private. + * + * @return true if the accessibility is package private + */ + public boolean isPackage() + { + ensureLoaded(); + return m_flags.isPackage(); + } + + /** + * Set the accessibility to package private. + */ + public void setPackage() + { + ensureLoaded(); + m_flags.setPackage(); + setModified(true); + } + + /** + * Determine if the accessibility is private. + * + * @return true if the accessibility is private + */ + public boolean isPrivate() + { + ensureLoaded(); + return m_flags.isPrivate(); + } + + /** + * Set the accessibility to private. + */ + public void setPrivate() + { + ensureLoaded(); + m_flags.setPrivate(); + setModified(true); + } + + + // ----- accessor: Static ------------------------------------------- + + /** + * Determine if the Static attribute is set. + * + * @return true if Static + */ + public boolean isStatic() + { + ensureLoaded(); + return m_flags.isStatic(); + } + + /** + * Set the Static attribute. + * + * @param fStatic true to set to Static, false otherwise + */ + public void setStatic(boolean fStatic) + { + ensureLoaded(); + m_flags.setStatic(fStatic); + setModified(true); + } + + + // ----- accessor: abstract ------------------------------------------- + + /** + * Determine if the Abstract attribute is set. + * + * @return true if Abstract + */ + public boolean isAbstract() + { + ensureLoaded(); + return m_flags.isAbstract(); + } + + /** + * Set the Abstract attribute. + * + * @param fAbstract true to set to Abstract, false otherwise + */ + public void setAbstract(boolean fAbstract) + { + ensureLoaded(); + m_flags.setAbstract(fAbstract); + setModified(true); + } + + + // ----- accessor: final ------------------------------------------- + + /** + * Determine if the Final attribute is set. + * + * @return true if Final + */ + public boolean isFinal() + { + ensureLoaded(); + return m_flags.isFinal(); + } + + /** + * Set the Final attribute. + * + * @param fFinal true to set to Final, false otherwise + */ + public void setFinal(boolean fFinal) + { + ensureLoaded(); + m_flags.setFinal(fFinal); + setModified(true); + } + + + // ----- accessor: synchronized ------------------------------------------- + + /** + * Determine if the synchronized attribute is set. + * + * @return true if synchronized + */ + public boolean isSynchronized() + { + ensureLoaded(); + return m_flags.isSynchronized(); + } + + /** + * Set the synchronized attribute. + * + * @param fSynchronized true to set to synchronized, false otherwise + */ + public void setSynchronized(boolean fSynchronized) + { + ensureLoaded(); + m_flags.setSynchronized(fSynchronized); + setModified(true); + } + + + // ----- accessor: field ----------------------------------------------- + + /** + * Access a Java .class field structure. + * + * @param sName the field name + * + * @return the specified field or null if the field does not exist + */ + public Field getField(String sName) + { + ensureLoaded(); + return (Field) m_tblField.get(sName); + } + + /** + * Add a Java .class field structure. + * + * @param sName the field name + * @param sType the field type + * + * @return the new field + */ + public Field addField(String sName, String sType) + { + ensureLoaded(); + Field field = new Field(sName, sType); + m_tblField.put(field.getIdentity(), field); + setModified(true); + return field; + } + + /** + * Remove a field. + * + * @param sName the field name + */ + public void removeField(String sName) + { + ensureLoaded(); + m_tblField.remove(sName); + setModified(true); + } + + /** + * Access the set of fields. + * + * @return an enumeration of fields (not field names) + */ + public Enumeration getFields() + { + ensureLoaded(); + return m_tblField.elements(); + } + + /** + * Get a field constant for the specified class/interface field. + * + * @param sName the field name + * + * @return the specified field constant or null if the field does not exist + */ + public FieldConstant getFieldConstant(String sName) + { + Field field = getField(sName); + + return field == null ? null : + new FieldConstant(getName(), sName, field.getType()); + } + + // ----- accessor: method ----------------------------------------------- + + /** + * Access a Java .class method structure. + * + * @param sName the method name + * @param sSig the method signature + * + * @return the specified method or null if the method does not exist + */ + public Method getMethod(String sName, String sSig) + { + return getMethod(sName + sSig.replace('.', '/')); + } + + /** + * Access a Java .class method structure. + * + * @param sSig the complete JVM method signature, including the + * method name + * + * @return the specified method or null if the method does not exist + */ + public Method getMethod(String sSig) + { + ensureLoaded(); + return (Method) m_tblMethod.get(sSig); + } + + /** + * Add a Java .class method structure. + * + * @param sSig the complete JVM method signature, including the + * method name + * + * @return the new method + */ + public Method addMethod(String sSig) + { + int of = sSig.indexOf('('); + return addMethod(sSig.substring(0, of), sSig.substring(of)); + } + + /** + * Add a Java .class method structure. + * + * @param sName the method name + * @param sSig the method signature + * + * @return the new method + */ + public Method addMethod(String sName, String sSig) + { + ensureLoaded(); + Method method = new Method(sName, sSig, m_flags.isInterface()); + m_tblMethod.put(method.getIdentity(), method); + setModified(true); + return method; + } + + /** + * Remove a method. + * + * @param sName the method name + * @param sSig the method signature + */ + public void removeMethod(String sName, String sSig) + { + removeMethod(sName + sSig); + } + + /** + * Remove a method. + * + * @param sSig the complete JVM method signature, including the + * method name + */ + public void removeMethod(String sSig) + { + ensureLoaded(); + m_tblMethod.remove(sSig); + setModified(true); + } + + /** + * Access the set of methods. + * + * @return an enumeration of methods (not method names) + */ + public Enumeration getMethods() + { + ensureLoaded(); + return m_tblMethod.elements(); + } + + + /** + * Get a method constant for the specified method + * + * @param sSig the complete JVM method signature, including the + * method name + * + * @return the specified method constant or null if the method does not exist + */ + public MethodConstant getMethodConstant(String sSig) + { + Method method = getMethod(sSig); + + return method == null ? null : + new MethodConstant(getName(), method.getName(), method.getType()); + } + + // ----- accessor: attribute ------------------------------------------- + + /** + * Access a Java .class attribute structure. + * + * @param sName the attribute name + * + * @return the specified attribute or null if the attribute does not exist + */ + public Attribute getAttribute(String sName) + { + ensureLoaded(); + return (Attribute) m_tblAttribute.get(sName); + } + + /** + * Add a Java .class attribute structure. + * + * @param sName the attribute name + * + * @return the new attribute + */ + public Attribute addAttribute(String sName) + { + ensureLoaded(); + + Attribute attribute; + if (sName.equals(ATTR_FILENAME)) + { + attribute = new SourceFileAttribute(this); + } + else if (sName.equals(ATTR_DEPRECATED)) + { + attribute = new DeprecatedAttribute(this); + } + else if (sName.equals(ATTR_SYNTHETIC)) + { + attribute = new SyntheticAttribute(this); + } + else if (sName.equals(ATTR_INNERCLASSES)) + { + attribute = new InnerClassesAttribute(this); + } + else if (sName.equals(ATTR_ENCMETHOD)) + { + attribute = new EnclosingMethodAttribute(this); + } + else if (sName.equals(ATTR_SIGNATURE)) + { + attribute = new SignatureAttribute(this); + } + else if (sName.equals(ATTR_RTVISANNOT)) + { + attribute = new RuntimeVisibleAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTINVISANNOT)) + { + attribute = new RuntimeInvisibleAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTVISTANNOT)) + { + attribute = new RuntimeVisibleTypeAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTINVISTANNOT)) + { + attribute = new RuntimeInvisibleTypeAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_BOOTSTRAPMETHODS)) + { + attribute = new BootstrapMethodsAttribute(this); + } + else + { + attribute = new Attribute(this, sName); + } + + m_tblAttribute.put(attribute.getIdentity(), attribute); + setModified(true); + + return attribute; + } + + /** + * Remove a attribute. + * + * @param sName the attribute name + */ + public void removeAttribute(String sName) + { + ensureLoaded(); + m_tblAttribute.remove(sName); + setModified(true); + } + + /** + * Access the set of attributes. + * + * @return an enumeration of attributes (not attribute names) + */ + public Enumeration getAttributes() + { + ensureLoaded(); + return m_tblAttribute.elements(); + } + + + // ----- accessor: attribute helpers ----------------------------------- + + /** + * Determine if the class is deprecated. + * + * @return true if deprecated, false otherwise + */ + public boolean isDeprecated() + { + ensureLoaded(); + return m_tblAttribute.contains(ATTR_DEPRECATED); + } + + /** + * Toggle if the class is deprecated. + * + * @param fDeprecated pass true to deprecate, false otherwise + */ + public void setDeprecated(boolean fDeprecated) + { + if (fDeprecated) + { + addAttribute(ATTR_DEPRECATED); + } + else + { + removeAttribute(ATTR_DEPRECATED); + } + } + + /** + * Determine if the class is synthetic. + * + * @return true if synthetic, false otherwise + */ + public boolean isSynthetic() + { + ensureLoaded(); + return m_tblAttribute.contains(ATTR_SYNTHETIC); + } + + /** + * Toggle if the class is synthetic. + * + * @param fSynthetic pass true to set synthetic, false otherwise + */ + public void setSynthetic(boolean fSynthetic) + { + if (fSynthetic) + { + addAttribute(ATTR_SYNTHETIC); + } + else + { + removeAttribute(ATTR_SYNTHETIC); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "ClassFile"; + + /** + * Access flags applicable to a class. + */ + private static final int ACC_CLASS = AccessFlags.ACC_PUBLIC | + AccessFlags.ACC_PRIVATE | + AccessFlags.ACC_PROTECTED | + AccessFlags.ACC_STATIC | + AccessFlags.ACC_FINAL | + AccessFlags.ACC_ABSTRACT; + + /** + * Access flags applicable to an interface. + */ + private static final int ACC_INTERFACE = AccessFlags.ACC_PUBLIC; + + /** + * Major version of this class. + */ + private int m_nVersionMajor = VERSION_MAJOR; + + /** + * Minor version of this class. + */ + private int m_nVersionMinor = VERSION_MINOR; + + /** + * Has the class information been modified? + */ + private boolean m_fModified; + + /** + * Is the class in a "loaded" state? + */ + private boolean m_fLoaded; + + /** + * The .class structure stored (cached) as a byte array. + */ + private byte[] m_abClazz; + + /** + * The constant pool for the class. + */ + private ConstantPool m_pool; + + /** + * The fully qualified name of the Java .class being managed by this data + * structure. + */ + private ClassConstant m_clzName; + + /** + * The fully qualified name of the Java .class from which the Java .class + * being managed by this data structure derives. + */ + private ClassConstant m_clzSuper; + + /** + * The class/interface access flags. + */ + private AccessFlags m_flags; + + /** + * The interface names implemented by the Java .class. + */ + private StringTable m_tblInterface; + + /** + * The fields of the Java .class. + */ + private StringTable m_tblField; + + /** + * The methods of the Java .class. + */ + private StringTable m_tblMethod; + + /** + * The attributes of the Java .class. + */ + private StringTable m_tblAttribute; + + /** + * Three tables contained by the ClassFile storing VM structures which + * are often manipulated iteratively and identically by the ClassFile. + */ + private final StringTable[] CONTAINED_TABLE = new StringTable[3]; + + + // ----- inner classes -------------------------------------------------- + + /** + * A callback interface for swapping out constants in the constant pool. + * + * @see ClassFile#resolve(com.tangosol.dev.assembler.ClassFile.Resolver) + */ + public static interface Resolver + { + Constant resolve(Constant constOrig); + } + + /** + * An implementation of Resolver which relocates a package. + * + * @see ClassFile#resolve(com.tangosol.dev.assembler.ClassFile.Resolver) + */ + public static class Relocator implements Resolver + { + public static final String PACKAGE = "_package/"; + + private String sPkg; + + public Relocator(String sPkg) + { + if (sPkg.length() > 0) + { + sPkg = sPkg.replace('.', '/'); + if (!sPkg.endsWith("/")) + { + sPkg += '/'; + } + } + + this.sPkg = sPkg; + } + + public Constant resolve(Constant constOrig) + { + if (constOrig instanceof UtfConstant) + { + UtfConstant utf = (UtfConstant) constOrig; + String s = utf.getValue(); + int of = s.indexOf(PACKAGE); + if (of >= 0) + { + do + { + s = s.substring(0, of) + sPkg + s.substring(of + PACKAGE.length()); + of = s.indexOf(PACKAGE); + } + while (of >= 0); + return new UtfConstant(s); + } + } + + return constOrig; + } + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Cnewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Cnewarray.java new file mode 100644 index 0000000000000..7ce4d5355a2b1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Cnewarray.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The CNEWARRAY pseudo-op instantiates an array of char. +*

+* JASM op         :  CNEWARRAY    (0xf5)
+* JVM byte code(s):  NEWARRAY     (0xbc)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Cnewarray extends OpArray implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Cnewarray() + { + super(CNEWARRAY); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Cnewarray"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/CodeAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/CodeAttribute.java new file mode 100644 index 0000000000000..4f1229dd386e3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/CodeAttribute.java @@ -0,0 +1,1695 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.Iterator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Vector; + +import com.tangosol.util.StringTable; + + +/** +* Represents a Java Virtual Machine "code" attribute which contains Java +* Byte Codes. +*

+* The Code Attribute is defined by the Java Virtual Machine Specification as +* follows: +*

+*

+*   Code_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u2 max_stack;
+*       u2 max_locals;
+*       u4 code_length;
+*       u1 code[code_length];
+*       u2 exception_table_length;
+*           {
+*           u2 start_pc;
+*           u2 end_pc;
+*           u2  handler_pc;
+*           u2  catch_type;
+*           } exception_table[exception_table_length];
+*       u2 attributes_count;
+*       attribute_info attributes[attributes_count];
+*       }
+* 
+* +* @version 0.50, 05/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class CodeAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a code attribute. + * + * @param context the JVM structure containing the attribute + */ + protected CodeAttribute(VMStructure context) + { + super(context, ATTR_CODE); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + + // read the information related to the method's frame size; + // this includes the number of local variable slots and the + // maximum stack size + m_cwStack = stream.readUnsignedShort(); + m_cVars = stream.readUnsignedShort(); + + // read the Java byte codes + int cbCode = stream.readInt(); + if (cbCode <= 0 || cbCode > 0xFFFF) + { + throw new IllegalStateException("Method code attribute is restricted " + + "to the range 0-65535 bytes"); + } + + m_abCode = new byte[cbCode]; + stream.readFully(m_abCode); + + // store the constant pool for later reference (when the byte codes + // are disassembled) + m_pool = pool; + + // read the exception table; each entry in the exception table + // specifies a range of byte codes which are guarded, the exception + // being guarded for, and the byte code to transfer execution to if + // that exception occurs + m_vectCatch.clear(); + int cExcept = stream.readUnsignedShort(); + for (int i = 0; i < cExcept; ++i) + { + GuardedSection section = new GuardedSection(); + section.disassemble(stream, pool); + m_vectCatch.add(section); + } + + // the Code attribute also contains attributes; there are only two + // expected (aka supported by JVM) attributes: LineNumberTable and + // LocalVariableTable + m_tblAttribute.clear(); + int cAttr = stream.readUnsignedShort(); + for (int i = 0; i < cAttr; ++i) + { + Attribute attribute = Attribute.loadAttribute(this, stream, pool); + m_tblAttribute.put(attribute.getIdentity(), attribute); + } + + m_nState = LOADED; + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + + // byte code + ensureOps(); + preassembleOps(pool); + + // guarded sections + for (Enumeration enmr = m_vectCatch.elements(); enmr.hasMoreElements(); ) + { + ((GuardedSection) enmr.nextElement()).preassemble(pool); + } + + // sub-attributes + for (Enumeration enmr = m_tblAttribute.elements(); enmr.hasMoreElements(); ) + { + ((Attribute) enmr.nextElement()).preassemble(pool); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + // assemble the ops into byte codes; this also sets up the guarded + // section information, frame (stack size/variable count) + // information, and the default sub-attributes + assembleOps(pool); + + // assemble the sub-attributes + byte[] abAttr = NO_BYTES; + int cAttrs = m_tblAttribute.getSize(); + if (cAttrs > 0) + { + ByteArrayOutputStream streamRaw = new ByteArrayOutputStream(); + DataOutput streamAttr = new DataOutputStream(streamRaw); + for (Enumeration enmr = m_tblAttribute.elements(); enmr.hasMoreElements(); ) + { + ((Attribute) enmr.nextElement()).assemble(streamAttr, pool); + } + abAttr = streamRaw.toByteArray(); + } + + // header (name of attribute, length of attribute) + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(2 // max stack + + 2 // max locals + + 4 // code length + + m_abCode.length // code + + 2 // guarded section count + + 8 * m_vectCatch.size() // guarded sections + + 2 // attribute count + + abAttr.length); // attributes + + // body + stream.writeShort(m_cwStack); + stream.writeShort(m_cVars); + stream.writeInt(m_abCode.length); + stream.write(m_abCode); + stream.writeShort(m_vectCatch.size()); + for (Enumeration enmr = m_vectCatch.elements(); enmr.hasMoreElements(); ) + { + ((GuardedSection) enmr.nextElement()).assemble(stream, pool); + } + stream.writeShort(cAttrs); + stream.write(abAttr); + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + if (m_fModified) + { + return true; + } + + // attributes + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + Attribute attr = (Attribute) enmr.nextElement(); + if (attr.isModified()) + { + return true; + } + } + + return false; + } + + /** + * Reset the modified state of the VM structure. + * + * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + + // attributes + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + ((Attribute) enmr.nextElement()).resetModified(); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return "Code attribute for " + getContext().toString(); + } + + + // ----- internal state management -------------------------------------- + + /** + * Make sure that the ops are disassembled if the code is from a + * disassembled class. + */ + protected void ensureOps() + { + if (m_nState == LOADED || m_nState == ASSEMBLED) + { + disassembleOps(); + } + } + + /** + * Disassemble the byte code into JASM ops. This is done the first time + * that an operation requires the byte code to be disassembled. + */ + protected void disassembleOps() + { + // assertion + if (m_abCode == null || m_opFirst != null) + { + throw new IllegalStateException(CLASS + + ".disassembleOps: No code exists or ops already exist!"); + } + + // at this point, all the inputs exist to disassemble the code + // m_abCode - the byte code + // m_cVars - number of local variables (actually, the number + // of words) + // m_vectCatch - guarded sections, which become try/catch ops + // m_tblAttribute - the attributes table, which may contain the line + // number and local variable information + // m_pool - the constant pool + try + { + // get a linked list of ops + Method method = (Method) getContext(); + + // build an array of data types for the parameters + String[] asTypes = method.getTypes(); + if (method.isStatic()) + { + // return param is in element 0; remove it + int cNew = asTypes.length - 1; + String[] asNew = new String[cNew]; + System.arraycopy(asTypes, 1, asNew, 0, cNew); + asTypes = asNew; + } + else + { + // return param is in element 0; replace it with "this" + asTypes[0] = 'L' + method.getClassName() + ';'; + } + + Op[] aopBounds = new Op[2]; + + Op.disassembleOps( + m_abCode, + m_cVars, + asTypes, + m_vectCatch, + (LineNumberTableAttribute) m_tblAttribute.get(ATTR_LINENUMBERS), + (LocalVariableTableAttribute) m_tblAttribute.get(ATTR_VARIABLES ), + (LocalVariableTypeTableAttribute) m_tblAttribute.get(ATTR_VARIABLETYPES), + m_pool, + aopBounds); + + m_opFirst = aopBounds[0]; + m_opLast = aopBounds[1]; + } + catch (IOException e) + { + throw ensureRuntimeException(e); + } + + m_abCode = null; + m_nState = DISASSEMBLED; + } + + /** + * Make a quick pass at the JASM ops to remove dead code and register + * any constants. + * + * @param pool the constant pool for the class + */ + protected void preassembleOps(ConstantPool pool) + { + // assert: expected state (unassembled) and something to assemble + if (!((m_nState == ADDED || m_nState == DISASSEMBLED) && m_opFirst != null)) + { + throw new IllegalStateException(CLASS + ".preassembleOps: Illegal state (" + + m_nState + ") or no ops to assemble!"); + } + + // always start the code with a label; this makes it easier to + // enforce type safety since all non-linear execution (including + // the initial method invocation) will start with a LABEL op + if (!(m_opFirst instanceof Label)) + { + Label label = new Label(); + label.setNext(m_opFirst); + m_opFirst = label; + } + + // collect all code contexts starting with the main context + Label labelMain = (Label) m_opFirst; + HashSet setContexts = new HashSet(); + setContexts.add(labelMain); + + // calculate the maximum stack size (also marks dead code, etc.) + int cwStack = checkStack(labelMain, 0, 0, labelMain, setContexts, 0); + + // assign an integer index to each context + int cContexts = 0; + for (Iterator iter = setContexts.iterator(); iter.hasNext(); ) + { + ((Label) iter.next()).setContextIndex(cContexts++); + } + + // attributes of the code attribute which support debugging + LineNumberTableAttribute attrLines = new LineNumberTableAttribute(this); + LocalVariableTableAttribute attrVars = null; + if (m_nState == DISASSEMBLED) + { + // use the disassembled local variable table (it is close + // to impossible to reconstruct scope etc. from assembled + // code so assume the original information was correct) + attrVars = (LocalVariableTableAttribute) m_tblAttribute.get(ATTR_VARIABLES); + } + if (attrVars == null) + { + attrVars = new LocalVariableTableAttribute(this); + } + + // iterate through the ops, removing dead code, registering + // constants, matching up BEGINs/ENDs (scope) and associating scopes + // with variable declarations, assigning register (variable) offsets, + // and preparing for the definite assignment determination + Begin begin = null; // current scope + HashSet setVars = new HashSet(); // currently in-scope variables + boolean fDebugInfo= false; // set to true if any debug info + Vector vectVar = new Vector(); // all variable declaration ops + int[] aiVar = new int[cContexts]; // variable pool (by code context) + int cMaxSlots = 0; // number of variable slots + int iPrevLine = 0; // line number of the previous op + Vector vectCatch = new Vector(); // guarded session information +pass: for (Op op = m_opFirst, opPrev = null; op != null; op = (opPrev = op).getNext()) + { + // remove unreachable ops (dead code) + if (op.isDiscardable()) + { + do + { + op = op.getNext(); + if (op == null) + { + opPrev.setNext(null); + break pass; + } + } + while (op.isDiscardable()); + + // fix the linked list, removing all dead code + opPrev.setNext(op); + } + + // specific handling for certain ops + int iOp = op.getValue(); + switch (iOp) + { + case BEGIN: + { + Begin beginNew = (Begin) op; + + // store the current variable pool state with the scope + beginNew.setVariablePool((int[]) aiVar.clone()); + + // push this begin onto the scope stack + beginNew.setOuterScope(begin); + + // use the new begin as the start of the current scope + begin = beginNew; + } + break; + + case END: + { + End end = (End) op; + + // END must have a matching BEGIN + if (begin == null) + { + throw new IllegalStateException(CLASS + ".preassembleOps: " + + "END without BEGIN!"); + } + begin.setEnd(end); + + // update currently declared variable list + // (used later by definite assignment) + for (Enumeration enmr = begin.getDeclarations(); enmr.hasMoreElements(); ) + { + OpDeclare decl = (OpDeclare) enmr.nextElement(); + setVars.remove(decl); + } + + // restore the variable pool + aiVar = begin.getVariablePool(); + + // pop a begin off of the scope stack + begin = begin.getOuterScope(); + } + break; + + case IVAR: + case LVAR: + case FVAR: + case DVAR: + case AVAR: + case RVAR: + { + OpDeclare decl = (OpDeclare) op; + + // must have begin + if (begin == null) + { + throw new IllegalStateException(CLASS + ".preassembleOps: " + + "Declaration (?VAR) without BEGIN!"); + } + + // add the local variable to the list of all variables + // which do not have an absolute register index + vectVar.addElement(decl); + + // add the local variable to the scope + begin.addDeclaration(decl); + + // inform the variable of its scope + decl.setBegin(begin); + + // update currently declared variable list + // (used later by definite assignment) + if (decl.hasDebugInfo()) + { + fDebugInfo = true; + setVars.add(decl); + } + + int iVar = decl.getSlot(); + if (iVar == UNKNOWN) + { + // assign a register offset (variable slot offset) + // note: this is not the actual variable slot index + // unless the context is the main context; + // the index is calculated later based on the number + // of variable slots already used when the variable + // declaration's code context is entered + int iCtx = decl.getContext().getContextIndex(); + iVar = aiVar[iCtx]; + decl.setSlot(iVar); + aiVar[iCtx] = iVar + decl.getWidth(); + } + else + { + int cSlots = iVar + decl.getWidth(); + if (cSlots > cMaxSlots) + { + cMaxSlots = cSlots; + } + } + } + break; + + case ILOAD: + case LLOAD: + case FLOAD: + case DLOAD: + case ALOAD: + case ISTORE: + case LSTORE: + case FSTORE: + case DSTORE: + case ASTORE: + case RSTORE: + case IINC: + case RET: + { + OpDeclare decl = ((OpVariable)op).getVariable(); + + // if begin is unknown then no var declaration + // if end op is known then var is out of scope + if (decl.getBegin() == null || decl.getEnd() != null) + { + throw new IllegalStateException(CLASS + ".preassembleOps: " + + "Variable used out of scope! " + op); + } + } + break; + + case JSR: + { + Jsr jsr = (Jsr) op; + + // store the next available variable number for the + // context containing the JSR op (to help determine + // the variable number base for the subroutine context + // which is branched to by the JSR op) + int iCtx = jsr.getContext().getContextIndex(); + jsr.setFirstSlot(aiVar[iCtx]); + } + break; + + case LABEL: + { + Label label = (Label) op; + + // register all currently in-scope variables with the + // label; this information is used during the definite + // assignment check + label.setVariables((HashSet) setVars.clone()); + } + break; + + case CATCH: + { + // the guarded sections are built in the order that the Catch + // ops are encounted; this reflects Java language compilation + Catch opCatch = (Catch) op; + Try opTry = opCatch.getTry(); + ClassConstant clz = opCatch.getExceptionClass(); + Label label = opCatch.getLabel(); + GuardedSection section = new GuardedSection(opTry, opCatch, clz, label); + vectCatch.addElement(section); + } + break; + } + + // build line number information + int iCurLine = op.getLine(); + if (iCurLine != iPrevLine && iCurLine > 0 && op.hasSize()) + { + attrLines.add(op); + iPrevLine = iCurLine; + } + + // register any constants + op.preassemble(pool); + } + + // assert: verify that all begins were matched with ends + if (begin != null) + { + throw new IllegalStateException(CLASS + ".preassembleOps: " + + "BEGIN without END!"); + } + + // assign variable slots (unless they were pre-assigned, e.g. the ops + // were disassembled and therefore already had variable slot numbers) + if (cMaxSlots == 0) + { + // organize contexts by depth + Label[] alabelContext = (Label[]) setContexts.toArray(LABEL_ARRAY); + Arrays.sort(alabelContext); + + // assert: number of contexts hasn't changed + if (alabelContext.length != cContexts) + { + throw new IllegalStateException(CLASS + ".preassembleOps: " + + "Unexpected number of contexts!"); + } + + // assert: first context is main (depth 0) and if second context + // exists, it is depth >0 + if (alabelContext[0].getDepth() != 0 || + cContexts > 1 && alabelContext[1].getDepth() != 1) + { + throw new IllegalStateException(CLASS + ".preassembleOps: " + + "Unexpected depths of contexts!"); + } + + // determine register base for each non-main context + for (int i = 0; i < cContexts; ++i) + { + Label labelContext = alabelContext[i]; + int iFirstSlot = 0; + for (Enumeration enmr = labelContext.getCallers(); enmr.hasMoreElements(); ) + { + Jsr jsr = (Jsr) enmr.nextElement(); + int iSlot = jsr.getContext().getFirstSlot() + jsr.getFirstSlot(); + if (iSlot > iFirstSlot) + { + iFirstSlot = iSlot; + } + } + labelContext.setFirstSlot(iFirstSlot); + } + + // assign absolute register slots to variables + // determine number of local variables ("slots") + for (Enumeration enmr = vectVar.elements(); enmr.hasMoreElements(); ) + { + OpDeclare decl = (OpDeclare) enmr.nextElement(); + int iSlotOffset = decl.getSlot(); + Label labelContext = decl.getContext(); + + int iSlot = labelContext.getFirstSlot() + iSlotOffset; + int cSlots = iSlot + decl.getWidth(); + + decl.setSlot(iSlot); + if (cSlots > cMaxSlots) + { + cMaxSlots = cSlots; + } + } + + if (fDebugInfo) + { + // definite assignment: build the local variable table + // 1) determine the set of automatically-assigned variables + // (the parameters to the method) + Method method = (Method) getContext(); + String[] asType = method.getTypes(); + int cTypes = asType.length; + int cwParams = (method.isStatic() ? 0 : 1); + for (int i = 0; i < cTypes; ++i) + { + char chType = asType[i].charAt(0); + cwParams += OpDeclare.getJavaWidth(chType); + } + + // 2) determine what variables are assigned at each label + // (checkAssignment) + checkAssignment(labelMain, setVars, cwParams); + + // 3) iterate through all ops, building a map keyed by + // variable with the value being the op where the + // variable became definitely assigned (either a label + // or a ?STORE op) + // 4) when a variable located in the map is un-assigned + // either by a label which does not contain that + // variable in its assigned list or by an END op which + // ends the scope of the variable, add a new range + // to the local variable table + HashMap mapVars = new HashMap(); + for (Op op = m_opFirst; op != null; op = op.getNext()) + { + switch (op.getValue()) + { + case IVAR: + case LVAR: + case FVAR: + case DVAR: + case AVAR: + case RVAR: + { + OpDeclare decl = (OpDeclare) op; + + // parameters are definitely assigned + if (decl.hasDebugInfo() + && decl.getSlot() < cwParams + && !mapVars.containsKey(decl)) + { + mapVars.put(decl, decl); + } + } + break; + + case ISTORE: + case LSTORE: + case FSTORE: + case DSTORE: + case ASTORE: + case RSTORE: + { + OpStore stor = (OpStore) op; + OpDeclare decl = stor.getVariable(); + + // the ?STORE ops definitely assign a variable + if (decl.hasDebugInfo() && !mapVars.containsKey(decl)) + { + mapVars.put(decl, stor); + } + } + break; + + case LABEL: + { + Label label = (Label) op; + HashSet setLabelVars = label.getVariables(); + + // a LABEL can unassign variables which are in + // the current map but which are not in its set + Iterator iterator = mapVars.entrySet().iterator(); + while (iterator.hasNext()) + { + Map.Entry entry = (Map.Entry) iterator.next(); + OpDeclare decl = (OpDeclare) entry.getKey(); + if (!setLabelVars.contains(decl)) + { + Op opInit = (Op) entry.getValue(); + attrVars.add(decl, opInit, label); + iterator.remove(); + } + } + + // a LABEL can assign variables which are in its + // set of definitely assigned variables but which + // are not in the current map + if (setLabelVars.size() != mapVars.size()) + { + iterator = setLabelVars.iterator(); + while (iterator.hasNext()) + { + OpDeclare decl = (OpDeclare) iterator.next(); + if (!mapVars.containsKey(decl)) + { + mapVars.put(decl, label); + } + } + } + } + break; + + case END: + { + End end = (End) op; + + // an END op unassigns all variables that go out + // of scope + Iterator iterator = mapVars.entrySet().iterator(); + while (iterator.hasNext()) + { + Map.Entry entry = (Map.Entry) iterator.next(); + OpDeclare decl = (OpDeclare) entry.getKey(); + if (decl.getEnd() == end) + { + Op opInit = (Op) entry.getValue(); + attrVars.add(decl, opInit, end); + iterator.remove(); + } + } + } + break; + } + } + } + } + + // store the frame size (register count and stack height) + m_cVars = cMaxSlots; + m_cwStack = cwStack; + + // store the guarded section information + m_vectCatch = vectCatch; + + // store the line number table + if (attrLines.isEmpty()) + { + m_tblAttribute.remove(attrLines.getIdentity()); + } + else + { + m_tblAttribute.put(attrLines.getIdentity(), attrLines); + } + + // store the local variable table (but only if there is any + // line number information) + if (attrLines.isEmpty() || attrVars.isEmpty()) + { + m_tblAttribute.remove(attrVars.getIdentity()); + } + else + { + m_tblAttribute.put(attrVars.getIdentity(), attrVars); + } + } + + /** + * Assemble the JASM ops into byte code. This is done when the attribute + * is asked to assemble. + * + * @param pool the constant pool for the class which contains any + * constants referenced by the byte code + */ + protected void assembleOps(ConstantPool pool) + throws IOException + { + // assert: expected state (unassembled) and something to assemble + if (!((m_nState == ADDED || m_nState == DISASSEMBLED) && m_opFirst != null)) + { + throw new IllegalStateException(CLASS + ".assembleOps: Illegal state (" + + m_nState + ") or no ops to assemble!"); + } + + // assign PC's (byte code offsets) to each op + int of = 0; + for (Op op = m_opFirst; op != null; op = op.getNext()) + { + op.setOffset(of); + op.calculateSize(pool); + of += op.getSize(); + } + + // assemble the byte code + ByteArrayOutputStream streamRaw = new ByteArrayOutputStream(); + DataOutput stream = new DataOutputStream(streamRaw); + for (Op op = m_opFirst; op != null; op = op.getNext()) + { + op.assemble(stream, pool); + } + + // store the byte code + m_abCode = streamRaw.toByteArray(); + m_nState = ASSEMBLED; + } + + /** + * Walk the code as the JVM would, following all possible branches, and + * verify that the stack is maintained properly by the code. + * + * @param labelFirst the label to start checking the stack from + * @param cwCurStack the size of the stack immediately before opFirst + * @param cwMaxStack the maximum size of the stack encountered so far + * @param labelContext the label starting the code (either main or sub) + * @param setContexts the set of all reachable code contexts + * @param iSubDepth the current depth in the subroutine call stack + * (0==main) + * + * @return the maximum stack size encountered + */ + protected int checkStack(Label labelFirst, int cwCurStack, int cwMaxStack, Label labelContext, Set setContexts, int iSubDepth) + { + // check for a new max stack (e.g. from JSR pushing the return address) + if (cwCurStack > cwMaxStack) + { + cwMaxStack = cwCurStack; + } + + // if the label isn't a new code context, verify that the label + // is not reached by a JSR + if (labelFirst != labelContext && labelFirst.isSubroutine()) + { + throw new IllegalStateException(CLASS + ".checkStack: " + + "Label reachable both by subroutine invocation and otherwise!"); + } + + for (Op op = labelFirst; op != null; op = op.getNext()) + { + // check if this op has already been visited + boolean fVisited = op.isReachable(); + + // set the expected stack height for the op + op.setStackHeight(cwCurStack); + + // in order to compute the stack change for a JSR, trace the + // branch to its conclusion (i.e. the RET) + int iOp = op.getValue(); + if (iOp == JSR) + { + Jsr jsr = (Jsr) op; + Label label = jsr.getLabel(); + boolean fSub = label.isSubroutine(); + boolean fPrev = label.isReachable(); + + // each reachable JSR is within a specific code context; + // the JSR must know its code context in order to help with + // the calculation of subroutine depth + jsr.setContext(labelContext); + + // each subroutine label knows all reachable JSR ops which + // invoke the subroutine + label.addCaller(jsr); + + // there are several possibilities: + // 1) the label has already been reached + // 1) it is not marked as a subroutine so the code is + // illegal (a subroutine must only be accessed by + // a JSR statement) + // 2) it is marked as a subroutine but the RET height + // at the label is unknown, which means that either + // the call is recursive or the subroutine does not + // reach a RET + // 3) it is marked as a subroutine and the RET height + // is already known so use it + // 2) the label has not already been reached so mark the + // label as a subroutine and calculate its RET height + // by checking the stack from there + if (!fSub) + { + // verify that the label has not already been reached by + // some non-JSR method + if (fPrev) + { + throw new IllegalStateException(CLASS + ".checkStack: " + + "Label reachable both by subroutine invocation and otherwise!"); + } + + // keep track of all subroutines (each is a code context) + setContexts.add(label); + + // JSR pushes one word onto the stack before transferring + // control to the label + cwMaxStack = checkStack(label, cwCurStack + 1, cwMaxStack, label, setContexts, iSubDepth + 1); + } + + // if a RET was not found for the JSR, then the JSR does + // not continue + if (label.getRet() == null) + { + return cwMaxStack; + } + } + + // update the size of the stack based on the affect of this op + cwCurStack += op.getStackChange(); + + // check for new max stack + if (cwCurStack > cwMaxStack) + { + cwMaxStack = cwCurStack; + } + // check for stack underflow + else if (cwCurStack < 0) + { + throw new IllegalStateException(CLASS + ".checkStack: " + + "Stack underflow on " + op.toString() + " (#" + + op.getValue() + " @" + op.getOffset() + ")!"); + } + + switch (iOp) + { + case LABEL: + { + Label label = (Label) op; + + // check if we've already been here; if so then return + if (fVisited) + { + if (labelContext != label.getContext()) + { + throw new IllegalStateException(CLASS + ".checkStack: " + + "Label " + label + " is reachable within two " + + "different code contexts!"); + } + + return cwMaxStack; + } + else + { + label.setContext(labelContext); + } + } + break; + + case RET: + // must be here from a JSR + if (iSubDepth > 0) + { + labelContext.setRet((Ret)op); + labelContext.setRetHeight(cwCurStack); + return cwMaxStack; + } + else + { + throw new IllegalStateException(CLASS + ".checkStack: " + + "RET without JSR!"); + } + + case GOTO: + return checkStack(((Goto)op).getLabel(), cwCurStack, cwMaxStack, labelContext, setContexts, iSubDepth); + + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ACMPEQ: + case IF_ACMPNE: + case IFNULL: + case IFNONNULL: + cwMaxStack = checkStack(((OpBranch)op).getLabel(), cwCurStack, cwMaxStack, labelContext, setContexts, iSubDepth); + break; + + case SWITCH: + case TABLESWITCH: + case LOOKUPSWITCH: + { + OpSwitch opswitch = (OpSwitch)op; + + // check default branch + cwMaxStack = checkStack(opswitch.getLabel(), cwCurStack, cwMaxStack, labelContext, setContexts, iSubDepth); + + // check cases + Case[] aopCase = opswitch.getCases(); + int cCases = aopCase.length; + for (int i = 0; i < cCases; ++i) + { + cwMaxStack = checkStack(aopCase[i].getLabel(), cwCurStack, cwMaxStack, labelContext, setContexts, iSubDepth); + } + } + return cwMaxStack; + + case TRY: + { + Try optry = (Try) op; + Catch[] aopCatch = optry.getCatches(); + int cCatches = aopCatch.length; + for (int i = 0; i < cCatches; ++i) + { + // there is always exactly one word on the stack when + // an exception handler is invoked + cwMaxStack = checkStack(aopCatch[i].getLabel(), 1, cwMaxStack, labelContext, setContexts, iSubDepth); + } + } + break; + + case IRETURN: + case LRETURN: + case FRETURN: + case DRETURN: + case ARETURN: + case RETURN: + case ATHROW: + return cwMaxStack; + + case IVAR: + case LVAR: + case FVAR: + case DVAR: + case AVAR: + case RVAR: + { + // associate each declaration with a code context + OpDeclare decl = (OpDeclare) op; + decl.setContext(labelContext); + } + break; + } + } + + throw new IllegalStateException(CLASS + ".checkStack: " + + "Code not terminated properly! (e.g. no return)"); + } + + /** + * Walk the code as the JVM would, following all possible branches, and + * determine which variables are assigned at each label. + * + * @param labelFirst the op to start checking from + * @param setVars the current set of variables that have a value + * @param cwParams the number of parameter words to the method + */ + protected void checkAssignment(Label labelFirst, HashSet setVars, int cwParams) + { + for (Op op = labelFirst; op != null; op = op.getNext()) + { + int iOp = op.getValue(); + switch (iOp) + { + case IVAR: + case LVAR: + case FVAR: + case DVAR: + case AVAR: + case RVAR: + { + OpDeclare decl = (OpDeclare) op; + + // parameters are definitely assigned + if (decl.getSlot() < cwParams) + { + setVars.add(decl); + } + } + break; + + case ISTORE: + case LSTORE: + case FSTORE: + case DSTORE: + case ASTORE: + case RSTORE: + { + OpStore stor = (OpStore) op; + + // the ?STORE ops definitely assign a variable + OpDeclare decl = stor.getVariable(); + setVars.add(decl); + } + break; + + case LABEL: + { + Label label = (Label) op; + HashSet setLabelVars = label.getVariables(); + + // determine the intersection of the two sets of + // assigned variables; + // if the set of definitely assigned variables at the + // label did not change, and if we've been here before, + // then return (no further changes to make on this path) + if (!setLabelVars.retainAll(setVars) && label.isVisited()) + { + return; + } + + // make sure the current set of definitely-assigned, + // in-scope variables is up-to-date + if (setVars.size() != setLabelVars.size()) + { + setVars = (HashSet) setLabelVars.clone(); + } + + label.setVisited(true); + } + break; + + case JSR: + { + Jsr jsr = (Jsr) op; + Label label = jsr.getLabel(); + Ret ret = label.getRet(); + + // check variable assignments in the subroutine + // (Note: assignments made by the subroutine are not + // reflected when control returns to this point) + checkAssignment(label, (HashSet) setVars.clone(), cwParams); + + // if the flow of control cannot return, don't continue + if (ret == null) + { + return; + } + + // merge the definitely assigned variables at the RET + // with the current set; (union of in-scope assigned + // vars, although some of the RET vars may not be in- + // scope; that is automatically corrected on the next + // encountered label) + setVars.addAll(ret.getVariables()); + } + break; + + case RET: + { + Ret ret = (Ret) op; + + // keep an intersection of the current set of assigned + // variables with the set already stored on the RET + HashSet setRetVars = ret.getVariables(); + if (setRetVars == null) + { + // none registered on the ret; store the current set + ret.setVariables(setVars); + } + else + { + // compute intersection of in-scope assigned vars + setRetVars.retainAll(setVars); + } + } + return; + + case GOTO: + // since the flow of control is transferring uncondition- + // ally, pass the current set of assigned variables + checkAssignment(((Goto)op).getLabel(), setVars, cwParams); + return; + + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ACMPEQ: + case IF_ACMPNE: + case IFNULL: + case IFNONNULL: + // the flow of control can branch at this point + checkAssignment(((OpBranch)op).getLabel(), (HashSet) setVars.clone(), cwParams); + break; + + case SWITCH: + case TABLESWITCH: + case LOOKUPSWITCH: + { + OpSwitch opswitch = (OpSwitch)op; + + // check cases + Case[] aopCase = opswitch.getCases(); + int cCases = aopCase.length; + for (int i = 0; i < cCases; ++i) + { + checkAssignment(aopCase[i].getLabel(), (HashSet) setVars.clone(), cwParams); + } + + // check default branch + checkAssignment(opswitch.getLabel(), setVars, cwParams); + } + return; + + case TRY: + { + Try optry = (Try) op; + Catch[] aopCatch = optry.getCatches(); + int cCatches = aopCatch.length; + for (int i = 0; i < cCatches; ++i) + { + checkAssignment(aopCatch[i].getLabel(), (HashSet) setVars.clone(), cwParams); + } + } + break; + + case IRETURN: + case LRETURN: + case FRETURN: + case DRETURN: + case ARETURN: + case RETURN: + case ATHROW: + return; + } + } + + throw new IllegalStateException(CLASS + ".checkAssignment: " + + "Code not terminated properly! (e.g. no return)"); + } + + + // ----- assembler interface -------------------------------------------- + + /** + * Clear any ops and associated structures. + */ + public void clear() + { + m_abCode = null; + m_opFirst = null; + m_opLast = null; + m_iLine = 0; + m_vectCatch.clear(); + + m_nState = CLEAR; + m_fModified = true; + } + + /** + * Add an op to the code. This is the primary means of building byte + * code. + * + * @param op an instance of Op (or an Op sub-class) + * + * @return the op that was added + */ + public Op add(Op op) + { + // assertions + if (m_nState > ADDED) + { + throw new IllegalStateException(CLASS + ".add: Illegal state (" + m_nState + ")"); + } + if (op == null) + { + throw new IllegalArgumentException(CLASS + ".add: Op is required!"); + } + if (op instanceof Case) + { + // a Case op follows only a Switch or Case op + if (!(m_opLast instanceof Switch || m_opLast instanceof Case)) + { + throw new IllegalArgumentException(CLASS + + ".add: A Case Op can only follow a Switch or a Case op!"); + } + } + + // set the source code line for the op + op.setLine(m_iLine); + + // add the op to the linked list + if (m_opFirst == null) + { + m_opFirst = op; + m_opLast = op; + } + else + { + m_opLast.setNext(op); + m_opLast = op; + } + + // update state + m_nState = ADDED; + m_fModified = true; + + return op; + } + + /** + * Get the current "source code line". + * + * @return the current line of code + */ + public int getLine() + { + return m_iLine; + } + + /** + * Set the current "source code line". + * + * @param iLine the current line of code + */ + public void setLine(int iLine) + { + m_iLine = iLine; + } + + /** + * Advance to the next "source code line". + */ + public void nextLine() + { + ++m_iLine; + } + + /** + * Return the first op-code for this CodeAttribute + */ + public Op getFirstOp() + { + ensureOps(); + return m_opFirst; + } + + /** + * For debugging or listing purposes, print the code details. + */ + public void print() + { + out("Code Listing:"); + for (Op op = m_opFirst; op != null; op = op.getNext()) + { + op.print(); + } + + LocalVariableTableAttribute attr = + (LocalVariableTableAttribute) m_tblAttribute.get(ATTR_VARIABLES); + if (attr != null) + { + out("Local variable table:"); + out(attr); + } + } + + /** + * For script display purposes, format the JASM code. + */ + public String toJasm() + { + StringBuffer sb = new StringBuffer(8192); + switch (m_nState) + { + case LOADED: + ensureOps(); + // fall through + + case DISASSEMBLED: + if (m_opFirst != null) + { + int cDigits = getMaxDecDigits(m_opLast.getOffset()); + int nLine = 0; + for (Op op = m_opFirst; op != null; op = op.getNext()) + { + int nOpLine = op.getLine(); + if (nOpLine != nLine) + { + nLine = nOpLine; + sb.append('\n') + .append(toDecString(op.getOffset(), cDigits)) + .append(": // line ") + .append(nLine); + } + + String s = op.toJasm(); + if (s != null) + { + sb.append('\n') + .append(toDecString(op.getOffset(), cDigits)) + .append(": ") + .append(s); + } + } + break; + } + // fall through + + default: + for (Op op = m_opFirst; op != null; op = op.getNext()) + { + sb.append("\n[") + .append(op.getOffset()) + .append("] (") + .append(op.getLine()) + .append(") ") + .append(op.toString()); + } + break; + } + + return sb.substring(1); + } + + // ----- accessor: attribute ------------------------------------------- + + /** + * Access an attribute structure. + * + * @param sName the attribute name + * + * @return the specified attribute or null if the attribute does not exist + */ + public Attribute getAttribute(String sName) + { + return (Attribute) m_tblAttribute.get(sName); + } + + /** + * Add an attribute structure. + * + * @param sName the attribute name + * + * @return the new attribute + */ + public Attribute addAttribute(String sName) + { + Attribute attribute; + if (sName.equals(ATTR_LINENUMBERS)) + { + attribute = new LineNumberTableAttribute(this); + } + else if (sName.equals(ATTR_VARIABLES)) + { + attribute = new LocalVariableTableAttribute(this); + } + else if (sName.equals(ATTR_VARIABLETYPES)) + { + attribute = new LocalVariableTypeTableAttribute(this); + } + else if (sName.equals(ATTR_STACKMAPTABLE)) + { + attribute = new StackMapTableAttribute(this); + } + else if (sName.equals(ATTR_RTVISTANNOT)) + { + attribute = new RuntimeVisibleTypeAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTINVISTANNOT)) + { + attribute = new RuntimeInvisibleTypeAnnotationsAttribute(this); + } + else + { + attribute = new Attribute(this, sName); + } + + m_tblAttribute.put(attribute.getIdentity(), attribute); + m_fModified = true; + + return attribute; + } + + /** + * Remove a attribute. + * + * @param sName the attribute name + */ + public void removeAttribute(String sName) + { + if (m_tblAttribute.remove(sName) != null) + { + m_fModified = true; + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "CodeAttribute"; + + /** + * An empty byte array. + */ + private static final byte[] NO_BYTES = new byte[0]; + + /** + * An empty label array. + */ + private static final Label[] LABEL_ARRAY = new Label[0]; + + /** + * State: Initialized. + */ + private static final int CLEAR = 0; + /** + * State: The user of this class has "assembled" (added) ops. + */ + private static final int ADDED = 1; + /** + * State: This attribute has disassembled, but the binary code itself + * was not disassembled; it is in m_abCode. + */ + private static final int LOADED = 2; + /** + * State: The binary code in m_abCode was disassembled into ops. + */ + private static final int DISASSEMBLED = 3; + /** + * State: The ops in the linked list were assembled into m_abCode. + */ + private static final int ASSEMBLED = 4; + + /** + * Line number table sub-attribute. + */ + private static final UtfConstant CONST_LINENUMBERS = new UtfConstant(ATTR_LINENUMBERS); + /** + * Local variable table sub-attribute. + */ + private static final UtfConstant CONST_VARIABLES = new UtfConstant(ATTR_VARIABLES); + + /** + * State of the code attribute. + */ + private int m_nState; + + /** + * The constant pool supplied for disassembly. This is necessary for when + * the byte code must be fully disassembled. + */ + private ConstantPool m_pool; + + /** + * Tracks modifications to the code attribute. + */ + private boolean m_fModified; + + + /** + * Number of variables (as read or assembled). + */ + private int m_cVars; + + /** + * Size of stack (as read or assembled). + */ + private int m_cwStack; + + /** + * The byte code (as read or assembled). + */ + private byte[] m_abCode; + + /** + * The first op (the head of a linked list). + */ + private Op m_opFirst; + + /** + * The last op (the tail of a linked list). + * n/a for disassembled ops. + */ + private Op m_opLast; + + /** + * The current "source code line". + * n/a for disassembled ops. + */ + private int m_iLine; + + /** + * Guarded sections (each represents information about a Java "catch" or + * "finally" construct) (as read or assembled). + */ + private Vector m_vectCatch = new Vector(); + + /** + * Sub-attributes. + */ + private StringTable m_tblAttribute = new StringTable(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Constant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Constant.java new file mode 100644 index 0000000000000..b1f48437d872c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Constant.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represent a Java Virtual Machine constant. +* +* @version 0.50, 05/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class Constant extends VMStructure implements Constants, Comparable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct with a specific constant tag. + * + * @param nTag the constant tag + */ + protected Constant(int nTag) + { + m_nTag = nTag; + } + + + // ----- Constant operations -------------------------------------------- + + /** + * Based on the tag of the constant, which is encountered first in the + * stream, construct the correct constant class and disassemble it. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected static Constant loadConstant(DataInput stream, ConstantPool pool) + throws IOException + { + int nTag = stream.readUnsignedByte(); + Constant constant; + + switch (nTag) + { + case CONSTANT_UTF8: + constant = new UtfConstant(); + break; + case CONSTANT_INTEGER: + constant = new IntConstant(); + break; + case CONSTANT_FLOAT: + constant = new FloatConstant(); + break; + case CONSTANT_LONG: + constant = new LongConstant(); + break; + case CONSTANT_DOUBLE: + constant = new DoubleConstant(); + break; + case CONSTANT_CLASS: + constant = new ClassConstant(); + break; + case CONSTANT_STRING: + constant = new StringConstant(); + break; + case CONSTANT_FIELDREF: + constant = new FieldConstant(); + break; + case CONSTANT_METHODREF: + constant = new MethodConstant(); + break; + case CONSTANT_INTERFACEMETHODREF: + constant = new InterfaceConstant(); + break; + case CONSTANT_NAMEANDTYPE: + constant = new SignatureConstant(); + break; + case CONSTANT_METHODHANDLE: + ClassFile cf = pool.getClassFile(); + constant = new MethodHandleConstant( + cf != null && cf.getMajorVersion() >= 52); + break; + case CONSTANT_METHODTYPE: + constant = new MethodTypeConstant(); + break; + case CONSTANT_INVOKEDYNAMIC: + constant = new InvokeDynamicConstant(); + break; + default: + throw new IOException("Invalid constant tag " + nTag); + } + + constant.disassemble(stream, pool); + return constant; + } + + /** + * Determine the enumerated JVM constant classification. + * + * @return the JVM constant tag + */ + public final int getTag() + { + return m_nTag; + } + + /** + * Determine the number of constant pool slots used by the constant. + * + * @return the number of constant pool slots used by the constant + */ + protected final int getElementSize() + { + return CONSTANT_SIZE[m_nTag]; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected abstract void disassemble(DataInput stream, ConstantPool pool) + throws IOException; + + /** + * Resolve any referenced constants. When stored persistently, those + * constant types which refer to other constants do so by integer index; + * this method dereferences the integer index to access the referred-to + * constants directly. This method is overridden by constant types which + * reference other constants. + * + * @param pool the constant pool for the class which now contains all + * constants referenced by this constant + */ + protected void postdisassemble(ConstantPool pool) + { + } + + /** + * Register any referenced constants. This method is overridden by + * constant types which reference other constants. + * + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. The particular constant implementation should super + * to this generic implementation before assembling its own data. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeByte(m_nTag); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public abstract int compareTo(Object obj); + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public abstract String toString(); + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public abstract String format(); + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public abstract boolean equals(Object obj); + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Constant"; + + /** + * The JVM constant classification tag. + */ + private int m_nTag; + + /** + * The cached location of the constant in the pool; used as an + * optimization to avoid repeatedly searching for the same constant if + * it isn't actually moving around in the pool. + */ + protected transient int m_iLastKnownLocation; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ConstantPool.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ConstantPool.java new file mode 100644 index 0000000000000..7e34950f4e6b5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ConstantPool.java @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import com.tangosol.util.Filter; +import com.tangosol.util.FilterEnumerator; +import com.tangosol.util.NullFilter; +import com.tangosol.util.Tree; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import java.util.Enumeration; +import java.util.Vector; + + +/** +* Implements the constant pool section of a Java .class structure. +* +* @version 0.50, 05/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class ConstantPool extends VMStructure implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a new ConstantPool object associated to the provided {@link + * ClassFile}. + * + * @param cf the associated ClassFile + */ + public ConstantPool(ClassFile cf) + { + init(); + + // hold a reference to the ClassFile this Constant Pool is associated to + m_classFile = cf; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + init(); + + // read the number of constant elements (see note in the assemble() + // method) + int cConst = stream.readUnsignedShort(); + + // load the constant pool from the stream + int i = 1; + while (i < cConst) + { + Constant constant = Constant.loadConstant(stream, this); + add(constant); + // some constants take up two elements + i += constant.getElementSize(); + } + + // convert indexes into constant references + Enumeration enmr = m_vectConst.elements(); + while (enmr.hasMoreElements()) + { + Constant constant = (Constant) enmr.nextElement(); + if (constant != null) + { + constant.postdisassemble(this); + } + } + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + // register all constants + Enumeration enmr = constants(); + while (enmr.hasMoreElements()) + { + pool.registerConstant((Constant) enmr.nextElement()); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + // write out the number of constant elements (the size of the array + // used to hold the constants which is not equal to the number of + // constants because [0] is reserved and long/double constants "use" + // two elements) + stream.writeShort(m_vectConst.size()); + + // stream constants + Enumeration enmr = m_vectConst.elements(); + while (enmr.hasMoreElements()) + { + Constant constant = (Constant) enmr.nextElement(); + if (constant != null) + { + constant.assemble(stream, pool); + } + } + } + + /** + * Determine if the VM structure (or any contained VM structure) has been + * modified. + * + * @return true if the VM structure has been modified + */ + public boolean isModified() + { + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant pool. + * + * @return a string describing the constant pool + */ + public String toString() + { + return "Constant Pool contains " + m_vectConst.size() + " elements"; + } + + + // ----- ConstantPool operations ---------------------------------------- + + /** + * Register a constant in the pool (if it does not already exist). + * + * @param constant the constant to find/register + * + * @return the constant's index + */ + public int registerConstant(Constant constant) + { + if (constant == null) + { + return 0; + } + + int iConst = findConstant(constant); + if (iConst < 1) + { + // add the constant and assign an index + iConst = add(constant); + + // register any constants referenced by the constant that was + // just registered + constant.preassemble(this); + } + + return iConst; + } + + /** + * Search for a constant in the pool. + * + * @param constant the constant to search for + * + * @return the constant index or -1 if not found + */ + public int findConstant(Constant constant) + { + if (constant == null) + { + return 0; + } + + ensureLookup(); + + int iConst = constant.m_iLastKnownLocation; + if (iConst > 0) + { + Constant constantFound = getConstant(iConst); + if (constantFound != null && constant.equals(constantFound)) + { + return iConst; + } + } + + Integer index = (Integer) m_atblConst[constant.getTag()].get(constant); + if (index != null) + { + iConst = index.intValue(); + constant.m_iLastKnownLocation = iConst; + return iConst; + } + + return -1; + } + + /** + * Get a constant from the pool using the constant's index. + * + * @param iConst the constant index + * + * @return the constant or null if the specified constant does not exist + */ + public Constant getConstant(int iConst) + { + try + { + return (Constant) m_vectConst.get(iConst); + } + catch (ArrayIndexOutOfBoundsException e) + { + return null; + } + } + + /** + * Create an enumerator that is independent of the constant pool's own + * storage. This is necessary in cases where constants could be added + * while being enumerated, since a change to a vector, which is used + * to store the constants, will cause the vector's enumerator to throw + * an exception. + * + * @return an enumerator of the constants in the constant pool + */ + public Enumeration constants() + { + Constant[] aconst = new Constant[m_vectConst.size()]; + m_vectConst.copyInto(aconst); + + return new FilterEnumerator(aconst, NULL_FILTER); + } + + /** + * Reorder the contents of the pool for neatness and as a dependency + * optimization. + */ + protected void sort() + { + azzert(!isOrderImportant()); + + // get the ordered index of all registered constants + ensureLookup(); + Tree[] atblConst = m_atblConst; + + // re-build the pool in order based on the index + init(); + int[] aiTag = CONSTANT_ORDER; + int cTags = aiTag.length; + for (int i = 0; i < cTags; ++i) + { + Enumeration enmr = atblConst[aiTag[i]].keys(); + while (enmr.hasMoreElements()) + { + add((Constant) enmr.nextElement()); + } + } + } + + /** + * Determine if the current order of the pool is important. + * + * @return true if the pool should not allow itself to be re-ordered + */ + public boolean isOrderImportant() + { + return m_fOrderImportant; + } + + /** + * Specify that the current order of the pool is important or not. + * + * @param fOrderImportant true if the pool should not allow itself to + * be re-ordered + */ + public void setOrderImportant(boolean fOrderImportant) + { + m_fOrderImportant = fOrderImportant; + } + + /** + * Return the {@link ClassFile} this ConstantPool is associated to or null + * if this pool has become detached from a ClassFile. + * + * @return the ClassFile this ConstantPool is associated to or null + */ + public ClassFile getClassFile() + { + return m_classFile; + } + + /** + * Set the {@link ClassFile} this ConstantPool is associated to. + * + * @param classFile the ClassFile this ConstantPool is associated to + */ + protected void setClassFile(ClassFile classFile) + { + m_classFile = classFile; + } + + // ----- internal operations -------------------------------------------- + + /** + * (Internal) Initialize the constant pool information. + */ + protected void init() + { + // create constant pool (accessed by index) + m_vectConst = new Vector(); + m_vectConst.add(null); // constant 0 always n/a + + // reset lookup by constant + m_atblConst = null; + + // default order un-important (only unknown attributes make it + // important; see Attribute.loadAttribute) + m_fOrderImportant = false; + } + + /** + * (Internal) Add a constant to the constant pool. + * + * @param constant the constant to add top the pool + */ + protected int add(Constant constant) + { + // determine the constant index + int iConst = m_vectConst.size(); + + // append to constant pool + m_vectConst.add(constant); + if (constant.getElementSize() > 1) + { + // some constants take two slots + m_vectConst.add(null); + } + + // update lookup + if (m_atblConst != null) + { + m_atblConst[constant.getTag()].put(constant, Integer.valueOf(iConst)); + } + + m_fModified = true; + return iConst; + } + + /** + * (Internal) Initialize the constant pool lookup if it does not already + * exist. + */ + protected void ensureLookup() + { + if (m_atblConst == null) + { + // create lookup tables for constants + int[] acElements = CONSTANT_SIZE; + int cTags = acElements.length; + Tree[] atblConst = new Tree[cTags]; + for (int i = 0; i < cTags; ++i) + { + if (acElements[i] > 0) + { + atblConst[i] = new Tree(); + } + } + + // populate + Enumeration enmr = m_vectConst.elements(); + for (int i = 0; enmr.hasMoreElements(); ++i) + { + Constant constant = (Constant) enmr.nextElement(); + if (constant != null) + { + atblConst[constant.getTag()].put(constant, Integer.valueOf(i)); + } + } + + m_atblConst = atblConst; + } + } + + // ----- constants ------------------------------------------------------ + + /** + * Filter for hiding missing constants when enumerating. + */ + private static final Filter NULL_FILTER = NullFilter.getInstance(); + + // ----- data members --------------------------------------------------- + + /** + * The constant pool, an array of constants accessed via integer index. + */ + private Vector m_vectConst; + + /** + * Keeps track of modifications to the constant pool. + */ + private boolean m_fModified; + + /** + * Keeps track of whether the constant pool order is important. + */ + private boolean m_fOrderImportant; + + /** + * An array (indexed by constant type "tag") of ordered (aka searchable) + * constants in this constant pool. + */ + private Tree[] m_atblConst; + + /** + * The ClassFile this Constant Pool is associated to. + */ + private ClassFile m_classFile; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ConstantValueAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ConstantValueAttribute.java new file mode 100644 index 0000000000000..c1435d9a006d2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ConstantValueAttribute.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a Java Virtual Machine "ConstantValue" Attribute which specifies +* the file name from which the Java .class was compiled. +* +* @version 0.50, 05/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class ConstantValueAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a constant value attribute for a field. + * + * @param context the JVM structure containing the attribute + */ + protected ConstantValueAttribute(VMStructure context) + { + super(context, ATTR_CONSTANT); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + m_constant = pool.getConstant(stream.readUnsignedShort()); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + pool.registerConstant(m_constant); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(2); + stream.writeShort(pool.findConstant(m_constant)); + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + * + * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return super.getName() + '=' + m_constant.toString(); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + ConstantValueAttribute that = (ConstantValueAttribute) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_constant.equals(that.m_constant); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the constant value. + * + * @return the constant + */ + public Constant getConstant() + { + return m_constant; + } + + /** + * Set the constant value. + * + * @param constant + */ + public void setConstant(Constant constant) + { + m_constant = constant; + m_fModified = true; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "ConstantValueAttribute"; + + /** + * The constant value. + */ + private Constant m_constant; + + /** + * Has the attribute been modified? + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Constants.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Constants.java new file mode 100644 index 0000000000000..fb47836b3d42d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Constants.java @@ -0,0 +1,1326 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.assembler; + + + +/** +* Constants for the assembler package. +*/ +public interface Constants + { + /** + * The default super class name. + */ + public static final String DEFAULT_SUPER = "java/lang/Object"; + + /** + * Value used internally by the assembler to denote an unknown value. + */ + public static final int UNKNOWN = Integer.MIN_VALUE; + + + // ----- access flags ----------------------------------------------- + + /** + * Interface (vs. Class). + */ + public static final int ACC_INTERFACE = 0x0200; + /** + * Accessibility: Public. + */ + public static final int ACC_PUBLIC = 0x0001; + /** + * Accessibility: Protected. + */ + public static final int ACC_PROTECTED = 0x0004; + /** + * Accessibility: Package Private. + * (The absence of ACC_PUBLIC, ACC_PROTECTED, and ACC_PRIVATE.) + */ + public static final int ACC_PACKAGE = 0x0000; + /** + * Accessibility: Private. + */ + public static final int ACC_PRIVATE = 0x0002; + /** + * Abstract (vs. concrete). + */ + public static final int ACC_ABSTRACT = 0x0400; + /** + * Declared synthetic; not present in source code. + */ + public static final int ACC_SYNTHETIC = 0x1000; + /** + * Declared as an annotation type. + */ + public static final int ACC_ANNOTATION = 0x2000; + /** + * Declared as an enum type. + */ + public static final int ACC_ENUM = 0x4000; + /** + * Static (vs. instance). + */ + public static final int ACC_STATIC = 0x0008; + /** + * Final (vs. derivable). + */ + public static final int ACC_FINAL = 0x0010; + /** + * Synchronized (also "ACC_SUPER" in later JVM specifications). + */ + public static final int ACC_SYNCHRONIZED = 0x0020; + /** + * Native (vs. pure). + */ + public static final int ACC_NATIVE = 0x0100; + /** + * Volatile. + */ + public static final int ACC_VOLATILE = 0x0040; + /** + * Transient (vs. persistent) + */ + public static final int ACC_TRANSIENT = 0x0080; + /** + * A bridge method, generated by the compiler. + */ + public static final int ACC_BRIDGE = 0x0040; + /** + * Declared with a variable number of arguments + */ + public static final int ACC_VARARGS = 0x0080; + /** + * Declared strictfp; floating-point mode is strict. + */ + public static final int ACC_STRICT = 0x0800; + + /** + * The accessibility bits. + */ + public static final int ACC_ACCESS_MASK = ACC_PUBLIC | + ACC_PRIVATE | + ACC_PROTECTED; + + /** + * All applicable bits. + */ + public static final int ACC_ALL = ACC_PUBLIC | + ACC_PRIVATE | + ACC_PROTECTED | + ACC_STATIC | + ACC_FINAL | + ACC_SYNCHRONIZED | + ACC_VOLATILE | + ACC_TRANSIENT | + ACC_NATIVE | + ACC_INTERFACE | + ACC_ABSTRACT; + + // ----- java .class header --------------------------------------------- + + /** + * The special sequence of bytes that identifies a .class structure. + */ + public static final int CLASS_COOKIE = 0xCAFEBABE; + + /** + * The expected major version of the .class structure. + */ + public static final int VERSION_MAJOR = 45; + /** + * The expected minor version of the .class structure. + */ + public static final int VERSION_MINOR = 3; + + /** + * The minimum supported major version of the class structure. + */ + public static final int VERSION_MAJOR_MIN = 45; + /** + * The minimum supported minor version of the class structure. + */ + public static final int VERSION_MINOR_MIN = 0; + /** + * The maximum supported major version of the class structure. + */ + public static final int VERSION_MAJOR_MAX = 52; + /** + * The maximum supported minor version of the class structure. + */ + public static final int VERSION_MINOR_MAX = 0; + + + // ----- java .class constant pool -------------------------------------- + + /** + * Constant pool: Class name. + */ + public static final int CONSTANT_CLASS = 7; + /** + * Constant pool: Specification for a field of an object/class/interface. + */ + public static final int CONSTANT_FIELDREF = 9; + /** + * Constant pool: Specification for a method invoked against a class + * type. + */ + public static final int CONSTANT_METHODREF = 10; + /** + * Constant pool: Specification for a method invoked against an interface + * type. + */ + public static final int CONSTANT_INTERFACEMETHODREF = 11; + /** + * Constant pool: A constant string referenced from Java byte-code + * (instructions LDC and LDC_W). + */ + public static final int CONSTANT_STRING = 8; + /** + * Constant pool: 4-byte integer. + */ + public static final int CONSTANT_INTEGER = 3; + /** + * Constant pool: 4-byte single-precision floating point number. + */ + public static final int CONSTANT_FLOAT = 4; + /** + * Constant pool: 8-byte integer. + */ + public static final int CONSTANT_LONG = 5; + /** + * Constant pool: 8-byte double-precision floating point number. + */ + public static final int CONSTANT_DOUBLE = 6; + /** + * Constant pool: Specify the name and type/signature of a field/method + * (used by *ref constant types). + */ + public static final int CONSTANT_NAMEANDTYPE = 12; + /** + * Constant pool: UTF-8 encoded string value. + */ + public static final int CONSTANT_UTF8 = 1; + /** + * Constant pool: A MethodHandle used to reference a field/method, + * commonly used by invokedynamic op. + */ + public static final int CONSTANT_METHODHANDLE = 15; + /** + * Constant pool: Provides a descriptor for a method used by + * BootstrapMethods. + */ + public static final int CONSTANT_METHODTYPE = 16; + /** + * Constant pool: Used by the invokedynamic instruction. + */ + public static final int CONSTANT_INVOKEDYNAMIC = 18; + /** + * Constant pool: Not legal in this version of the JVM. + */ + public static final int CONSTANT_UNICODE = 2; + /** + * The number of constant pool elements used by the constant, indexed by + * JVM constant classification tag. + */ + public static final int[] CONSTANT_SIZE = {0,1,0,1,1,2,2,1,1,1,1,1,1,0,0,1,1,0,1}; + /** + * The desired order (in an assembled constant pool) of the constant + * types. + */ + public static final int[] CONSTANT_ORDER = + { + CONSTANT_INTEGER, + CONSTANT_LONG, + CONSTANT_FLOAT, + CONSTANT_DOUBLE, + CONSTANT_UTF8, + CONSTANT_STRING, + CONSTANT_CLASS, + CONSTANT_NAMEANDTYPE, + CONSTANT_FIELDREF, + CONSTANT_METHODREF, + CONSTANT_INTERFACEMETHODREF, + CONSTANT_METHODHANDLE, + CONSTANT_METHODTYPE, + CONSTANT_INVOKEDYNAMIC + }; + + + // ----- java .class constants implied by byte codes -------------------- + + /** + * ICONST_M1 implies an IntConstant of -1. + */ + public static final IntConstant CONSTANT_ICONST_M1 = new IntConstant(-1); + /** + * ICONST_0 implies an IntConstant of 0. + */ + public static final IntConstant CONSTANT_ICONST_0 = new IntConstant(0); + /** + * ICONST_1 implies an IntConstant of 1. + */ + public static final IntConstant CONSTANT_ICONST_1 = new IntConstant(1); + /** + * ICONST_2 implies an IntConstant of 2. + */ + public static final IntConstant CONSTANT_ICONST_2 = new IntConstant(2); + /** + * ICONST_3 implies an IntConstant of 3. + */ + public static final IntConstant CONSTANT_ICONST_3 = new IntConstant(3); + /** + * ICONST_4 implies an IntConstant of 4. + */ + public static final IntConstant CONSTANT_ICONST_4 = new IntConstant(4); + /** + * ICONST_5 implies an IntConstant of 5. + */ + public static final IntConstant CONSTANT_ICONST_5 = new IntConstant(5); + + /** + * LCONST_0 implies an LongConstant of 0. + */ + public static final LongConstant CONSTANT_LCONST_0 = new LongConstant(0L); + /** + * LCONST_1 implies an LongConstant of 1. + */ + public static final LongConstant CONSTANT_LCONST_1 = new LongConstant(1L); + + /** + * FCONST_0 implies an FloatConstant of 0. + */ + public static final FloatConstant CONSTANT_FCONST_0 = new FloatConstant(0.0F); + /** + * FCONST_1 implies an FloatConstant of 1. + */ + public static final FloatConstant CONSTANT_FCONST_1 = new FloatConstant(1.0F); + /** + * FCONST_2 implies an FloatConstant of 2. + */ + public static final FloatConstant CONSTANT_FCONST_2 = new FloatConstant(2.0F); + + /** + * DCONST_0 implies an DoubleConstant of 0. + */ + public static final DoubleConstant CONSTANT_DCONST_0 = new DoubleConstant(0.0); + /** + * DCONST_1 implies an DoubleConstant of 1. + */ + public static final DoubleConstant CONSTANT_DCONST_1 = new DoubleConstant(1.0); + + + // ----- java .class methods -------------------------------------------- + + /** + * JVM method name for a constructor. + */ + public static final String CONSTRUCTOR_NAME = ""; + + /** + * JVM method name for the static initializer. + */ + public static final String INITIALIZER_NAME = ""; + + /** + * JVM method signature for the static initializer. + */ + public static final String INITIALIZER_SIG = "()V"; + + + // ----- java .class attributes ----------------------------------------- + + /** + * Attribute: Source file name. + */ + public static final String ATTR_FILENAME = "SourceFile"; + + /** + * Attribute: Deprecated Class/Field/Method. + */ + public static final String ATTR_DEPRECATED = "Deprecated"; + + /** + * Attribute: Synthetic Class/Field/Method. + */ + public static final String ATTR_SYNTHETIC = "Synthetic"; + + /** + * Attribute: Inner class information. + */ + public static final String ATTR_INNERCLASSES = "InnerClasses"; + + /** + * Attribute: Constant Field. + */ + public static final String ATTR_CONSTANT = "ConstantValue"; + + /** + * Attribute: Method declared Exceptions. + */ + public static final String ATTR_EXCEPTIONS = "Exceptions"; + + /** + * Attribute: Method byte code. + */ + public static final String ATTR_CODE = "Code"; + + /** + * Attribute: Byte-code pc to source-code line number table. + */ + public static final String ATTR_LINENUMBERS = "LineNumberTable"; + + /** + * Attribute: Byte-code variable index to source-code variable name + * table. + */ + public static final String ATTR_VARIABLES = "LocalVariableTable"; + + /** + * Attribute: Byte-code variable index to source-code variable type + */ + public static final String ATTR_VARIABLETYPES = "LocalVariableTypeTable"; + + /** + * Attribute: Additional type-signature information. + */ + public static final String ATTR_SIGNATURE = "Signature"; + + /** + * Attribute: Default value for a method annotation. + */ + public static final String ATTR_ANNOTDEFAULT = "AnnotationDefault"; + + /** + * Attribute: Enclosing method of a local or anonymous inner class. + */ + public static final String ATTR_ENCMETHOD = "EnclosingMethod"; + + /** + * Attribute: Runtime-visible class/field/method annotations. + */ + public static final String ATTR_RTVISANNOT = "RuntimeVisibleAnnotations"; + + /** + * Attribute: Runtime-invisible class/field/method annotations. + */ + public static final String ATTR_RTINVISANNOT = "RuntimeInvisibleAnnotations"; + + /** + * Attribute: Runtime-visible method parameter annotations. + */ + public static final String ATTR_RTVISPARAMANNOT = "RuntimeVisibleParameterAnnotations"; + + /** + * Attribute: Runtime-invisible method parameter annotations. + */ + public static final String ATTR_RTINVISPARAMANNOT = "RuntimeInvisibleParameterAnnotations"; + + /** + * Attribute: Type verification Code attribute: {@value} + */ + public static final String ATTR_STACKMAPTABLE = "StackMapTable"; + + /** + * Attribute: Bootstrap Method definitions used by invokedynamic instructions + * applicable to class attributes only. + */ + public static final String ATTR_BOOTSTRAPMETHODS = "BootstrapMethods"; + + /** + * Attribute: Runtime-visible-type class/field/method/code annotations. + */ + public static final String ATTR_RTVISTANNOT = "RuntimeVisibleTypeAnnotations"; + + /** + * Attribute: Runtime-invisible-type class/field/method/code annotations. + */ + public static final String ATTR_RTINVISTANNOT = "RuntimeInvisibleTypeAnnotations"; + + /** + * Attribute: Method Parameters applicable to method attributes. + */ + public static final String ATTR_METHODPARAMS = "MethodParameters"; + + // ----- .java byte codes ----------------------------------------------- + + /** + * Byte Codes (also JASM ops except when specified): Enumerated by value. + */ + public static final int NOP = 0x00; + public static final int ACONST_NULL = 0x01; // not JASM op + public static final int ICONST_M1 = 0x02; // not JASM op + public static final int ICONST_0 = 0x03; // not JASM op + public static final int ICONST_1 = 0x04; // not JASM op + public static final int ICONST_2 = 0x05; // not JASM op + public static final int ICONST_3 = 0x06; // not JASM op + public static final int ICONST_4 = 0x07; // not JASM op + public static final int ICONST_5 = 0x08; // not JASM op + public static final int LCONST_0 = 0x09; // not JASM op + public static final int LCONST_1 = 0x0a; // not JASM op + public static final int FCONST_0 = 0x0b; // not JASM op + public static final int FCONST_1 = 0x0c; // not JASM op + public static final int FCONST_2 = 0x0d; // not JASM op + public static final int DCONST_0 = 0x0e; // not JASM op + public static final int DCONST_1 = 0x0f; // not JASM op + public static final int BIPUSH = 0x10; // not JASM op + public static final int SIPUSH = 0x11; // not JASM op + public static final int LDC = 0x12; + public static final int LDC_W = 0x13; // not JASM op + public static final int LDC2_W = 0x14; // not JASM op + public static final int ILOAD = 0x15; + public static final int LLOAD = 0x16; + public static final int FLOAD = 0x17; + public static final int DLOAD = 0x18; + public static final int ALOAD = 0x19; + public static final int ILOAD_0 = 0x1a; // not JASM op + public static final int ILOAD_1 = 0x1b; // not JASM op + public static final int ILOAD_2 = 0x1c; // not JASM op + public static final int ILOAD_3 = 0x1d; // not JASM op + public static final int LLOAD_0 = 0x1e; // not JASM op + public static final int LLOAD_1 = 0x1f; // not JASM op + public static final int LLOAD_2 = 0x20; // not JASM op + public static final int LLOAD_3 = 0x21; // not JASM op + public static final int FLOAD_0 = 0x22; // not JASM op + public static final int FLOAD_1 = 0x23; // not JASM op + public static final int FLOAD_2 = 0x24; // not JASM op + public static final int FLOAD_3 = 0x25; // not JASM op + public static final int DLOAD_0 = 0x26; // not JASM op + public static final int DLOAD_1 = 0x27; // not JASM op + public static final int DLOAD_2 = 0x28; // not JASM op + public static final int DLOAD_3 = 0x29; // not JASM op + public static final int ALOAD_0 = 0x2a; // not JASM op + public static final int ALOAD_1 = 0x2b; // not JASM op + public static final int ALOAD_2 = 0x2c; // not JASM op + public static final int ALOAD_3 = 0x2d; // not JASM op + public static final int IALOAD = 0x2e; + public static final int LALOAD = 0x2f; + public static final int FALOAD = 0x30; + public static final int DALOAD = 0x31; + public static final int AALOAD = 0x32; + public static final int BALOAD = 0x33; + public static final int CALOAD = 0x34; + public static final int SALOAD = 0x35; + public static final int ISTORE = 0x36; + public static final int LSTORE = 0x37; + public static final int FSTORE = 0x38; + public static final int DSTORE = 0x39; + public static final int ASTORE = 0x3a; + public static final int ISTORE_0 = 0x3b; // not JASM op + public static final int ISTORE_1 = 0x3c; // not JASM op + public static final int ISTORE_2 = 0x3d; // not JASM op + public static final int ISTORE_3 = 0x3e; // not JASM op + public static final int LSTORE_0 = 0x3f; // not JASM op + public static final int LSTORE_1 = 0x40; // not JASM op + public static final int LSTORE_2 = 0x41; // not JASM op + public static final int LSTORE_3 = 0x42; // not JASM op + public static final int FSTORE_0 = 0x43; // not JASM op + public static final int FSTORE_1 = 0x44; // not JASM op + public static final int FSTORE_2 = 0x45; // not JASM op + public static final int FSTORE_3 = 0x46; // not JASM op + public static final int DSTORE_0 = 0x47; // not JASM op + public static final int DSTORE_1 = 0x48; // not JASM op + public static final int DSTORE_2 = 0x49; // not JASM op + public static final int DSTORE_3 = 0x4a; // not JASM op + public static final int ASTORE_0 = 0x4b; // not JASM op + public static final int ASTORE_1 = 0x4c; // not JASM op + public static final int ASTORE_2 = 0x4d; // not JASM op + public static final int ASTORE_3 = 0x4e; // not JASM op + public static final int IASTORE = 0x4f; + public static final int LASTORE = 0x50; + public static final int FASTORE = 0x51; + public static final int DASTORE = 0x52; + public static final int AASTORE = 0x53; + public static final int BASTORE = 0x54; + public static final int CASTORE = 0x55; + public static final int SASTORE = 0x56; + public static final int POP = 0x57; + public static final int POP2 = 0x58; + public static final int DUP = 0x59; + public static final int DUP_X1 = 0x5a; + public static final int DUP_X2 = 0x5b; + public static final int DUP2 = 0x5c; + public static final int DUP2_X1 = 0x5d; + public static final int DUP2_X2 = 0x5e; + public static final int SWAP = 0x5f; + public static final int IADD = 0x60; + public static final int LADD = 0x61; + public static final int FADD = 0x62; + public static final int DADD = 0x63; + public static final int ISUB = 0x64; + public static final int LSUB = 0x65; + public static final int FSUB = 0x66; + public static final int DSUB = 0x67; + public static final int IMUL = 0x68; + public static final int LMUL = 0x69; + public static final int FMUL = 0x6a; + public static final int DMUL = 0x6b; + public static final int IDIV = 0x6c; + public static final int LDIV = 0x6d; + public static final int FDIV = 0x6e; + public static final int DDIV = 0x6f; + public static final int IREM = 0x70; + public static final int LREM = 0x71; + public static final int FREM = 0x72; + public static final int DREM = 0x73; + public static final int INEG = 0x74; + public static final int LNEG = 0x75; + public static final int FNEG = 0x76; + public static final int DNEG = 0x77; + public static final int ISHL = 0x78; + public static final int LSHL = 0x79; + public static final int ISHR = 0x7a; + public static final int LSHR = 0x7b; + public static final int IUSHR = 0x7c; + public static final int LUSHR = 0x7d; + public static final int IAND = 0x7e; + public static final int LAND = 0x7f; + public static final int IOR = 0x80; + public static final int LOR = 0x81; + public static final int IXOR = 0x82; + public static final int LXOR = 0x83; + public static final int IINC = 0x84; + public static final int I2L = 0x85; + public static final int I2F = 0x86; + public static final int I2D = 0x87; + public static final int L2I = 0x88; + public static final int L2F = 0x89; + public static final int L2D = 0x8a; + public static final int F2I = 0x8b; + public static final int F2L = 0x8c; + public static final int F2D = 0x8d; + public static final int D2I = 0x8e; + public static final int D2L = 0x8f; + public static final int D2F = 0x90; + public static final int I2B = 0x91; + public static final int I2C = 0x92; + public static final int I2S = 0x93; + public static final int LCMP = 0x94; + public static final int FCMPL = 0x95; + public static final int FCMPG = 0x96; + public static final int DCMPL = 0x97; + public static final int DCMPG = 0x98; + public static final int IFEQ = 0x99; + public static final int IFNE = 0x9a; + public static final int IFLT = 0x9b; + public static final int IFGE = 0x9c; + public static final int IFGT = 0x9d; + public static final int IFLE = 0x9e; + public static final int IF_ICMPEQ = 0x9f; + public static final int IF_ICMPNE = 0xa0; + public static final int IF_ICMPLT = 0xa1; + public static final int IF_ICMPGE = 0xa2; + public static final int IF_ICMPGT = 0xa3; + public static final int IF_ICMPLE = 0xa4; + public static final int IF_ACMPEQ = 0xa5; + public static final int IF_ACMPNE = 0xa6; + public static final int GOTO = 0xa7; + public static final int JSR = 0xa8; + public static final int RET = 0xa9; + public static final int TABLESWITCH = 0xaa; + public static final int LOOKUPSWITCH = 0xab; + public static final int IRETURN = 0xac; + public static final int LRETURN = 0xad; + public static final int FRETURN = 0xae; + public static final int DRETURN = 0xaf; + public static final int ARETURN = 0xb0; + public static final int RETURN = 0xb1; + public static final int GETSTATIC = 0xb2; + public static final int PUTSTATIC = 0xb3; + public static final int GETFIELD = 0xb4; + public static final int PUTFIELD = 0xb5; + public static final int INVOKEVIRTUAL = 0xb6; + public static final int INVOKESPECIAL = 0xb7; + public static final int INVOKESTATIC = 0xb8; + public static final int INVOKEINTERFACE = 0xb9; + public static final int INVOKEDYNAMIC = 0xba; + public static final int NEW = 0xbb; + public static final int NEWARRAY = 0xbc; // not JASM op + public static final int ANEWARRAY = 0xbd; + public static final int ARRAYLENGTH = 0xbe; + public static final int ATHROW = 0xbf; + public static final int CHECKCAST = 0xc0; + public static final int INSTANCEOF = 0xc1; + public static final int MONITORENTER = 0xc2; + public static final int MONITOREXIT = 0xc3; + public static final int WIDE = 0xc4; // not JASM op + public static final int MULTIANEWARRAY = 0xc5; + public static final int IFNULL = 0xc6; + public static final int IFNONNULL = 0xc7; + public static final int GOTO_W = 0xc8; // not JASM op + public static final int JSR_W = 0xc9; // not JASM op + + + /** + * JASM Instruction: Push an integer (single word) constant. + */ + public static final int ICONST = 0xe5; + + /** + * JASM Instruction: Push a long integer (double word) constant. + */ + public static final int LCONST = 0xe6; + + /** + * JASM Instruction: Push a single-precision floating point (single + * word) constant. + */ + public static final int FCONST = 0xe7; + + /** + * JASM Instruction: Push a double-precision floating point (double + * word) constant. + */ + public static final int DCONST = 0xe8; + + /** + * JASM Instruction: Push a reference (single word) constant. + */ + public static final int ACONST = 0xe9; + + /** + * JASM Instruction: Begin scope. + */ + public static final int BEGIN = 0xea; + + /** + * JASM Instruction: Close scope. + */ + public static final int END = 0xeb; + + /** + * JASM Instruction: Declare integer (single word) variable. + */ + public static final int IVAR = 0xec; + + /** + * JASM Instruction: Declare long integer (double word) variable. + */ + public static final int LVAR = 0xed; + + /** + * JASM Instruction: Declare single-precision floating point (single + * word) variable. + */ + public static final int FVAR = 0xee; + + /** + * JASM Instruction: Declare double-precision floating point (double + * word) variable. + */ + public static final int DVAR = 0xef; + + /** + * JASM Instruction: Declare reference (single word) variable. + */ + public static final int AVAR = 0xf0; + + /** + * JASM Instruction: Return address (single word) variable. + */ + public static final int RVAR = 0xf1; + + /** + * JASM Instruction: Store return address variable. + */ + public static final int RSTORE = 0xf2; + + /** + * JASM Instruction: Allocate boolean array. + */ + public static final int ZNEWARRAY = 0xf3; + + /** + * JASM Instruction: Allocate byte array. + */ + public static final int BNEWARRAY = 0xf4; + + /** + * JASM Instruction: Allocate short array. + */ + public static final int CNEWARRAY = 0xf5; + + /** + * JASM Instruction: Allocate char array. + */ + public static final int SNEWARRAY = 0xf6; + + /** + * JASM Instruction: Allocate integer array. + */ + public static final int INEWARRAY = 0xf7; + + /** + * JASM Instruction: Allocate long integer array. + */ + public static final int LNEWARRAY = 0xf8; + + /** + * JASM Instruction: Allocate single-precision float array. + */ + public static final int FNEWARRAY = 0xf9; + + /** + * JASM Instruction: Allocate double-precision float array. + */ + public static final int DNEWARRAY = 0xfa; + + /** + * JASM Instruction: JumpTarget. + */ + public static final int LABEL = 0xfb; + + /** + * JASM Instruction: Switch (assembles to either TABLESWITCH or + * LOOKUPSWITCH). + */ + public static final int SWITCH = 0xfc; + + /** + * JASM Instruction: Switch case (for SWITCH, TABLESWITCH, and + * LOOKUPSWITCH). + */ + public static final int CASE = 0xfd; + + /** + * JASM Instruction: Try. + */ + public static final int TRY = 0xfe; + + /** + * JASM Instruction: Catch. + */ + public static final int CATCH = 0xff; + + /** + * Byte Codes and JASM ops: Name by value. Null for non-existent. + */ + public static final String[] OPNAME = + { + /* [0x00] = */ "nop", + /* [0x01] = */ "aconst_null", + /* [0x02] = */ "iconst_m1", + /* [0x03] = */ "iconst_0", + /* [0x04] = */ "iconst_1", + /* [0x05] = */ "iconst_2", + /* [0x06] = */ "iconst_3", + /* [0x07] = */ "iconst_4", + /* [0x08] = */ "iconst_5", + /* [0x09] = */ "lconst_0", + /* [0x0a] = */ "lconst_1", + /* [0x0b] = */ "fconst_0", + /* [0x0c] = */ "fconst_1", + /* [0x0d] = */ "fconst_2", + /* [0x0e] = */ "dconst_0", + /* [0x0f] = */ "dconst_1", + /* [0x10] = */ "bipush", + /* [0x11] = */ "sipush", + /* [0x12] = */ "ldc", + /* [0x13] = */ "ldc_w", + /* [0x14] = */ "ldc2_w", + /* [0x15] = */ "iload", + /* [0x16] = */ "lload", + /* [0x17] = */ "fload", + /* [0x18] = */ "dload", + /* [0x19] = */ "aload", + /* [0x1a] = */ "iload_0", + /* [0x1b] = */ "iload_1", + /* [0x1c] = */ "iload_2", + /* [0x1d] = */ "iload_3", + /* [0x1e] = */ "lload_0", + /* [0x1f] = */ "lload_1", + /* [0x20] = */ "lload_2", + /* [0x21] = */ "lload_3", + /* [0x22] = */ "fload_0", + /* [0x23] = */ "fload_1", + /* [0x24] = */ "fload_2", + /* [0x25] = */ "fload_3", + /* [0x26] = */ "dload_0", + /* [0x27] = */ "dload_1", + /* [0x28] = */ "dload_2", + /* [0x29] = */ "dload_3", + /* [0x2a] = */ "aload_0", + /* [0x2b] = */ "aload_1", + /* [0x2c] = */ "aload_2", + /* [0x2d] = */ "aload_3", + /* [0x2e] = */ "iaload", + /* [0x2f] = */ "laload", + /* [0x30] = */ "faload", + /* [0x31] = */ "daload", + /* [0x32] = */ "aaload", + /* [0x33] = */ "baload", + /* [0x34] = */ "caload", + /* [0x35] = */ "saload", + /* [0x36] = */ "istore", + /* [0x37] = */ "lstore", + /* [0x38] = */ "fstore", + /* [0x39] = */ "dstore", + /* [0x3a] = */ "astore", + /* [0x3b] = */ "istore_0", + /* [0x3c] = */ "istore_1", + /* [0x3d] = */ "istore_2", + /* [0x3e] = */ "istore_3", + /* [0x3f] = */ "lstore_0", + /* [0x40] = */ "lstore_1", + /* [0x41] = */ "lstore_2", + /* [0x42] = */ "lstore_3", + /* [0x43] = */ "fstore_0", + /* [0x44] = */ "fstore_1", + /* [0x45] = */ "fstore_2", + /* [0x46] = */ "fstore_3", + /* [0x47] = */ "dstore_0", + /* [0x48] = */ "dstore_1", + /* [0x49] = */ "dstore_2", + /* [0x4a] = */ "dstore_3", + /* [0x4b] = */ "astore_0", + /* [0x4c] = */ "astore_1", + /* [0x4d] = */ "astore_2", + /* [0x4e] = */ "astore_3", + /* [0x4f] = */ "iastore", + /* [0x50] = */ "lastore", + /* [0x51] = */ "fastore", + /* [0x52] = */ "dastore", + /* [0x53] = */ "aastore", + /* [0x54] = */ "bastore", + /* [0x55] = */ "castore", + /* [0x56] = */ "sastore", + /* [0x57] = */ "pop", + /* [0x58] = */ "pop2", + /* [0x59] = */ "dup", + /* [0x5a] = */ "dup_x1", + /* [0x5b] = */ "dup_x2", + /* [0x5c] = */ "dup2", + /* [0x5d] = */ "dup2_x1", + /* [0x5e] = */ "dup2_x2", + /* [0x5f] = */ "swap", + /* [0x60] = */ "iadd", + /* [0x61] = */ "ladd", + /* [0x62] = */ "fadd", + /* [0x63] = */ "dadd", + /* [0x64] = */ "isub", + /* [0x65] = */ "lsub", + /* [0x66] = */ "fsub", + /* [0x67] = */ "dsub", + /* [0x68] = */ "imul", + /* [0x69] = */ "lmul", + /* [0x6a] = */ "fmul", + /* [0x6b] = */ "dmul", + /* [0x6c] = */ "idiv", + /* [0x6d] = */ "ldiv", + /* [0x6e] = */ "fdiv", + /* [0x6f] = */ "ddiv", + /* [0x70] = */ "irem", + /* [0x71] = */ "lrem", + /* [0x72] = */ "frem", + /* [0x73] = */ "drem", + /* [0x74] = */ "ineg", + /* [0x75] = */ "lneg", + /* [0x76] = */ "fneg", + /* [0x77] = */ "dneg", + /* [0x78] = */ "ishl", + /* [0x79] = */ "lshl", + /* [0x7a] = */ "ishr", + /* [0x7b] = */ "lshr", + /* [0x7c] = */ "iushr", + /* [0x7d] = */ "lushr", + /* [0x7e] = */ "iand", + /* [0x7f] = */ "land", + /* [0x80] = */ "ior", + /* [0x81] = */ "lor", + /* [0x82] = */ "ixor", + /* [0x83] = */ "lxor", + /* [0x84] = */ "iinc", + /* [0x85] = */ "i2l", + /* [0x86] = */ "i2f", + /* [0x87] = */ "i2d", + /* [0x88] = */ "l2i", + /* [0x89] = */ "l2f", + /* [0x8a] = */ "l2d", + /* [0x8b] = */ "f2i", + /* [0x8c] = */ "f2l", + /* [0x8d] = */ "f2d", + /* [0x8e] = */ "d2i", + /* [0x8f] = */ "d2l", + /* [0x90] = */ "d2f", + /* [0x91] = */ "i2b", + /* [0x92] = */ "i2c", + /* [0x93] = */ "i2s", + /* [0x94] = */ "lcmp", + /* [0x95] = */ "fcmpl", + /* [0x96] = */ "fcmpg", + /* [0x97] = */ "dcmpl", + /* [0x98] = */ "dcmpg", + /* [0x99] = */ "ifeq", + /* [0x9a] = */ "ifne", + /* [0x9b] = */ "iflt", + /* [0x9c] = */ "ifge", + /* [0x9d] = */ "ifgt", + /* [0x9e] = */ "ifle", + /* [0x9f] = */ "if_icmpeq", + /* [0xa0] = */ "if_icmpne", + /* [0xa1] = */ "if_icmplt", + /* [0xa2] = */ "if_icmpge", + /* [0xa3] = */ "if_icmpgt", + /* [0xa4] = */ "if_icmple", + /* [0xa5] = */ "if_acmpeq", + /* [0xa6] = */ "if_acmpne", + /* [0xa7] = */ "goto", + /* [0xa8] = */ "jsr", + /* [0xa9] = */ "ret", + /* [0xaa] = */ "tableswitch", + /* [0xab] = */ "lookupswitch", + /* [0xac] = */ "ireturn", + /* [0xad] = */ "lreturn", + /* [0xae] = */ "freturn", + /* [0xaf] = */ "dreturn", + /* [0xb0] = */ "areturn", + /* [0xb1] = */ "return", + /* [0xb2] = */ "getstatic", + /* [0xb3] = */ "putstatic", + /* [0xb4] = */ "getfield", + /* [0xb5] = */ "putfield", + /* [0xb6] = */ "invokevirtual", + /* [0xb7] = */ "invokespecial", + /* [0xb8] = */ "invokestatic", + /* [0xb9] = */ "invokeinterface", + /* [0xba] = */ "invokedynamic", + /* [0xbb] = */ "new", + /* [0xbc] = */ "newarray", + /* [0xbd] = */ "anewarray", + /* [0xbe] = */ "arraylength", + /* [0xbf] = */ "athrow", + /* [0xc0] = */ "checkcast", + /* [0xc1] = */ "instanceof", + /* [0xc2] = */ "monitorenter", + /* [0xc3] = */ "monitorexit", + /* [0xc4] = */ "wide", + /* [0xc5] = */ "multianewarray", + /* [0xc6] = */ "ifnull", + /* [0xc7] = */ "ifnonnull", + /* [0xc8] = */ "goto_w", + /* [0xc9] = */ "jsr_w", + /* [0xca] = */ null, + /* [0xcb] = */ null, + /* [0xcc] = */ null, + /* [0xcd] = */ null, + /* [0xce] = */ null, + /* [0xcf] = */ null, + /* [0xd0] = */ null, + /* [0xd1] = */ null, + /* [0xd2] = */ null, + /* [0xd3] = */ null, + /* [0xd4] = */ null, + /* [0xd5] = */ null, + /* [0xd6] = */ null, + /* [0xd7] = */ null, + /* [0xd8] = */ null, + /* [0xd9] = */ null, + /* [0xda] = */ null, + /* [0xdb] = */ null, + /* [0xdc] = */ null, + /* [0xdd] = */ null, + /* [0xde] = */ null, + /* [0xdf] = */ null, + /* [0xe0] = */ null, + /* [0xe1] = */ null, + /* [0xe2] = */ null, + /* [0xe3] = */ null, + /* [0xe4] = */ null, + /* [0xe5] = */ "iconst", + /* [0xe6] = */ "lconst", + /* [0xe7] = */ "fconst", + /* [0xe8] = */ "dconst", + /* [0xe9] = */ "aconst", + /* [0xea] = */ "begin", + /* [0xeb] = */ "end", + /* [0xec] = */ "ivar", + /* [0xed] = */ "lvar", + /* [0xee] = */ "fvar", + /* [0xef] = */ "dvar", + /* [0xf0] = */ "avar", + /* [0xf1] = */ "rvar", + /* [0xf2] = */ "rstore", + /* [0xf3] = */ "znewarray", + /* [0xf4] = */ "bnewarray", + /* [0xf5] = */ "cnewarray", + /* [0xf6] = */ "snewarray", + /* [0xf7] = */ "inewarray", + /* [0xf8] = */ "lnewarray", + /* [0xf9] = */ "fnewarray", + /* [0xfa] = */ "dnewarray", + /* [0xfb] = */ "label", + /* [0xfc] = */ "switch", + /* [0xfd] = */ "case", + /* [0xfe] = */ "try", + /* [0xff] = */ "catch" + }; + + /** + * Byte Codes and JASM ops: Effect on stack height by value. + */ + public static final int[] OPEFFECT = + { + /* [0x00] = */ 0, // nop + /* [0x01] = */ 1, // aconst_null + /* [0x02] = */ 1, // iconst_m1 + /* [0x03] = */ 1, // iconst_0 + /* [0x04] = */ 1, // iconst_1 + /* [0x05] = */ 1, // iconst_2 + /* [0x06] = */ 1, // iconst_3 + /* [0x07] = */ 1, // iconst_4 + /* [0x08] = */ 1, // iconst_5 + /* [0x09] = */ 2, // lconst_0 + /* [0x0a] = */ 2, // lconst_1 + /* [0x0b] = */ 1, // fconst_0 + /* [0x0c] = */ 1, // fconst_1 + /* [0x0d] = */ 1, // fconst_2 + /* [0x0e] = */ 2, // dconst_0 + /* [0x0f] = */ 2, // dconst_1 + /* [0x10] = */ 1, // bipush + /* [0x11] = */ 1, // sipush + /* [0x12] = */ 1, // ldc + /* [0x13] = */ 1, // ldc_w + /* [0x14] = */ 2, // ldc2_w + /* [0x15] = */ 1, // iload + /* [0x16] = */ 2, // lload + /* [0x17] = */ 1, // fload + /* [0x18] = */ 2, // dload + /* [0x19] = */ 1, // aload + /* [0x1a] = */ 1, // iload_0 + /* [0x1b] = */ 1, // iload_1 + /* [0x1c] = */ 1, // iload_2 + /* [0x1d] = */ 1, // iload_3 + /* [0x1e] = */ 2, // lload_0 + /* [0x1f] = */ 2, // lload_1 + /* [0x20] = */ 2, // lload_2 + /* [0x21] = */ 2, // lload_3 + /* [0x22] = */ 1, // fload_0 + /* [0x23] = */ 1, // fload_1 + /* [0x24] = */ 1, // fload_2 + /* [0x25] = */ 1, // fload_3 + /* [0x26] = */ 2, // dload_0 + /* [0x27] = */ 2, // dload_1 + /* [0x28] = */ 2, // dload_2 + /* [0x29] = */ 2, // dload_3 + /* [0x2a] = */ 1, // aload_0 + /* [0x2b] = */ 1, // aload_1 + /* [0x2c] = */ 1, // aload_2 + /* [0x2d] = */ 1, // aload_3 + /* [0x2e] = */ -1, // iaload + /* [0x2f] = */ 0, // laload + /* [0x30] = */ -1, // faload + /* [0x31] = */ 0, // daload + /* [0x32] = */ -1, // aaload + /* [0x33] = */ -1, // baload + /* [0x34] = */ -1, // caload + /* [0x35] = */ -1, // saload + /* [0x36] = */ -1, // istore + /* [0x37] = */ -2, // lstore + /* [0x38] = */ -1, // fstore + /* [0x39] = */ -2, // dstore + /* [0x3a] = */ -1, // astore + /* [0x3b] = */ -1, // istore_0 + /* [0x3c] = */ -1, // istore_1 + /* [0x3d] = */ -1, // istore_2 + /* [0x3e] = */ -1, // istore_3 + /* [0x3f] = */ -2, // lstore_0 + /* [0x40] = */ -2, // lstore_1 + /* [0x41] = */ -2, // lstore_2 + /* [0x42] = */ -2, // lstore_3 + /* [0x43] = */ -1, // fstore_0 + /* [0x44] = */ -1, // fstore_1 + /* [0x45] = */ -1, // fstore_2 + /* [0x46] = */ -1, // fstore_3 + /* [0x47] = */ -2, // dstore_0 + /* [0x48] = */ -2, // dstore_1 + /* [0x49] = */ -2, // dstore_2 + /* [0x4a] = */ -2, // dstore_3 + /* [0x4b] = */ -1, // astore_0 + /* [0x4c] = */ -1, // astore_1 + /* [0x4d] = */ -1, // astore_2 + /* [0x4e] = */ -1, // astore_3 + /* [0x4f] = */ -3, // iastore + /* [0x50] = */ -4, // lastore + /* [0x51] = */ -3, // fastore + /* [0x52] = */ -4, // dastore + /* [0x53] = */ -3, // aastore + /* [0x54] = */ -3, // bastore + /* [0x55] = */ -3, // castore + /* [0x56] = */ -3, // sastore + /* [0x57] = */ -1, // pop + /* [0x58] = */ -2, // pop2 + /* [0x59] = */ 1, // dup + /* [0x5a] = */ 1, // dup_x1 + /* [0x5b] = */ 1, // dup_x2 + /* [0x5c] = */ 2, // dup2 + /* [0x5d] = */ 2, // dup2_x1 + /* [0x5e] = */ 2, // dup2_x2 + /* [0x5f] = */ 0, // swap + /* [0x60] = */ -1, // iadd + /* [0x61] = */ -2, // ladd + /* [0x62] = */ -1, // fadd + /* [0x63] = */ -2, // dadd + /* [0x64] = */ -1, // isub + /* [0x65] = */ -2, // lsub + /* [0x66] = */ -1, // fsub + /* [0x67] = */ -2, // dsub + /* [0x68] = */ -1, // imul + /* [0x69] = */ -2, // lmul + /* [0x6a] = */ -1, // fmul + /* [0x6b] = */ -2, // dmul + /* [0x6c] = */ -1, // idiv + /* [0x6d] = */ -2, // ldiv + /* [0x6e] = */ -1, // fdiv + /* [0x6f] = */ -2, // ddiv + /* [0x70] = */ -1, // irem + /* [0x71] = */ -2, // lrem + /* [0x72] = */ -1, // frem + /* [0x73] = */ -2, // drem + /* [0x74] = */ 0, // ineg + /* [0x75] = */ 0, // lneg + /* [0x76] = */ 0, // fneg + /* [0x77] = */ 0, // dneg + /* [0x78] = */ -1, // ishl + /* [0x79] = */ -1, // lshl + /* [0x7a] = */ -1, // ishr + /* [0x7b] = */ -1, // lshr + /* [0x7c] = */ -1, // iushr + /* [0x7d] = */ -1, // lushr + /* [0x7e] = */ -1, // iand + /* [0x7f] = */ -2, // land + /* [0x80] = */ -1, // ior + /* [0x81] = */ -2, // lor + /* [0x82] = */ -1, // ixor + /* [0x83] = */ -2, // lxor + /* [0x84] = */ 0, // iinc + /* [0x85] = */ 1, // i2l + /* [0x86] = */ 0, // i2f + /* [0x87] = */ 1, // i2d + /* [0x88] = */ -1, // l2i + /* [0x89] = */ -1, // l2f + /* [0x8a] = */ 0, // l2d + /* [0x8b] = */ 0, // f2i + /* [0x8c] = */ 1, // f2l + /* [0x8d] = */ 1, // f2d + /* [0x8e] = */ -1, // d2i + /* [0x8f] = */ 0, // d2l + /* [0x90] = */ -1, // d2f + /* [0x91] = */ 0, // i2b + /* [0x92] = */ 0, // i2c + /* [0x93] = */ 0, // i2s + /* [0x94] = */ -3, // lcmp + /* [0x95] = */ -1, // fcmpl + /* [0x96] = */ -1, // fcmpg + /* [0x97] = */ -3, // dcmpl + /* [0x98] = */ -3, // dcmpg + /* [0x99] = */ -1, // ifeq + /* [0x9a] = */ -1, // ifne + /* [0x9b] = */ -1, // iflt + /* [0x9c] = */ -1, // ifge + /* [0x9d] = */ -1, // ifgt + /* [0x9e] = */ -1, // ifle + /* [0x9f] = */ -2, // if_icmpeq + /* [0xa0] = */ -2, // if_icmpne + /* [0xa1] = */ -2, // if_icmplt + /* [0xa2] = */ -2, // if_icmpge + /* [0xa3] = */ -2, // if_icmpgt + /* [0xa4] = */ -2, // if_icmple + /* [0xa5] = */ -2, // if_acmpeq + /* [0xa6] = */ -2, // if_acmpne + /* [0xa7] = */ 0, // goto + /* [0xa8] = */ 0, // jsr + /* [0xa9] = */ 0, // ret + /* [0xaa] = */ -1, // tableswitch + /* [0xab] = */ -1, // lookupswitch + /* [0xac] = */ -1, // ireturn + /* [0xad] = */ -2, // lreturn + /* [0xae] = */ -1, // freturn + /* [0xaf] = */ -2, // dreturn + /* [0xb0] = */ -1, // areturn + /* [0xb1] = */ 0, // return + /* [0xb2] = */ 0, // getstatic (calculated) + /* [0xb3] = */ 0, // putstatic (calculated) + /* [0xb4] = */ 0, // getfield (calculated) + /* [0xb5] = */ 0, // putfield (calculated) + /* [0xb6] = */ 0, // invokevirtual (calculated) + /* [0xb7] = */ 0, // invokespecial (calculated) + /* [0xb8] = */ 0, // invokestatic (calculated) + /* [0xb9] = */ 0, // invokeinterface (calculated) + /* [0xba] = */ 0, // invokedynamic (calculated) + /* [0xbb] = */ 1, // new + /* [0xbc] = */ 0, // newarray + /* [0xbd] = */ 0, // anewarray + /* [0xbe] = */ 0, // arraylength + /* [0xbf] = */ -1, // athrow + /* [0xc0] = */ 0, // checkcast + /* [0xc1] = */ 0, // instanceof + /* [0xc2] = */ -1, // monitorenter + /* [0xc3] = */ -1, // monitorexit + /* [0xc4] = */ 0, // wide (not supported) + /* [0xc5] = */ 0, // multianewarray (calculated) + /* [0xc6] = */ -1, // ifnull + /* [0xc7] = */ -1, // ifnonnull + /* [0xc8] = */ 0, // goto_w + /* [0xc9] = */ 0, // jsr_w + /* [0xca] = */ 0, + /* [0xcb] = */ 0, + /* [0xcc] = */ 0, + /* [0xcd] = */ 0, + /* [0xce] = */ 0, + /* [0xcf] = */ 0, + /* [0xd0] = */ 0, + /* [0xd1] = */ 0, + /* [0xd2] = */ 0, + /* [0xd3] = */ 0, + /* [0xd4] = */ 0, + /* [0xd5] = */ 0, + /* [0xd6] = */ 0, + /* [0xd7] = */ 0, + /* [0xd8] = */ 0, + /* [0xd9] = */ 0, + /* [0xda] = */ 0, + /* [0xdb] = */ 0, + /* [0xdc] = */ 0, + /* [0xdd] = */ 0, + /* [0xde] = */ 0, + /* [0xdf] = */ 0, + /* [0xe0] = */ 0, + /* [0xe1] = */ 0, + /* [0xe2] = */ 0, + /* [0xe3] = */ 0, + /* [0xe4] = */ 0, + /* [0xe5] = */ 1, // iconst + /* [0xe6] = */ 2, // lconst + /* [0xe7] = */ 1, // fconst + /* [0xe8] = */ 2, // dconst + /* [0xe9] = */ 1, // aconst + /* [0xea] = */ 0, // begin + /* [0xeb] = */ 0, // end + /* [0xec] = */ 0, // ivar + /* [0xed] = */ 0, // lvar + /* [0xee] = */ 0, // fvar + /* [0xef] = */ 0, // dvar + /* [0xf0] = */ 0, // avar + /* [0xf1] = */ 0, // rvar + /* [0xf2] = */ -1, // rstore + /* [0xf3] = */ 0, // znewarray + /* [0xf4] = */ 0, // bnewarray + /* [0xf5] = */ 0, // cnewarray + /* [0xf6] = */ 0, // snewarray + /* [0xf7] = */ 0, // inewarray + /* [0xf8] = */ 0, // lnewarray + /* [0xf9] = */ 0, // fnewarray + /* [0xfa] = */ 0, // dnewarray + /* [0xfb] = */ 0, // label + /* [0xfc] = */ -1, // switch + /* [0xfd] = */ 0, // case + /* [0xfe] = */ 0, // try + /* [0xff] = */ 0, // catch (calculated) + }; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/D2f.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/D2f.java new file mode 100644 index 0000000000000..ad8290f5c8355 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/D2f.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The D2F simple op converts the double on the top of the stack to a +* float. +*

+* JASM op         :  D2F  (0x90)
+* JVM byte code(s):  D2F  (0x90)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class D2f extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public D2f() + { + super(D2F); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "D2f"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/D2i.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/D2i.java new file mode 100644 index 0000000000000..112070111f179 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/D2i.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The D2I simple op converts the double on the top of the stack to an +* integer. +*

+* JASM op         :  D2I  (0x8e)
+* JVM byte code(s):  D2I  (0x8e)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class D2i extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public D2i() + { + super(D2I); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "D2i"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/D2l.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/D2l.java new file mode 100644 index 0000000000000..7a5cf739d3bc0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/D2l.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The D2L simple op converts the double on the top of the stack to a long. +*

+* JASM op         :  D2L  (0x8f)
+* JVM byte code(s):  D2L  (0x8f)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class D2l extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public D2l() + { + super(D2L); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "D2l"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dadd.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dadd.java new file mode 100644 index 0000000000000..866ff68b14449 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dadd.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DADD simple op adds the top two doubles in the stack. +*

+* JASM op         :  DADD  (0x63)
+* JVM byte code(s):  DADD  (0x63)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dadd extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dadd() + { + super(DADD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dadd"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Daload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Daload.java new file mode 100644 index 0000000000000..5f89095edb1f9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Daload.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DALOAD simple op pushes a double element (two words) from an array +* onto the stack. +*

+* JASM op         :  DALOAD (0x31)
+* JVM byte code(s):  DALOAD (0x31)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Daload extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Daload() + { + super(DALOAD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Daload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dastore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dastore.java new file mode 100644 index 0000000000000..69eae06a7c0a3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dastore.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DASTORE simple op stores a double array element. +*

+* JASM op         :  DASTORE (0x52)
+* JVM byte code(s):  DASTORE (0x52)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dastore extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dastore() + { + super(DASTORE); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dastore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dcmpg.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dcmpg.java new file mode 100644 index 0000000000000..ea98af439e86f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dcmpg.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DCMPG simple op compares the two doubles on the top of the stack +* (used to implement "<"). +*

+* JASM op         :  DCMPG  (0x98)
+* JVM byte code(s):  DCMPG  (0x98)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dcmpg extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dcmpg() + { + super(DCMPG); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dcmpg"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dcmpl.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dcmpl.java new file mode 100644 index 0000000000000..2b187b68ea662 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dcmpl.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DCMPL simple op compares the two doubles on the top of the stack +* (used to implement ">"). +*

+* JASM op         :  DCMPL  (0x97)
+* JVM byte code(s):  DCMPL  (0x97)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dcmpl extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dcmpl() + { + super(DCMPL); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dcmpl"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dconst.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dconst.java new file mode 100644 index 0000000000000..c76bc6b428820 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dconst.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DCONST variable-size op pushes a double-precision floating point +* constant onto the stack. +*

+* JASM op         :  DCONST    (0xe8)
+* JVM byte code(s):  DCONST_0  (0x0e)
+*                    DCONST_1  (0x0f)
+*                    LDC2_W    (0x14????)
+* Details         :
+* 
+* +* @version 0.50, 06/11/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dconst extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param dfl the double value + */ + public Dconst(double dfl) + { + this(new DoubleConstant(dfl)); + } + + /** + * Construct the op. + * + * @param constant the DoubleConstant to push + */ + public Dconst(DoubleConstant constant) + { + super(DCONST); + m_constant = constant; + + if (constant == null) + { + throw new IllegalArgumentException(CLASS + ": Constant must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_constant.format(), null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + m_constant.format(); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + // 0 and 1 are optimizable + DoubleConstant constant = m_constant; + double n = constant.getValue(); + if (n != 0.0 && n != 1.0) + { + pool.registerConstant(constant); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + DoubleConstant constant = m_constant; + double n = constant.getValue(); + + // 0 and 1 are optimizable + if (n == 0.0 || n == 1.0) + { + stream.writeByte(DCONST_0 + (int)n); + } + else + { + stream.writeByte(LDC2_W); + stream.writeShort(pool.findConstant(constant)); + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + double n = m_constant.getValue(); + setSize(n == 0.0 || n == 1.0 ? 1 : 3); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dconst"; + + /** + * The double-precision floating point constant loaded by this op. + */ + private DoubleConstant m_constant; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ddiv.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ddiv.java new file mode 100644 index 0000000000000..21b4ac53e23aa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ddiv.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DDIV simple op divides the second double in the stack by the first. +*

+* JASM op         :  DDIV  (0x6f)
+* JVM byte code(s):  DDIV  (0x6f)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ddiv extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ddiv() + { + super(DDIV); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ddiv"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/DeprecatedAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/DeprecatedAttribute.java new file mode 100644 index 0000000000000..708762ae944ec --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/DeprecatedAttribute.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a Java Virtual Machine "Deprecated" attribute which can apply +* to a class, a field, or a method. +*

+* The Deprecated Attribute is undocumented. Its structure appears to be: +*

+*

+*   Deprecated_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length; (=0)
+*       }
+* 
+* +* @version 0.50, 05/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class DeprecatedAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a Deprecated attribute. + * + * @param context the JVM structure containing the attribute + */ + protected DeprecatedAttribute(VMStructure context) + { + super(context, ATTR_DEPRECATED); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "DeprecatedAttribute"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Disassembler.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Disassembler.java new file mode 100644 index 0000000000000..310e212d8c247 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Disassembler.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.assembler; + + +import com.tangosol.util.Base; + +import com.tangosol.util.Resources; + +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.InputStream; + + +/** +* Command-line disassembler utility. +* +* @author 1997.07.30 cp Original programmer (disassembler package) +* @author 1998.01.06 cp Updating +* @author 2004.05.05 cp Updating +*/ +public class Disassembler + extends Base + { + public static void main(String asArgs[]) throws Throwable + { + try + { + if (asArgs.length != 1 || asArgs[0] == null) + { + showInstructions(); + return; + } + + String sName = asArgs[0].trim(); + if (asArgs.length == 0) + { + showInstructions(); + return; + } + + String sFile = sName.replace('.', '/').concat(".class"); + out(); + out("Loading resource: " + sFile); + InputStream in = null; + try + { + in = Resources.findFileOrResource(sFile, getContextClassLoader()).openStream(); + } + catch (Exception e) + { + out(); + err(e); + } + + if (in == null) + { + out(); + out("Error: Could not load resource."); + showInstructions(); + return; + } + + out(); + out("Disassembling:"); + out(); + + ClassFile cf = new ClassFile(new DataInputStream(in)); + cf.dump(getOut()); + + try + { + if (in.read() >= 0) + { + out("WARNING!!! Stream not exhausted!!!"); + } + } + catch (EOFException e) + { + } + } + catch (Throwable t) + { + out("Caught \"" + t + "\", stack trace:"); + out(t); + out("(end stack trace)"); + } + } + + /** + * Print command line instructions. + */ + public static void showInstructions() + { + out(); + out("Usage instructions:"); + out(); + out(" java " + Disassembler.class.getName() + " "); + out(); + out("(Where is the fully qualified Java class name.)"); + out(); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dload.java new file mode 100644 index 0000000000000..e4d8b8e261862 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dload.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DLOAD variable-size op pushes a double variable (two words) onto the +* stack. +*

+* JASM op         :  DLOAD    (0x18)
+* JVM byte code(s):  DLOAD    (0x18)
+*                    DLOAD_0  (0x26)
+*                    DLOAD_1  (0x27)
+*                    DLOAD_2  (0x28)
+*                    DLOAD_3  (0x29)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dload extends OpLoad implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to push + */ + public Dload(Dvar var) + { + super(DLOAD, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dmul.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dmul.java new file mode 100644 index 0000000000000..7184f5f9896e3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dmul.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DMUL simple op multiplies the second double in the stack by the +* first. +*

+* JASM op         :  DMUL  (0x6b)
+* JVM byte code(s):  DMUL  (0x6b)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dmul extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dmul() + { + super(DMUL); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dmul"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dneg.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dneg.java new file mode 100644 index 0000000000000..4e4d938e02cd0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dneg.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DNEG simple op computes the arithmetic negative of the double on the +* top of the stack. +*

+* JASM op         :  DNEG  (0x77)
+* JVM byte code(s):  DNEG  (0x77)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dneg extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dneg() + { + super(DNEG); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dneg"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dnewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dnewarray.java new file mode 100644 index 0000000000000..a389fb78cb198 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dnewarray.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DNEWARRAY pseudo-op instantiates an array of double. +*

+* JASM op         :  DNEWARRAY    (0xfa)
+* JVM byte code(s):  NEWARRAY     (0xbc)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dnewarray extends OpArray implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dnewarray() + { + super(DNEWARRAY); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dnewarray"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/DoubleConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/DoubleConstant.java new file mode 100644 index 0000000000000..982569abeea56 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/DoubleConstant.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represent a Java Virtual Machine double constant. +* +* @version 0.50, 05/13/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class DoubleConstant extends Constant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected DoubleConstant() + { + super(CONSTANT_DOUBLE); + } + + /** + * Construct a constant whose value is a java double. + * + * @param dflVal the java double + */ + public DoubleConstant(double dflVal) + { + this(); + m_dflVal = dflVal; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_dflVal = stream.readDouble(); + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeDouble(m_dflVal); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + DoubleConstant that = (DoubleConstant) obj; + + double dflThis = this.m_dflVal; + double dflThat = that.m_dflVal; + + return (dflThis < dflThat ? -1 : (dflThis > dflThat ? +1 : 0)); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Double) " + m_dflVal; + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + return String.valueOf(m_dflVal); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + DoubleConstant that = (DoubleConstant) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_dflVal == that.m_dflVal; + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the value of the constant. + * + * @return the constant's double value + */ + public double getValue() + { + return m_dflVal; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "DoubleConstant"; + + /** + * The constant double value. + */ + private double m_dflVal; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Drem.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Drem.java new file mode 100644 index 0000000000000..9274054f03592 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Drem.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DREM simple op computes the remainder when the second double in the +* stack is divided by the first. +*

+* JASM op         :  DREM  (0x73)
+* JVM byte code(s):  DREM  (0x73)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Drem extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Drem() + { + super(DREM); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Drem"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dreturn.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dreturn.java new file mode 100644 index 0000000000000..6cf08f41b9a6b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dreturn.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DRETURN simple op returns a double value from the method. +*

+* JASM op         :  DRETURN  (0xaf)
+* JVM byte code(s):  DRETURN  (0xaf)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dreturn extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dreturn() + { + super(DRETURN); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dreturn"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dstore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dstore.java new file mode 100644 index 0000000000000..b60699442aae0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dstore.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DSTORE variable-size op stores a double variable. +*

+* JASM op         :  DSTORE    (0x39)
+* JVM byte code(s):  DSTORE    (0x39)
+*                    DSTORE_0  (0x47)
+*                    DSTORE_1  (0x48)
+*                    DSTORE_2  (0x49)
+*                    DSTORE_3  (0x4a)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dstore extends OpStore implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to pop + */ + public Dstore(Dvar var) + { + super(DSTORE, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dstore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dsub.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dsub.java new file mode 100644 index 0000000000000..ac5f871bee0f0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dsub.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DSUB simple op subtracts the second double in the stack from the +* first. +*

+* JASM op         :  DSUB  (0x67)
+* JVM byte code(s):  DSUB  (0x67)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dsub extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dsub() + { + super(DSUB); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dsub"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup.java new file mode 100644 index 0000000000000..1c7fc2a1eff04 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DUP simple op duplicates the top word of the stack. +*

+* JASM op         :  DUP  (0x59)
+* JVM byte code(s):  DUP  (0x59)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dup extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dup() + { + super(DUP); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dup"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup2.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup2.java new file mode 100644 index 0000000000000..1a4a04e319e84 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup2.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DUP2 simple op duplicates the top two words (a long or double) of +* the stack. +*

+* JASM op         :  DUP2  (0x5c)
+* JVM byte code(s):  DUP2  (0x5c)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dup2 extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dup2() + { + super(DUP2); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dup2"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup2_x1.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup2_x1.java new file mode 100644 index 0000000000000..8ac3225e02484 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup2_x1.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DUP2_X1 simple op duplicates the top two words (a long or double) of +* the stack and places it three down in the stack. +*

+* JASM op         :  DUP2_X1  (0x5d)
+* JVM byte code(s):  DUP2_X1  (0x5d)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dup2_x1 extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dup2_x1() + { + super(DUP2_X1); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dup2_x1"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup2_x2.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup2_x2.java new file mode 100644 index 0000000000000..4f603e39c7119 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup2_x2.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DUP2_X2 simple op duplicates the top two words (a long or double) of +* the stack and places it four down in the stack. +*

+* JASM op         :  DUP2_X2  (0x5e)
+* JVM byte code(s):  DUP2_X2  (0x5e)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dup2_x2 extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dup2_x2() + { + super(DUP2_X2); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dup2_x2"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup_x1.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup_x1.java new file mode 100644 index 0000000000000..337f93cf28a52 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup_x1.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DUP_X1 simple op duplicates the top word of the stack and places it +* two down in the stack. +*

+* JASM op         :  DUP_X1  (0x5a)
+* JVM byte code(s):  DUP_X1  (0x5a)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dup_x1 extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dup_x1() + { + super(DUP_X1); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dup_x1"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup_x2.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup_x2.java new file mode 100644 index 0000000000000..45d9b3880635d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dup_x2.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DUP_X2 simple op duplicates the top word of the stack and places it +* three down in the stack. +*

+* JASM op         :  DUP_X2  (0x5b)
+* JVM byte code(s):  DUP_X2  (0x5b)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dup_x2 extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dup_x2() + { + super(DUP_X2); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dup_x2"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dvar.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dvar.java new file mode 100644 index 0000000000000..08dfe21bf0d8f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Dvar.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The DVAR pseudo-op declares a double variable. The variable can +* optionally be named. +*

+* JASM op         :  DVAR  (0xef)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Dvar extends OpDeclare implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Dvar() + { + super(DVAR, null, null); + } + + /** + * Construct the op. + * + * @param sName the name of the variable + */ + public Dvar(String sName) + { + super(DVAR, sName, null); + } + + /** + * Construct the op. Used by disassembler. + * + * @param iVar the variable index + */ + protected Dvar(int iVar) + { + super(DVAR, null, null, iVar); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Dvar"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/EnclosingMethodAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/EnclosingMethodAttribute.java new file mode 100644 index 0000000000000..9f4711ca5f51f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/EnclosingMethodAttribute.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a Java Virtual Machine "EnclosingMethod" attribute which +* specifies the enclosing method of a local or anonymous class. +* +*

+* The EnclosingMethod Attribute is defined by the JDK 1.5 documentation as: +*

+*

+*   EnclosingMethod_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length; (=4)
+*       u2 class_index;
+*       u2 method_index;
+*       }
+* 
+* +* @author rhl 2008.09.23 +*/ +public class EnclosingMethodAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a enclosing method attribute. + * + * @param context the JVM structure containing the attribute + */ + protected EnclosingMethodAttribute(VMStructure context) + { + super(context, ATTR_ENCMETHOD); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + m_methEnclosing = new MethodConstant(); + m_methEnclosing.disassemble(stream, pool); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + m_methEnclosing.preassemble(pool); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(4); /* class_index, method_index */ + stream.writeShort(pool.findConstant(m_methEnclosing.getClassConstant())); + stream.writeShort(pool.findConstant(m_methEnclosing.getSignatureConstant())); + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return super.getName() + '=' + m_methEnclosing; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the enclosing method. + * + * @return the enclosing method + */ + public MethodConstant getEnclosingMethod() + { + return m_methEnclosing; + } + + /** + * Set the enclosing method. + * + * @param methEnclosing the enclosing method + */ + public void setEnclosingMethod(MethodConstant methEnclosing) + { + m_methEnclosing = methEnclosing; + m_fModified = true; + } + + + // ----- data members --------------------------------------------------- + + /** + * The enclosing method. + */ + private MethodConstant m_methEnclosing; + + /** + * Has the attribute been modified? + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/End.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/End.java new file mode 100644 index 0000000000000..8ec140bd2ab14 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/End.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The END pseudo op closes a variable scope. +*

+* JASM op         :  END (0xeb)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/17/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class End extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public End() + { + super(END); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Determine if the op is discardable. Begin and End ops are never + * considered discardable since they may come before labels and after + * returns/unconditional branches and since they do not affect execution. + * + * @return false always + */ + protected boolean isDiscardable() + { + return false; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "End"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ExceptionsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ExceptionsAttribute.java new file mode 100644 index 0000000000000..1830011fd021a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/ExceptionsAttribute.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Enumeration; + +import com.tangosol.util.StringTable; + + +/** +* Represents a Java Virtual Machine "exceptions" Attribute which declares +* exceptions that may be thrown by a method. +* +* @version 0.50, 05/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class ExceptionsAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an exceptions attribute. + * + * @param context the JVM structure containing the attribute + */ + protected ExceptionsAttribute(VMStructure context) + { + super(context, ATTR_EXCEPTIONS); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + int c = stream.readUnsignedShort(); + for (int i = 0; i < c; ++i) + { + ClassConstant clz = (ClassConstant) pool.getConstant(stream.readUnsignedShort()); + m_tblException.put(clz.getValue(), clz); + } + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + + Enumeration enmr = m_tblException.elements(); + while (enmr.hasMoreElements()) + { + pool.registerConstant((ClassConstant) enmr.nextElement()); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(2 + m_tblException.getSize() * 2); + stream.writeShort(m_tblException.getSize()); + Enumeration enmr = m_tblException.elements(); + while (enmr.hasMoreElements()) + { + ClassConstant clz = (ClassConstant) enmr.nextElement(); + stream.writeShort(pool.findConstant(clz)); + } + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + * + * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return "Exceptions: " + m_tblException.toString(); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + ExceptionsAttribute that = (ExceptionsAttribute) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_tblException.equals(that.m_tblException); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessor: exception ------------------------------------------- + + /** + * Add an exception. + * + * @param sClz the class name of the exception + */ + public void addException(String sClz) + { + m_tblException.put(sClz, new ClassConstant(sClz)); + m_fModified = true; + } + + /** + * Remove an exception. + * + * @param sClz the class name of the exception + */ + public void removeException(String sClz) + { + m_tblException.remove(sClz); + m_fModified = true; + } + + /** + * Access the set of exceptions. + * + * @return an enumeration of exception class names + */ + public Enumeration getExceptions() + { + return m_tblException.keys(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "ExceptionsAttribute"; + + /** + * The constant value. + */ + private StringTable m_tblException = new StringTable(); + + /** + * Has the attribute been modified? + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/F2d.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/F2d.java new file mode 100644 index 0000000000000..467ebe86936fc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/F2d.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The F2D simple op converts the float on the top of the stack to a +* double. +*

+* JASM op         :  F2D  (0x8d)
+* JVM byte code(s):  F2D  (0x8d)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class F2d extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public F2d() + { + super(F2D); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "F2d"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/F2i.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/F2i.java new file mode 100644 index 0000000000000..60e6a3c4c8ca3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/F2i.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The F2I simple op converts the float on the top of the stack to an +* integer. +*

+* JASM op         :  F2I  (0x8b)
+* JVM byte code(s):  F2I  (0x8b)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class F2i extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public F2i() + { + super(F2I); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "F2i"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/F2l.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/F2l.java new file mode 100644 index 0000000000000..a96dd24740547 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/F2l.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The F2L simple op converts the float on the top of the stack to a long. +*

+* JASM op         :  F2L  (0x8c)
+* JVM byte code(s):  F2L  (0x8c)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class F2l extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public F2l() + { + super(F2L); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "F2l"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fadd.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fadd.java new file mode 100644 index 0000000000000..a0b646615c59a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fadd.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FADD simple op adds the top two floats in the stack. +*

+* JASM op         :  FADD  (0x62)
+* JVM byte code(s):  FADD  (0x62)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fadd extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fadd() + { + super(FADD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fadd"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Faload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Faload.java new file mode 100644 index 0000000000000..b4acd102e151a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Faload.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FALOAD simple op pushes a float element from an array onto the +* stack. +*

+* JASM op         :  FALOAD (0x30)
+* JVM byte code(s):  FALOAD (0x30)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Faload extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Faload() + { + super(FALOAD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Faload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fastore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fastore.java new file mode 100644 index 0000000000000..a9b5678b64dc6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fastore.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FASTORE simple op stores a float array element. +*

+* JASM op         :  FASTORE (0x51)
+* JVM byte code(s):  FASTORE (0x51)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fastore extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fastore() + { + super(FASTORE); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fastore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fcmpg.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fcmpg.java new file mode 100644 index 0000000000000..c82abd606dbee --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fcmpg.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FCMPG simple op compares the two floats on the top of the stack +* (used to implement "<"). +*

+* JASM op         :  FCMPG  (0x96)
+* JVM byte code(s):  FCMPG  (0x96)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fcmpg extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fcmpg() + { + super(FCMPG); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fcmpg"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fcmpl.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fcmpl.java new file mode 100644 index 0000000000000..4517cd489355d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fcmpl.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FCMPL simple op compares the two floats on the top of the stack +* (used to implement ">"). +*

+* JASM op         :  FCMPL  (0x95)
+* JVM byte code(s):  FCMPL  (0x95)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fcmpl extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fcmpl() + { + super(FCMPL); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fcmpl"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fconst.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fconst.java new file mode 100644 index 0000000000000..b2a29651e9928 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fconst.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FCONST variable-size op pushes a single-precision floating point +* constant onto the stack. +*

+* JASM op         :  FCONST    (0xe7)
+* JVM byte code(s):  FCONST_0  (0x0b)
+*                    FCONST_1  (0x0c)
+*                    FCONST_1  (0x0d)
+*                    LDC       (0x12??)
+*                    LDC_W     (0x13????)
+* Details         :
+* 
+* +* @version 0.50, 06/11/98, assembler/dis-assembler +* @version 0.51, 12/04/98, fix to calculateSize +* @author Cameron Purdy +*/ +public class Fconst extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param fl the float value + */ + public Fconst(float fl) + { + this(new FloatConstant(fl)); + } + + /** + * Construct the op. + * + * @param constant the FloatConstant to push + */ + public Fconst(FloatConstant constant) + { + super(FCONST); + m_constant = constant; + + if (constant == null) + { + throw new IllegalArgumentException(CLASS + ": Constant must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_constant.format(), null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + m_constant.format(); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + // 0F, 1F and 2F are optimizable + FloatConstant constant = m_constant; + float n = constant.getValue(); + if (n != 0F && n != 1F && n != 2F) + { + pool.registerConstant(constant); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + FloatConstant constant = m_constant; + float n = constant.getValue(); + + // 0F, 1F, and 2F are optimizable + if (n == 0F || n == 1F || n == 2F) + { + stream.writeByte(FCONST_0 + (int)n); + } + else + { + int iConst = pool.findConstant(constant); + if (iConst <= 0xFF) + { + stream.writeByte(LDC); + stream.writeByte(iConst); + } + else + { + stream.writeByte(LDC_W); + stream.writeShort(iConst); + } + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + FloatConstant constant = m_constant; + float fl = constant.getValue(); + + if (fl == 0F || fl == 1F || fl == 2F) + { + setSize(1); + } + else if (pool.findConstant(constant) <= 0xFF) + { + setSize(2); + } + else + { + setSize(3); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fconst"; + + /** + * The single-precision floating point constant loaded by this op. + */ + private FloatConstant m_constant; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fdiv.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fdiv.java new file mode 100644 index 0000000000000..84e9bed1cbf82 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fdiv.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FDIV simple op divides the second float in the stack by the first. +*

+* JASM op         :  FDIV  (0x6e)
+* JVM byte code(s):  FDIV  (0x6e)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fdiv extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fdiv() + { + super(FDIV); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fdiv"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Field.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Field.java new file mode 100644 index 0000000000000..585d7eed6a29b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Field.java @@ -0,0 +1,929 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Enumeration; + +import com.tangosol.util.StringTable; + + +/** +* Represents a Java Virtual Machine Field structure as defined by the Java +* Virtual Machine (JVM) Specification. +*

+* The structure is defined by the Java Virtual Machine Specification as: +*

+*

+*   field_info
+*       {
+*       u2 access_flags;
+*       u2 name_index;
+*       u2 descriptor_index;
+*       u2 attributes_count;
+*       attribute_info attributes[attributes_count];
+*       }
+* 
+* +* @version 0.50, 05/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Field extends VMStructure implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Construct a field structure. + */ + protected Field() + { + } + + /** + * Construct a field structure. + * + * @param sName the field name + * @param sType the field type + */ + protected Field(String sName, String sType) + { + this(new UtfConstant(sName), new UtfConstant(sType.replace('.', '/'))); + } + + /** + * Construct a field which references the passed UTF constant. + * + * @param constantName the referenced UTF constant which contains the + * name of the field + * @param constantType the referenced UTF constant which contains the + * field type + */ + protected Field(UtfConstant constantName, UtfConstant constantType) + { + if (constantName == null || constantType == null) + { + throw new IllegalArgumentException(CLASS + ": Values cannot be null!"); + } + + m_utfName = constantName; + m_utfType = constantType; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + // access flags + m_flags.disassemble(stream, pool); + + // name and type + m_utfName = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + m_utfType = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + + // attributes + m_tblAttribute.clear(); + int cAttr = stream.readUnsignedShort(); + for (int i = 0; i < cAttr; ++i) + { + Attribute attr = Attribute.loadAttribute(this, stream, pool); + m_tblAttribute.put(attr.getIdentity(), attr); + } + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utfName); + pool.registerConstant(m_utfType); + + m_flags.preassemble(pool); + + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + ((Attribute) enmr.nextElement()).preassemble(pool); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + m_flags.assemble(stream, pool); + + stream.writeShort(pool.findConstant(m_utfName)); + stream.writeShort(pool.findConstant(m_utfType)); + + stream.writeShort(m_tblAttribute.getSize()); + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + ((Attribute) enmr.nextElement()).assemble(stream, pool); + } + } + + /** + * Determine the identity of the VM structure (if applicable). + * + * @return the string identity of the VM structure + */ + public String getIdentity() + { + return m_utfName.getValue(); + } + + /** + * Determine if the VM structure (or any contained VM structure) has been + * modified. + * + * @return true if the VM structure has been modified + */ + public boolean isModified() + { + if (m_fModified || m_flags.isModified()) + { + return true; + } + + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + Attribute attr = (Attribute) enmr.nextElement(); + if (attr.isModified()) + { + return true; + } + } + + return false; + } + + /** + * Reset the modified state of the VM structure. + */ + protected void resetModified() + { + m_flags.resetModified(); + + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + ((Attribute) enmr.nextElement()).resetModified(); + } + + m_fModified = false; + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + Field that = (Field) obj; + int nResult = this.m_utfName.compareTo(that.m_utfName); + if (nResult == 0) + { + nResult = this.m_utfType.compareTo(that.m_utfType); + } + return nResult; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the field. + * + * @return a string describing the field + */ + public String toString() + { + String sMods = m_flags.toString(ACC_FIELD); + String sType = getTypeString(); + String sName = m_utfName.getValue(); + + StringBuffer sb = new StringBuffer(); + if (sMods.length() > 0) + { + sb.append(sMods) + .append(' '); + } + + sb.append(sType) + .append(' ') + .append(sName); + + return sb.toString(); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + Field that = (Field) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_utfName .equals(that.m_utfName ) + && this.m_utfType .equals(that.m_utfType ) + && this.m_flags .equals(that.m_flags ) + && this.m_tblAttribute.equals(that.m_tblAttribute); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- Field operations ----------------------------------------------- + + /** + * Provide a Java source representation of a JVM type signature. + * + * @param sSig the JVM type signature + * + * @return the Java type name as found in Java source code + */ + public static String toTypeString(String sSig) + { + switch (sSig.charAt(0)) + { + case 'V': + return "void"; + case 'Z': + return "boolean"; + case 'B': + return "byte"; + case 'C': + return "char"; + case 'S': + return "short"; + case 'I': + return "int"; + case 'J': + return "long"; + case 'F': + return "float"; + case 'D': + return "double"; + + case 'L': + return sSig.substring(1, sSig.indexOf(';')).replace('/', '.'); + + case '[': + int of = 0; + while (isDecimal(sSig.charAt(++of))) + {} + return toTypeString(sSig.substring(of)) + '[' + sSig.substring(1, of) + ']'; + + default: + throw new IllegalArgumentException("JVM Type Signature cannot start with '" + sSig.charAt(0) + "'"); + } + } + + /** + * Provide a boxed version of the given primitive in binary format. + * + * @param sSig the JVM type signature + * + * @return the boxed version of the given primitive in binary format + */ + public static String toBoxedType(String sSig) + { + String sBoxedType = "java/lang/"; + switch (sSig.charAt(0)) + { + case 'V': + sBoxedType += "Void"; + break; + case 'Z': + sBoxedType += "Boolean"; + break; + case 'B': + sBoxedType += "Byte"; + break; + case 'C': + sBoxedType += "Character"; + break; + case 'S': + sBoxedType += "Short"; + break; + case 'I': + sBoxedType += "Integer"; + break; + case 'J': + sBoxedType += "Long"; + break; + case 'F': + sBoxedType += "Float"; + break; + case 'D': + sBoxedType += "Double"; + break; + case 'L': + if (sSig.startsWith("Ljava/lang/")) + { + return sSig.substring(1, sSig.length() - 1); + } + case '[': + // reference and array types are quietly unsupported + sBoxedType = null; + break; + default: + throw new IllegalArgumentException("JVM Type Signature cannot start with '" + sSig.charAt(0) + "'"); + } + return sBoxedType; + } + + /** + * Provide a boxed version of the given primitive in binary format. + * + * @param sSig the JVM type signature + * + * @return the boxed version of the given primitive in binary format + */ + public static char fromBoxedType(String sSig) + { + if (sSig.startsWith("java/lang/")) + { + switch (sSig.substring(10)) + { + case "Void": + return 'V'; + case "Boolean": + return 'Z'; + case "Byte": + return 'B'; + case "Character": + return 'C'; + case "Short": + return 'S'; + case "Integer": + return 'I'; + case "Long": + return 'J'; + case "Float": + return 'F'; + case "Double": + return 'D'; + } + } + return 0; // ascii null + } + + + // ----- accessors: name and type -------------------------------------- + + /** + * Get the name of the field as a string. + * + * @return the field name + */ + public String getName() + { + return m_utfName.getValue(); + } + + /** + * Get the type of the field as a string. + * + * @return the field type + */ + public String getType() + { + return m_utfType.getValue(); + } + + /** + * Get the type of the field as a string as it appears in Java source. + * + * @return the field type string + */ + public String getTypeString() + { + return toTypeString(m_utfType.getValue()); + } + + /** + * Get the UTF constant which holds the field name. + * + * @return the UTF constant which contains the name + */ + public UtfConstant getNameConstant() + { + return m_utfName; + } + + /** + * Get the UTF constant which holds the field type. + * + * @return the UTF constant which contains the type + */ + public UtfConstant getTypeConstant() + { + return m_utfType; + } + + + // ----- accessor: access ---------------------------------------------- + + /** + * Get the field accessibility value. + * + * @return one of ACC_PUBLIC, ACC_PROTECTED, ACC_PRIVATE, or ACC_PACKAGE + */ + public int getAccess() + { + return m_flags.getAccess(); + } + + /** + * Set the field accessibility value. + * + * @param nAccess should be one of ACC_PUBLIC, ACC_PROTECTED, + * ACC_PRIVATE, or ACC_PACKAGE + */ + public void setAccess(int nAccess) + { + m_flags.setAccess(nAccess); + } + + /** + * Determine if the accessibility is public. + * + * @return true if the accessibility is public + */ + public boolean isPublic() + { + return m_flags.isPublic(); + } + + /** + * Set the accessibility to public. + */ + public void setPublic() + { + m_flags.setPublic(); + } + + /** + * Determine if the accessibility is protected. + * + * @return true if the accessibility is protected + */ + public boolean isProtected() + { + return m_flags.isProtected(); + } + + /** + * Set the accessibility to protected. + */ + public void setProtected() + { + m_flags.setProtected(); + } + + /** + * Determine if the accessibility is package private. + * + * @return true if the accessibility is package private + */ + public boolean isPackage() + { + return m_flags.isPackage(); + } + + /** + * Set the accessibility to package private. + */ + public void setPackage() + { + m_flags.setPackage(); + } + + /** + * Determine if the accessibility is private. + * + * @return true if the accessibility is private + */ + public boolean isPrivate() + { + return m_flags.isPrivate(); + } + + /** + * Set the accessibility to private. + */ + public void setPrivate() + { + m_flags.setPrivate(); + } + + + // ----- accessor: static ------------------------------------------- + + /** + * Determine if the Static attribute is set. + * + * @return true if Static + */ + public boolean isStatic() + { + return m_flags.isStatic(); + } + + /** + * Set the Static attribute. + * + * @param fStatic true to set to Static, false otherwise + */ + public void setStatic(boolean fStatic) + { + m_flags.setStatic(fStatic); + } + + + // ----- accessor: final ------------------------------------------- + + /** + * Determine if the Final attribute is set. + * + * @return true if Final + */ + public boolean isFinal() + { + return m_flags.isFinal(); + } + + /** + * Set the Final attribute. + * + * @param fFinal true to set to Final, false otherwise + */ + public void setFinal(boolean fFinal) + { + m_flags.setFinal(fFinal); + } + + + // ----- accessor: volatile ------------------------------------------- + + /** + * Determine if the Volatile attribute is set. + * + * @return true if Volatile + */ + public boolean isVolatile() + { + return m_flags.isVolatile(); + } + + /** + * Set the Volatile attribute. + * + * @param fVolatile true to set to Volatile, false otherwise + */ + public void setVolatile(boolean fVolatile) + { + m_flags.setVolatile(fVolatile); + } + + + // ----- accessor: transient ------------------------------------------- + + /** + * Determine if the Transient attribute is set. + * + * @return true if Transient + */ + public boolean isTransient() + { + return m_flags.isTransient(); + } + + /** + * Set the Transient attribute. + * + * @param fTransient true to set to Transient, false otherwise + */ + public void setTransient(boolean fTransient) + { + m_flags.setTransient(fTransient); + } + + + // ----- accessor: enum ------------------------------------------------ + + /** + * Determine if the Enum attribute is set. + * + * @return true if Enum + */ + public boolean isEnum() + { + return m_flags.isEnum(); + } + + /** + * Set the Enum attribute. + * + * @param fEnum true to set to Enum, false otherwise + */ + public void setEnum(boolean fEnum) + { + m_flags.setEnum(fEnum); + } + + + // ----- accessor: attribute ------------------------------------------- + + /** + * Access a Java .class attribute structure. + * + * @param sName the attribute name + * + * @return the specified attribute or null if the attribute does not exist + */ + public Attribute getAttribute(String sName) + { + return (Attribute) m_tblAttribute.get(sName); + } + + /** + * Add a Java .class attribute structure. + * + * @param sName the attribute name + * + * @return the new attribute + */ + public Attribute addAttribute(String sName) + { + Attribute attribute; + if (sName.equals(ATTR_CONSTANT)) + { + attribute = new ConstantValueAttribute(this); + } + else if (sName.equals(ATTR_DEPRECATED)) + { + attribute = new DeprecatedAttribute(this); + } + else if (sName.equals(ATTR_SYNTHETIC)) + { + attribute = new SyntheticAttribute(this); + } + else if (sName.equals(ATTR_RTVISANNOT)) + { + attribute = new RuntimeVisibleAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTINVISANNOT)) + { + attribute = new RuntimeInvisibleAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTVISTANNOT)) + { + attribute = new RuntimeVisibleTypeAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTINVISTANNOT)) + { + attribute = new RuntimeInvisibleTypeAnnotationsAttribute(this); + } + else + { + attribute = new Attribute(this, sName); + } + + m_tblAttribute.put(attribute.getIdentity(), attribute); + m_fModified = true; + + return attribute; + } + + /** + * Remove a attribute. + * + * @param sName the attribute name + */ + public void removeAttribute(String sName) + { + m_tblAttribute.remove(sName); + m_fModified = true; + } + + /** + * Access the set of attributes. + * + * @return an enumeration of attributes (not attribute names) + */ + public Enumeration getAttributes() + { + return m_tblAttribute.elements(); + } + + + // ----- accessor: attribute helpers ----------------------------------- + + /** + * Determine if the field is a constant. + * + * @return true if the field is final and initialized to a constant. + */ + public boolean isConstant() + { + return isFinal() && m_tblAttribute.contains(ATTR_CONSTANT); + } + + /** + * Determine the constant value of the field. + * + * @return the constant value or null if none + */ + public Constant getConstantValue() + { + ConstantValueAttribute attr = + (ConstantValueAttribute) m_tblAttribute.get(ATTR_CONSTANT); + return (attr == null ? null : attr.getConstant()); + } + + /** + * Set the constant value. + * + * @param constant the constant value + */ + public void setConstantValue(Constant constant) + { + // It is quite rare but possible to have a final instance constant + // so we don't force setStatic(true); + setFinal(true); + ConstantValueAttribute attr = + (ConstantValueAttribute) addAttribute(ATTR_CONSTANT); + attr.setConstant(constant); + } + + /** + * Determine if the field is deprecated. + * + * @return true if deprecated, false otherwise + */ + public boolean isDeprecated() + { + return m_tblAttribute.contains(ATTR_DEPRECATED); + } + + /** + * Toggle if the field is deprecated. + * + * @param fDeprecated pass true to deprecate, false otherwise + */ + public void setDeprecated(boolean fDeprecated) + { + if (fDeprecated) + { + addAttribute(ATTR_DEPRECATED); + } + else + { + removeAttribute(ATTR_DEPRECATED); + } + } + + /** + * Determine if the field is synthetic. + * + * @return true if synthetic, false otherwise + */ + public boolean isSynthetic() + { + return m_tblAttribute.contains(ATTR_SYNTHETIC); + } + + /** + * Toggle if the field is synthetic. + * + * @param fSynthetic pass true to set synthetic, false otherwise + */ + public void setSynthetic(boolean fSynthetic) + { + if (fSynthetic) + { + addAttribute(ATTR_SYNTHETIC); + } + else + { + removeAttribute(ATTR_SYNTHETIC); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Field"; + + /** + * Access flags applicable to a field. + */ + private static final int ACC_FIELD = AccessFlags.ACC_PUBLIC | + AccessFlags.ACC_PRIVATE | + AccessFlags.ACC_PROTECTED | + AccessFlags.ACC_STATIC | + AccessFlags.ACC_FINAL | + AccessFlags.ACC_VOLATILE | + AccessFlags.ACC_TRANSIENT | + AccessFlags.ACC_SYNTHETIC | + AccessFlags.ACC_ENUM; + + /** + * The name of the field. + */ + private UtfConstant m_utfName; + + /** + * The type of the field. + */ + private UtfConstant m_utfType; + + /** + * The AccessFlags structure contained in the field. + */ + private AccessFlags m_flags = new AccessFlags(); + + /** + * The Attribute structures contained in the field. + */ + private StringTable m_tblAttribute = new StringTable(); + + /** + * Tracks changes to the field. + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/FieldConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/FieldConstant.java new file mode 100644 index 0000000000000..878287b77802f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/FieldConstant.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + + +/** +* Represents the field of a class/interface. +* +* @version 0.50, 05/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class FieldConstant extends RefConstant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected FieldConstant() + { + super(CONSTANT_FIELDREF); + } + + /** + * Construct a constant which specifies a class/interface field. + * + * @param sClass the class name + * @param sName the field name + * @param sType the field type + */ + public FieldConstant(String sClass, String sName, String sType) + { + super(CONSTANT_FIELDREF, sClass, sName, sType); + } + + /** + * Construct a constant which references the passed constants. + * + * @param constantClz the referenced Class constant which contains the + * name of the class + * @param constantSig the referenced Signature constant which contains + * the type/name information for the field + */ + public FieldConstant(ClassConstant constantClz, SignatureConstant constantSig) + { + super(CONSTANT_FIELDREF, constantClz, constantSig); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Field)->" + super.toString(); + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + String sClass = getClassConstant().format(); + if (sClass.startsWith("java")) + { + int of = sClass.lastIndexOf('.'); + if (of != -1) + { + sClass = sClass.substring(of + 1); + } + } + sClass = sClass.replace('$', '.'); + + SignatureConstant sig = getSignatureConstant(); + String sName = sig.getName(); + String sType = sig.getType(); + + sType = Field.toTypeString(sType); + int of = sType.lastIndexOf('.'); + if (of != -1) + { + sType = sType.substring(of + 1); + } + sType = sType.replace('$', '.'); + + // format as: + // class.name (field-type) + return sClass + '.' + sName + " (" + sType + ')'; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the JVM variable type (I, F, L, D, or A) for the passed JVM + * type signature. + * + * @param sType the JVM type signature + * + * @return the JVM variable type + */ + public static char getVariableType(String sType) + { + // check for intrinsic types (1-character reserved type names) + if (sType.length() == 1) + { + char ch = sType.charAt(0); + switch (ch) + { + case 'Z': // boolean + case 'B': // byte + case 'C': // char + case 'S': // short + case 'I': // int + return 'I'; + + case 'J': // long + return 'L'; + + case 'F': // float + return 'F'; + + case 'D': // double + return 'D'; + + case 'V': // void (only for return values) + return 0x00; + + default: + throw new IllegalStateException(CLASS + "Illegal type=" + ch); + } + } + else + { + // reference type + return 'A'; + } + } + + /** + * Get the JVM variable type (I, F, L, D, or A) that can store the field + * value. + */ + public char getVariableType() + { + char chType = m_chType; + if (chType == 0x00) + { + m_chType = chType = getVariableType(getType()); + } + return chType; + } + + /** + * Get the variable size (number of words used to store the value in a + * local variable or on the stack) for the field. + * + * @return the number of words used by the variable type + */ + public int getVariableSize() + { + return OpDeclare.getWidth(getVariableType()); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "FieldConstant"; + + /** + * Cached type info. + */ + private char m_chType; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fload.java new file mode 100644 index 0000000000000..6349376d4c30b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fload.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FLOAD variable-size op pushes a float variable (one word) onto the +* stack. +*

+* JASM op         :  FLOAD    (0x17)
+* JVM byte code(s):  FLOAD    (0x17)
+*                    FLOAD_0  (0x22)
+*                    FLOAD_1  (0x23)
+*                    FLOAD_2  (0x24)
+*                    FLOAD_3  (0x25)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fload extends OpLoad implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to push + */ + public Fload(Fvar var) + { + super(FLOAD, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/FloatConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/FloatConstant.java new file mode 100644 index 0000000000000..689f28e1c7a40 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/FloatConstant.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represent a Java Virtual Machine float constant. +* +* @version 0.50, 05/13/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class FloatConstant extends Constant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected FloatConstant() + { + super(CONSTANT_FLOAT); + } + + /** + * Construct a constant whose value is a java float. + * + * @param flVal the java float + */ + public FloatConstant(float flVal) + { + this(); + m_flVal = flVal; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_flVal = stream.readFloat(); + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeFloat(m_flVal); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + FloatConstant that = (FloatConstant) obj; + + float flThis = this.m_flVal; + float flThat = that.m_flVal; + + return (flThis < flThat ? -1 : (flThis > flThat ? +1 : 0)); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Float) " + m_flVal; + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + return String.valueOf(m_flVal) + 'F'; + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + FloatConstant that = (FloatConstant) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_flVal == that.m_flVal; + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the value of the constant. + * + * @return the constant's float value + */ + public float getValue() + { + return m_flVal; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "FloatConstant"; + + /** + * The constant float value. + */ + private float m_flVal; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fmul.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fmul.java new file mode 100644 index 0000000000000..5801135e1136b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fmul.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FMUL simple op multiplies the second float in the stack by the +* first. +*

+* JASM op         :  FMUL  (0x6a)
+* JVM byte code(s):  FMUL  (0x6a)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fmul extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fmul() + { + super(FMUL); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fmul"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fneg.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fneg.java new file mode 100644 index 0000000000000..7afab1b58bbe8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fneg.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FNEG simple op computes the arithmetic negative of the float on the +* top of the stack. +*

+* JASM op         :  FNEG  (0x76)
+* JVM byte code(s):  FNEG  (0x76)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fneg extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fneg() + { + super(FNEG); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fneg"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fnewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fnewarray.java new file mode 100644 index 0000000000000..7d2eee3400941 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fnewarray.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FNEWARRAY pseudo-op instantiates an array of float. +*

+* JASM op         :  FNEWARRAY    (0xf9)
+* JVM byte code(s):  NEWARRAY     (0xbc)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fnewarray extends OpArray implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fnewarray() + { + super(FNEWARRAY); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fnewarray"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Frem.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Frem.java new file mode 100644 index 0000000000000..a6d205516b73c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Frem.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FREM simple op computes the remainder when the second float in the +* stack is divided by the first. +*

+* JASM op         :  FREM  (0x72)
+* JVM byte code(s):  FREM  (0x72)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Frem extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Frem() + { + super(FREM); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Frem"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Freturn.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Freturn.java new file mode 100644 index 0000000000000..a10c416e4608b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Freturn.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FRETURN simple op returns a float value from the method. +*

+* JASM op         :  FRETURN  (0xae)
+* JVM byte code(s):  FRETURN  (0xae)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Freturn extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Freturn() + { + super(FRETURN); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Freturn"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fstore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fstore.java new file mode 100644 index 0000000000000..392289ff3d6c7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fstore.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FSTORE variable-size op stores a float variable. +*

+* JASM op         :  FSTORE    (0x38)
+* JVM byte code(s):  FSTORE    (0x38)
+*                    FSTORE_0  (0x43)
+*                    FSTORE_1  (0x44)
+*                    FSTORE_2  (0x45)
+*                    FSTORE_3  (0x46)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fstore extends OpStore implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to pop + */ + public Fstore(Fvar var) + { + super(FSTORE, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fstore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fsub.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fsub.java new file mode 100644 index 0000000000000..38b4df168fd98 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fsub.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FSUB simple op subtracts the second float in the stack from the +* first. +*

+* JASM op         :  FSUB  (0x66)
+* JVM byte code(s):  FSUB  (0x66)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fsub extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fsub() + { + super(FSUB); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fsub"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fvar.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fvar.java new file mode 100644 index 0000000000000..444873983200b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Fvar.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The FVAR pseudo-op declares a float variable. The variable can +* optionally be named. +*

+* JASM op         :  FVAR  (0xee)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Fvar extends OpDeclare implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Fvar() + { + super(FVAR, null, null); + } + + /** + * Construct the op. + * + * @param sName the name of the variable + */ + public Fvar(String sName) + { + super(FVAR, sName, null); + } + + /** + * Construct the op. Used by disassembler. + * + * @param iVar the variable index + */ + protected Fvar(int iVar) + { + super(FVAR, null, null, iVar); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Fvar"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Getfield.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Getfield.java new file mode 100644 index 0000000000000..6bf259fd47b99 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Getfield.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The GETFIELD op accesses an object (instance) field. +*

+* JASM op         :  GETFIELD    (0xb4)
+* JVM byte code(s):  GETFIELD    (0xb4)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Getfield extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the FieldConstant + */ + public Getfield(FieldConstant constant) + { + super(GETFIELD, constant); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // pops object reference + // pushes value + return -1 + ((FieldConstant) getConstant()).getVariableSize(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Getfield"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Getstatic.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Getstatic.java new file mode 100644 index 0000000000000..e4fb18197061b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Getstatic.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The GETSTATIC op accesses a class (static) field. +*

+* JASM op         :  GETSTATIC    (0xb2)
+* JVM byte code(s):  GETSTATIC    (0xb2)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Getstatic extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the FieldConstant + */ + public Getstatic(FieldConstant constant) + { + super(GETSTATIC, constant); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // pushes value + return ((FieldConstant) getConstant()).getVariableSize(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Getstatic"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Goto.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Goto.java new file mode 100644 index 0000000000000..ba1d9a46e37bc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Goto.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The GOTO op branches unconditionally to the label. +*

+* JASM op         :  GOTO    (0xa7)
+* JVM byte code(s):  GOTO    (0xa7)
+*                    GOTO_W  (0xc8)
+* Details         :  The GOTO_W byte code is currently not produced by the
+*                    assembler.
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Goto extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Goto(Label label) + { + super(GOTO, label); + } + + + // ----- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + getLabel().getOffset(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Goto"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/GuardedSection.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/GuardedSection.java new file mode 100644 index 0000000000000..db78f158efcf0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/GuardedSection.java @@ -0,0 +1,361 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a section of byte code which is guarded. In Java, this is a +* the section of source after a try statement. This class is publicly +* immutable. +*

+* The GuardedSection information is part of the Code Attribute of a method, +* and is defined by the JDK 1.1 documentation as: +*

+*

+*   {
+*   u2 start_pc;
+*   u2 end_pc;
+*   u2 handler_pc;
+*   u2 catch_type;
+*   }
+* 
+*

+* The assembler builds guarded sections using the TRY and CATCH ops. A TRY +* op is simply a place-holder; it assembles to a 0-length binary. For a TRY, +* there are zero or more CATCH ops that reference the TRY, specify an +* exception to guard against, and provide an exception handler (via a LABEL). +* Like the TRY op, the CATCH op assembles to a 0-length binary, but each +* CATCH op encountered produces a GuardedSection structure. +*

+* Since TRY precedes the first guarded op and CATCH follows the last guarded +* op, the offset of the TRY is the offset of the guarded section of byte code +* and the offset of the CATCH is the offset of the first byte code following +* the guarded section of code. This is very handy because the JVM spec for +* guarded sections specifies the start_pc and end_pc values (see structure +* above) in this exact manner. +*

+* The exception being guarded against can be null, which means "catch any". +* This is used to implement the finally keyword in the Java language, for +* example. The guarded section information then specifies a zero for the +* catch_type value (see structure above). +* +* @version 0.50, 06/08/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class GuardedSection extends VMStructure implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor; typically used before disassembly. + */ + protected GuardedSection() + { + } + + /** + * Initializing constructor. + * + * @param opTry the TRY op preceding the first guarded op + * @param opCatch the CATCH op following the last guarded op + * @param clzExcept the Java class deriving from java.lang.Throwable + * which this section is guarded against; if null, all + * exceptions are guarded against + * @param opHandler the LABEL to transfer execution to when the specified + * exception occurs within the guarded section of code + */ + protected GuardedSection(Try opTry, Catch opCatch, ClassConstant clzExcept, Label opHandler) + { + m_opTry = opTry; + m_opCatch = opCatch; + m_clzExcept = clzExcept; + m_opHandler = opHandler; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + // read the guarded section information as integer offsets; defer + // translation into op references until the byte code is disassembled + m_ofTry = stream.readUnsignedShort(); + m_ofCatch = stream.readUnsignedShort(); + m_ofHandler = stream.readUnsignedShort(); + m_clzExcept = (ClassConstant) pool.getConstant(stream.readUnsignedShort()); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_clzExcept); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(m_opTry .getOffset()); + stream.writeShort(m_opCatch .getOffset()); + stream.writeShort(m_opHandler.getOffset()); + stream.writeShort(pool.findConstant(m_clzExcept)); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + // by default, use the disassembled offsets + int ofTry = m_ofTry; + int ofCatch = m_ofCatch; + int ofHandler = m_ofHandler; + + // if the ops are available (either because they were assembled as + // opposed to being disassembled or because the byte code has been + // fully disassembled), use the offsets from the ops instead + if (m_opTry != null) + { + ofTry = m_opTry .getOffset(); + ofCatch = m_opCatch .getOffset(); + ofHandler = m_opHandler.getOffset(); + } + + return "(" + CLASS + ")->" + + " on " + (m_clzExcept == null ? "any" : m_clzExcept.toString()) + + " in [" + ofTry + "," + ofCatch + ")" + + " goto " + ofHandler; + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + GuardedSection that = (GuardedSection) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_clzExcept.equals(that.m_clzExcept) + && this.m_opTry .equals(that.m_opTry ) + && this.m_opCatch .equals(that.m_opCatch ) + && this.m_opHandler.equals(that.m_opHandler); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the start of the guarded section of byte code. + * + * @return the offset in the byte code of the first guarded instruction + */ + protected int getTryOffset() + { + return m_ofTry; + } + + /** + * Get the TRY op which starts the guarded section. + * + * @return the TRY op + */ + public Try getTry() + { + return m_opTry; + } + + /** + * Set the TRY op. + * (Used internally by the byte code disassembly process.) + */ + protected void setTry(Try op) + { + m_opTry = op; + } + + + /** + * Determine the end of the guarded section of byte code. + * + * @return the offset in the byte code of the first instruction following + * the guarded section + */ + protected int getCatchOffset() + { + return m_ofCatch; + } + + /** + * Get the CATCH op which ends the guarded section. + * + * @return the CATCH op + */ + public Catch getCatch() + { + return m_opCatch; + } + + /** + * Set the CATCH op. + * (Used internally by the byte code disassembly process.) + */ + protected void setCatch(Catch op) + { + m_opCatch = op; + } + + + /** + * Get the exception which the section guards against. + * + * @return the caught exception or null for any + */ + public ClassConstant getException() + { + return m_clzExcept; + } + + + /** + * Determine the start of the guarded section of byte code. + * + * @return the offset in the byte code of the first guarded instruction + */ + protected int getHandlerOffset() + { + return m_ofHandler; + } + + /** + * Get the LABEL op for the exception handler + * + * @return the LABEL op + */ + public Label getHandler() + { + return m_opHandler; + } + + /** + * Set the LABEL op for the exception handler. + * (Used internally by the byte code disassembly process.) + */ + protected void setHandler(Label label) + { + m_opHandler = label; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "GuardedSection"; + + + /** + * The TRY op. + */ + private Try m_opTry; + + /** + * The CATCH op past the last guarded op. + */ + private Catch m_opCatch; + + /** + * The exception class guarded against. + */ + private ClassConstant m_clzExcept; + + /** + * The label where the handler is located. + */ + private Label m_opHandler; + + + /** + * The raw offset of the first guarded op. This value is set by this + * class's disassemble method and used by the byte code disassemble + * method to determine the actual TRY op. + */ + private int m_ofTry; + + /** + * The raw offset of the op following the last guarded op. This value + * is set by this class's disassemble method and used by the byte code + * disassemble method to determine the actual CATCH op. + */ + private int m_ofCatch; + + /** + * The raw offset of the exception handler. This value is set by this + * class's disassemble method and used by the byte code disassemble + * method to determine the actual LABEL op for the exception handler. + */ + private int m_ofHandler; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2b.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2b.java new file mode 100644 index 0000000000000..295668f1b0bb5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2b.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The I2B simple op converts the integer on the top of the stack to a +* byte. +*

+* JASM op         :  I2B  (0x91)
+* JVM byte code(s):  I2B  (0x91)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class I2b extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public I2b() + { + super(I2B); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "I2b"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2c.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2c.java new file mode 100644 index 0000000000000..3ce673e470fb2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2c.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The I2C simple op converts the integer on the top of the stack to a +* character. +*

+* JASM op         :  I2C  (0x92)
+* JVM byte code(s):  I2C  (0x92)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class I2c extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public I2c() + { + super(I2C); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "I2c"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2d.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2d.java new file mode 100644 index 0000000000000..6579b0319a658 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2d.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The I2D simple op converts the integer on the top of the stack to a +* double. +*

+* JASM op         :  I2D  (0x87)
+* JVM byte code(s):  I2D  (0x87)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class I2d extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public I2d() + { + super(I2D); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "I2d"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2f.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2f.java new file mode 100644 index 0000000000000..043ad42ce11c3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2f.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The I2F simple op converts the integer on the top of the stack to a +* float. +*

+* JASM op         :  I2F  (0x86)
+* JVM byte code(s):  I2F  (0x86)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class I2f extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public I2f() + { + super(I2F); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "I2f"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2l.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2l.java new file mode 100644 index 0000000000000..c7f8fe991045d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2l.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The I2L simple op converts the integer on the top of the stack to a +* long. +*

+* JASM op         :  I2L  (0x85)
+* JVM byte code(s):  I2L  (0x85)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class I2l extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public I2l() + { + super(I2L); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "I2l"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2s.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2s.java new file mode 100644 index 0000000000000..ac90b83df9fc3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/I2s.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The I2S simple op converts the integer on the top of the stack to a +* short. +*

+* JASM op         :  I2S  (0x93)
+* JVM byte code(s):  I2S  (0x93)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class I2s extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public I2s() + { + super(I2S); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "I2s"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iadd.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iadd.java new file mode 100644 index 0000000000000..ac9511f4138c4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iadd.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IADD simple op adds the top two integers in the stack. +*

+* JASM op         :  IADD  (0x60)
+* JVM byte code(s):  IADD  (0x60)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Iadd extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Iadd() + { + super(IADD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Iadd"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iaload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iaload.java new file mode 100644 index 0000000000000..bb10ddfc1d3a0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iaload.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IALOAD simple op pushes an integer element from an array onto the +* stack. +*

+* JASM op         :  IALOAD (0x2e)
+* JVM byte code(s):  IALOAD (0x2e)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Iaload extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Iaload() + { + super(IALOAD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Iaload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iand.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iand.java new file mode 100644 index 0000000000000..7e1a0182fab19 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iand.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IAND simple op bitwise and's the bits of the first and second +* integers in the stack. +*

+* JASM op         :  IAND  (0x7e)
+* JVM byte code(s):  IAND  (0x7e)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Iand extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Iand() + { + super(IAND); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Iand"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iastore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iastore.java new file mode 100644 index 0000000000000..caa8576f73614 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iastore.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IASTORE simple op stores an integer array element. +*

+* JASM op         :  IASTORE (0x4f)
+* JVM byte code(s):  IASTORE (0x4f)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Iastore extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Iastore() + { + super(IASTORE); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Iastore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iconst.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iconst.java new file mode 100644 index 0000000000000..57944aa01f195 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iconst.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ICONST variable-size op pushes an integer constant onto the stack. +*

+* JASM op         :  ICONST    (0xe5)
+* JVM byte code(s):  ICONST_M1 (0x02)
+*                    ICONST_0  (0x03)
+*                    ICONST_1  (0x04)
+*                    ICONST_2  (0x05)
+*                    ICONST_3  (0x06)
+*                    ICONST_4  (0x07)
+*                    ICONST_5  (0x08)
+*                    BIPUSH    (0x10??)
+*                    SIPUSH    (0x11????)
+*                    LDC       (0x12??)
+*                    LDC_W     (0x13????)
+* Details         :
+* 
+* +* @version 0.50, 06/11/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Iconst extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param n the int value + */ + public Iconst(int n) + { + this(new IntConstant(n)); + } + + /** + * Construct the op. + * + * @param constant the IntConstant to push + */ + public Iconst(IntConstant constant) + { + super(ICONST); + m_constant = constant; + + if (constant == null) + { + throw new IllegalArgumentException(CLASS + ": Constant must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_constant.format(), null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + m_constant.format(); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + // the range of "short" is optimizable using SIPUSH + IntConstant constant = m_constant; + int n = constant.getValue(); + if (n < Short.MIN_VALUE || n > Short.MAX_VALUE) + { + pool.registerConstant(constant); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + IntConstant constant = m_constant; + int n = constant.getValue(); + + // -1 through 5 are optimized to 1-byte codes + if (n >= -1 && n <= 5) + { + stream.writeByte(ICONST_0 + n); + } + // the range of "byte" is optimizable using BIPUSH + else if (n >= Byte.MIN_VALUE && n <= Byte.MAX_VALUE) + { + stream.writeByte(BIPUSH); + stream.writeByte(n); + } + // the range of "short" is optimizable using SIPUSH + else if (n >= Short.MIN_VALUE && n <= Short.MAX_VALUE) + { + stream.writeByte(SIPUSH); + stream.writeShort(n); + } + // otherwise use the constant pool + else + { + int iConst = pool.findConstant(constant); + if (iConst <= 0xFF) + { + stream.writeByte(LDC); + stream.writeByte(iConst); + } + else + { + stream.writeByte(LDC_W); + stream.writeShort(iConst); + } + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + IntConstant constant = m_constant; + int n = constant.getValue(); + + // -1 through 5 are optimized to 1-byte codes + if (n >= -1 && n <= 5) + { + setSize(1); + } + // the range of "byte" is optimizable using BIPUSH + else if (n >= Byte.MIN_VALUE && n <= Byte.MAX_VALUE) + { + setSize(2); + } + // the range of "short" is optimizable using SIPUSH + else if (n >= Short.MIN_VALUE && n <= Short.MAX_VALUE) + { + setSize(3); + } + // otherwise use the constant pool + else if (pool.findConstant(constant) <= 0xFF) + { + setSize(2); + } + else + { + setSize(3); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Iconst"; + + /** + * The integer constant loaded by this op. + */ + private IntConstant m_constant; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Idiv.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Idiv.java new file mode 100644 index 0000000000000..b4382aff6a7a6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Idiv.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IDIV simple op divides the second integer in the stack by the first. +*

+* JASM op         :  IDIV  (0x6c)
+* JVM byte code(s):  IDIV  (0x6c)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Idiv extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Idiv() + { + super(IDIV); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Idiv"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_acmpeq.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_acmpeq.java new file mode 100644 index 0000000000000..0783ca610d0b7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_acmpeq.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IF_ACMPEQ op branches to the label if the top two references on the +* stack are equal. +*

+* JASM op         :  IF_ACMPEQ    (0xa5)
+* JVM byte code(s):  IF_ACMPEQ    (0xa5)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class If_acmpeq extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public If_acmpeq(Label label) + { + super(IF_ACMPEQ, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "If_acmpeq"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_acmpne.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_acmpne.java new file mode 100644 index 0000000000000..e3c9df505c30f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_acmpne.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IF_ACMPNE op branches to the label if the top two references on the +* stack are not equal. +*

+* JASM op         :  IF_ACMPNE    (0xa6)
+* JVM byte code(s):  IF_ACMPNE    (0xa6)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class If_acmpne extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public If_acmpne(Label label) + { + super(IF_ACMPNE, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "If_acmpne"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpeq.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpeq.java new file mode 100644 index 0000000000000..34877f52e7375 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpeq.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IF_ICMPEQ op branches to the label if the top two integers on the stack +* are equal. +*

+* JASM op         :  IF_ICMPEQ    (0x9f)
+* JVM byte code(s):  IF_ICMPEQ    (0x9f)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class If_icmpeq extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public If_icmpeq(Label label) + { + super(IF_ICMPEQ, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "If_icmpeq"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpge.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpge.java new file mode 100644 index 0000000000000..67fd191abf72b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpge.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IF_ICMPGE op branches to the label if the second integer on the stack +* is greater than or equal to the first integer on the stack. +*

+* JASM op         :  IF_ICMPGE    (0xa2)
+* JVM byte code(s):  IF_ICMPGE    (0xa2)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class If_icmpge extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public If_icmpge(Label label) + { + super(IF_ICMPGE, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "If_icmpge"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpgt.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpgt.java new file mode 100644 index 0000000000000..6a6aa583f5700 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpgt.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IF_ICMPGT op branches to the label if the second integer on the stack +* is greater than the first integer on the stack. +*

+* JASM op         :  IF_ICMPGT    (0xa3)
+* JVM byte code(s):  IF_ICMPGT    (0xa3)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class If_icmpgt extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public If_icmpgt(Label label) + { + super(IF_ICMPGT, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "If_icmpgt"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmple.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmple.java new file mode 100644 index 0000000000000..88e8157aa5115 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmple.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IF_ICMPLE op branches to the label if the second integer on the stack +* is less than or equal to the first integer on the stack. +*

+* JASM op         :  IF_ICMPLE    (0xa4)
+* JVM byte code(s):  IF_ICMPLE    (0xa4)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class If_icmple extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public If_icmple(Label label) + { + super(IF_ICMPLE, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "If_icmple"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmplt.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmplt.java new file mode 100644 index 0000000000000..615ee2855b172 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmplt.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IF_ICMPLT op branches to the label if the second integer on the stack +* is less than the first integer on the stack. +*

+* JASM op         :  IF_ICMPLT    (0xa1)
+* JVM byte code(s):  IF_ICMPLT    (0xa1)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class If_icmplt extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public If_icmplt(Label label) + { + super(IF_ICMPLT, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "If_icmplt"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpne.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpne.java new file mode 100644 index 0000000000000..12a726c8815c7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/If_icmpne.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IF_ICMPNE op branches to the label if the top two integers on the stack +* are not equal. +*

+* JASM op         :  IF_ICMPNE    (0xa0)
+* JVM byte code(s):  IF_ICMPNE    (0xa0)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class If_icmpne extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public If_icmpne(Label label) + { + super(IF_ICMPNE, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "If_icmpne"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifeq.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifeq.java new file mode 100644 index 0000000000000..faf2db6d75969 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifeq.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IFEQ op branches to the label if the top integer on the stack is +* equal to zero. +*

+* JASM op         :  IFEQ    (0x99)
+* JVM byte code(s):  IFEQ    (0x99)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ifeq extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Ifeq(Label label) + { + super(IFEQ, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ifeq"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifge.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifge.java new file mode 100644 index 0000000000000..b3ec0d2203a94 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifge.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IFGE op branches to the label if the top integer on the stack is +* greater than or equal to zero. +*

+* JASM op         :  IFGE    (0x9c)
+* JVM byte code(s):  IFGE    (0x9c)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ifge extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Ifge(Label label) + { + super(IFGE, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ifge"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifgt.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifgt.java new file mode 100644 index 0000000000000..799b7e13fd2d7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifgt.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IFGT op branches to the label if the top integer on the stack is +* greater than zero. +*

+* JASM op         :  IFGT    (0x9d)
+* JVM byte code(s):  IFGT    (0x9d)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ifgt extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Ifgt(Label label) + { + super(IFGT, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ifgt"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifle.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifle.java new file mode 100644 index 0000000000000..fbad4dc77cf7a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifle.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IFLE op branches to the label if the top integer on the stack is less +* than or equal to zero. +*

+* JASM op         :  IFLE    (0x9e)
+* JVM byte code(s):  IFLE    (0x9e)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ifle extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Ifle(Label label) + { + super(IFLE, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ifle"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iflt.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iflt.java new file mode 100644 index 0000000000000..5a24f7aa85448 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iflt.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IFLT op branches to the label if the top integer on the stack is less +* than zero. +*

+* JASM op         :  IFLT    (0x9b)
+* JVM byte code(s):  IFLT    (0x9b)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Iflt extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Iflt(Label label) + { + super(IFLT, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Iflt"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifne.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifne.java new file mode 100644 index 0000000000000..f4d2e6c2f61fe --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifne.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IFNE op branches to the label if the top integer on the stack is not +* equal to zero. +*

+* JASM op         :  IFNE    (0x9a)
+* JVM byte code(s):  IFNE    (0x9a)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ifne extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Ifne(Label label) + { + super(IFNE, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ifne"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifnonnull.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifnonnull.java new file mode 100644 index 0000000000000..89c2f57a7932a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifnonnull.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IFNONNULL op branches to the label if the top reference on the stack is +* not null. +*

+* JASM op         :  IFNONNULL    (0xc7)
+* JVM byte code(s):  IFNONNULL    (0xc7)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ifnonnull extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Ifnonnull(Label label) + { + super(IFNONNULL, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ifnonnull"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifnull.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifnull.java new file mode 100644 index 0000000000000..1ad2f964917dd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ifnull.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IFNULL op branches to the label if the top reference on the stack is +* null. +*

+* JASM op         :  IFNULL    (0xc6)
+* JVM byte code(s):  IFNULL    (0xc6)
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ifnull extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Ifnull(Label label) + { + super(IFNULL, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ifnull"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iinc.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iinc.java new file mode 100644 index 0000000000000..a9bead2f5ab6e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iinc.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IINC variable-size op increments a local integer variable by a short +* value. +*

+* JASM op         :  IINC       (0x84)
+* JVM byte code(s):  IINC       (0x84????)
+*                    WIDE IINC  (0xC484????????)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Iinc extends Op implements Constants, OpVariable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the integer variable + * @param sInc the increment + */ + public Iinc(Ivar var, short sInc) + { + super(IINC); + m_var = var; + m_sInc = sInc; + + if (var == null) + { + throw new IllegalArgumentException(CLASS + + ": Variable must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_var.format() + ", " + m_sInc, null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + m_var.format() + ", " + m_sInc; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + int n = m_var.getSlot(); + int i = m_sInc; + + if (n > 0xFF || i < Byte.MIN_VALUE || i > Byte.MAX_VALUE) + { + stream.writeByte(WIDE); + stream.writeByte(IINC); + stream.writeShort(n); + stream.writeShort(i); + } + else + { + stream.writeByte(IINC); + stream.writeByte(n); + stream.writeByte(i); + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + int i = m_sInc; + if (m_var.getSlot() > 0xFF || i < Byte.MIN_VALUE || i > Byte.MAX_VALUE) + { + setSize(6); + } + else + { + setSize(3); + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the variable affected by this op. + * + * @return the variable + */ + public OpDeclare getVariable() + { + return m_var; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Iinc"; + + /** + * The integer variable to increment. + */ + private Ivar m_var; + + /** + * The integer increment. + */ + private short m_sInc; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iload.java new file mode 100644 index 0000000000000..1db7f5cb748b1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iload.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ILOAD variable-size op pushes an integer variable (one word) onto the +* stack. +*

+* JASM op         :  ILOAD    (0x15)
+* JVM byte code(s):  ILOAD    (0x15)
+*                    ILOAD_0  (0x1a)
+*                    ILOAD_1  (0x1b)
+*                    ILOAD_2  (0x1c)
+*                    ILOAD_3  (0x1d)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Iload extends OpLoad implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to push + */ + public Iload(Ivar var) + { + super(ILOAD, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Iload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Imul.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Imul.java new file mode 100644 index 0000000000000..57ca7edcb8137 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Imul.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IMUL simple op multiplies the second integer in the stack by the +* first. +*

+* JASM op         :  IMUL  (0x68)
+* JVM byte code(s):  IMUL  (0x68)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Imul extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Imul() + { + super(IMUL); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Imul"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ineg.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ineg.java new file mode 100644 index 0000000000000..2010e26b9b81a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ineg.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The INEG simple op computes the arithmetic negative of the integer on +* the top of the stack. +*

+* JASM op         :  INEG  (0x74)
+* JVM byte code(s):  INEG  (0x74)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ineg extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ineg() + { + super(INEG); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ineg"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Inewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Inewarray.java new file mode 100644 index 0000000000000..e136bea13638e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Inewarray.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The INEWARRAY pseudo-op instantiates an array of int. +*

+* JASM op         :  INEWARRAY    (0xf7)
+* JVM byte code(s):  NEWARRAY     (0xbc)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Inewarray extends OpArray implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Inewarray() + { + super(INEWARRAY); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Inewarray"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InnerClass.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InnerClass.java new file mode 100644 index 0000000000000..9df6708faed23 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InnerClass.java @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents Java Virtual Machine "inner class" information, part of the +* "InnerClasses" attribute. +*

+* The InnerClasses Attribute is defined by the JDK 1.1 documentation as: +*

+*

+*   InnerClasses_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u2 number_of_classes;
+*           {
+*           u2 inner_class_info_index;   // CONSTANT_Class_info index
+*           u2 outer_class_info_index;   // CONSTANT_Class_info index
+*           u2 inner_name_index;         // CONSTANT_Utf8_info index
+*           u2 inner_class_access_flags; // access_flags bitmask
+*           } classes[number_of_classes]
+*       }
+* 
+* +* @version 0.50, 05/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class InnerClass extends VMStructure implements Constants, Comparable + { + // ----- constructors --------------------------------------------------- + + /** + * Default constructor; typically used before disassembly. + */ + protected InnerClass() + { + } + + /** + * Initializing constructor. + * + * @param clzInner inner class constant + */ + protected InnerClass(ClassConstant clzInner) + { + m_clzInner = clzInner; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_clzInner = (ClassConstant) pool.getConstant(stream.readUnsignedShort()); + m_clzOuter = (ClassConstant) pool.getConstant(stream.readUnsignedShort()); + m_utfInner = (UtfConstant ) pool.getConstant(stream.readUnsignedShort()); + m_flags.disassemble(stream, pool); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_clzInner); + pool.registerConstant(m_clzOuter); + pool.registerConstant(m_utfInner); + m_flags.preassemble(pool); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(m_clzInner)); + stream.writeShort(pool.findConstant(m_clzOuter)); + stream.writeShort(pool.findConstant(m_utfInner)); + m_flags.assemble(stream, pool); + } + + /** + * Determine the identity of the VM structure (if applicable). + * + * @return the string identity of the VM structure + */ + public String getIdentity() + { + return m_clzInner.getValue(); + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified || m_flags.isModified(); + } + + /** + * Reset the modified state of the VM structure. + */ + protected void resetModified() + { + m_flags.resetModified(); + m_fModified = false; + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + InnerClass that = (InnerClass) obj; + return this.m_clzInner.compareTo(that.m_clzInner); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return "(" + CLASS + ")->" + m_clzInner.toString(); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + InnerClass that = (InnerClass) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_clzInner.equals(that.m_clzInner) + && (this.m_clzOuter == null ? that.m_clzOuter == null + : this.m_clzOuter.equals(that.m_clzOuter)) + && (this.m_utfInner == null ? that.m_utfInner == null + : this.m_utfInner.equals(that.m_utfInner)) + && this.m_flags .equals(that.m_flags); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Access the inner class "encoded" name. + * + * @return the encoded inner class name + */ + protected String getInnerClass() + { + return m_clzInner.getValue(); + } + + /** + * Access the inner class "encoded" name. + * + * @return the encoded inner class constant + */ + protected ClassConstant getInnerClassConstant() + { + return m_clzInner; + } + + /** + * Access the "defining scope" outer class. + * + * @return the outer class name or null + */ + protected String getOuterClass() + { + return (m_clzOuter != null ? m_clzOuter.getValue() : null); + } + + /** + * Access the "defining scope" outer class. + * + * @return the outer class constant or null + */ + protected ClassConstant getOuterClassConstant() + { + return m_clzOuter; + } + + /** + * Set the "defining scope" outer class. + * + * @param sOuter the outer class name or null + */ + protected void setOuterClass(String sOuter) + { + m_clzOuter = (sOuter != null ? new ClassConstant(sOuter) : null); + } + + /** + * Set the "defining scope" outer class. + * + * @param clzOuter the outer class constant or null + */ + protected void setOuterClassConstant(ClassConstant clzOuter) + { + m_clzOuter = clzOuter; + } + + /** + * Access the declared inner class name. + * + * @return the inner class simple name or null + */ + protected String getInnerName() + { + return (m_utfInner != null ? m_utfInner.getValue() : null); + } + + /** + * Access the declared inner class name. + * + * @return the inner class simple name constant or null + */ + protected UtfConstant getInnerNameConstant() + { + return m_utfInner; + } + + /** + * Set the declared inner class name. + * + * @param sInner the inner class simple name or null + */ + protected void setInnerName(String sInner) + { + m_utfInner = (sInner != null ? new UtfConstant(sInner) : null); + } + + /** + * Set the declared inner class name. + * + * @param utfInner the inner class simple name constant or null + */ + protected void setInnerNameConstant(UtfConstant utfInner) + { + m_utfInner = utfInner; + } + + /** + * Get the access flags for the inner class as they were declared in + * source; the access flags are typically modified by the compiler to + * follow the accessibility rules for inner classes as defined by the + * JDK 1.1. + * + * @return the declared access flags for the inner class + */ + protected AccessFlags getAccessFlags() + { + return m_flags; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "InnerClass"; + + /** + * Encoded name (i.e. assigned by compiler) of the inner class. + */ + private ClassConstant m_clzInner; + + /** + * "Defining scope" of the inner class. + */ + private ClassConstant m_clzOuter; + + /** + * Inner class name (as declared in the source). + */ + private UtfConstant m_utfInner; + + /** + * Access flags for the inner class (as declared in the source). + */ + private AccessFlags m_flags = new AccessFlags(); + + /** + * Tracks if the inner class information has been modified. + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InnerClassesAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InnerClassesAttribute.java new file mode 100644 index 0000000000000..3a55181dc0ae7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InnerClassesAttribute.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Enumeration; + +import com.tangosol.util.Tree; + + +/** +* Represents a Java Virtual Machine "inner class" attribute which contains +* relationship information tying together an inner and outer class. +*

+* The InnerClasses Attribute is defined by the JDK 1.1 documentation as: +*

+*

+*   InnerClasses_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u2 number_of_classes;
+*           {
+*           u2 inner_class_info_index;   // CONSTANT_Class_info index
+*           u2 outer_class_info_index;   // CONSTANT_Class_info index
+*           u2 inner_name_index;         // CONSTANT_Utf8_info index
+*           u2 inner_class_access_flags; // access_flags bitmask
+*           } classes[number_of_classes]
+*       }
+* 
+* +* @version 0.50, 05/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class InnerClassesAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct an inner classes attribute. + * + * @param context the JVM structure containing the attribute + */ + protected InnerClassesAttribute(VMStructure context) + { + super(context, ATTR_INNERCLASSES); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + Tree tbl = m_tblInner; + tbl.clear(); + + stream.readInt(); + int c = stream.readUnsignedShort(); + for (int i = 0; i < c; ++i) + { + InnerClass inner = new InnerClass(); + inner.disassemble(stream, pool); + tbl.put(inner.getIdentity(), inner); + } + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + + for (Enumeration enmr = m_tblInner.elements(); enmr.hasMoreElements(); ) + { + ((InnerClass) enmr.nextElement()).preassemble(pool); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + Tree tbl = m_tblInner; + int c = tbl.getSize(); + + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(2 + c * 8); + + stream.writeShort(c); + for (Enumeration enmr = m_tblInner.elements(); enmr.hasMoreElements(); ) + { + ((InnerClass) enmr.nextElement()).assemble(stream, pool); + } + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + if (m_fModified) + { + return true; + } + + for (Enumeration enmr = m_tblInner.elements(); enmr.hasMoreElements(); ) + { + if (((InnerClass) enmr.nextElement()).isModified()) + { + return true; + } + } + + return false; + } + + /** + * Reset the modified state of the VM structure. + */ + protected void resetModified() + { + for (Enumeration enmr = m_tblInner.elements(); enmr.hasMoreElements(); ) + { + ((InnerClass) enmr.nextElement()).resetModified(); + } + + m_fModified = false; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return super.getName() + ' ' + m_tblInner.toString(); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + InnerClassesAttribute that = (InnerClassesAttribute) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_tblInner.equals(that.m_tblInner); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Access an inner class information structure. + * + * @param sName the inner class name + * + * @return the specified inner class information structure or null + */ + public InnerClass getInnerClass(String sName) + { + return (InnerClass) m_tblInner.get(sName); + } + + /** + * Add an inner class information structure. + * + * @param sName the inner class encoded name + * + * @return the new inner class information structure + */ + public InnerClass addInnerClass(String sName) + { + InnerClass inner = new InnerClass(new ClassConstant(sName)); + m_tblInner.put(inner.getIdentity(), inner); + m_fModified = true; + return inner; + } + + /** + * Remove an inner class information structure. + * + * @param sName the inner class encoded name + */ + public void removeInnerClass(String sName) + { + m_tblInner.remove(sName); + m_fModified = true; + } + + /** + * Access the set of encoded inner class names. + * + * @return an enumeration of InnerClass identities + */ + public Enumeration getInnerClassNames() + { + return m_tblInner.keys(); + } + + /** + * Access the set of inner class information structures. + * + * @return an enumeration of InnerClass instances (not inner class names) + */ + public Enumeration getInnerClasses() + { + return m_tblInner.elements(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "InnerClassesAttribute"; + + /** + * Set of inner class information. + */ + private Tree m_tblInner = new Tree(); + + /** + * Tracks modifications to the inner classes attribute. + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Instanceof.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Instanceof.java new file mode 100644 index 0000000000000..ad0ea89da1f04 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Instanceof.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The INSTANCEOF op compares the class of the reference on the stack to the +* specified ClassConstant. +*

+* JASM op         :  INSTANCEOF    (0xc1)
+* JVM byte code(s):  INSTANCEOF    (0xc1)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Instanceof extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the ClassConstant to test a cast to + */ + public Instanceof(ClassConstant constant) + { + super(INSTANCEOF, constant); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Instanceof"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/IntConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/IntConstant.java new file mode 100644 index 0000000000000..c5ead1da7217d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/IntConstant.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represent a Java Virtual Machine integer constant. +* +* @version 0.50, 05/13/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class IntConstant extends Constant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected IntConstant() + { + super(CONSTANT_INTEGER); + } + + /** + * Construct a constant whose value is a java integer. + * + * @param nVal the java integer + */ + public IntConstant(int nVal) + { + this(); + m_nVal = nVal; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_nVal = stream.readInt(); + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeInt(m_nVal); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + IntConstant that = (IntConstant) obj; + + int nThis = this.m_nVal; + int nThat = that.m_nVal; + + return (nThis < nThat ? -1 : (nThis > nThat ? +1 : 0)); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Int) " + m_nVal; + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + return String.valueOf(m_nVal); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + IntConstant that = (IntConstant) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_nVal == that.m_nVal; + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the value of the constant. + * + * @return the constant's integer value + */ + public int getValue() + { + return m_nVal; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "IntConstant"; + + /** + * The constant integer value. + */ + private int m_nVal; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InterfaceConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InterfaceConstant.java new file mode 100644 index 0000000000000..a865ca3bfd15d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InterfaceConstant.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + + +/** +* Represents the method of an interface. +* +* @version 0.50, 05/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class InterfaceConstant extends MethodConstant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected InterfaceConstant() + { + super(CONSTANT_INTERFACEMETHODREF); + } + + /** + * Construct a constant which specifies an interface method. + * + * @param sClass the interface name + * @param sName the method name + * @param sType the method signature + */ + public InterfaceConstant(String sClass, String sName, String sType) + { + super(CONSTANT_INTERFACEMETHODREF, sClass, sName, sType); + } + + /** + * Construct a constant which references the passed constants. + * + * @param constantClz the referenced Class constant which contains the + * name of the interface + * @param constantSig the referenced Signature constant which contains + * the name and signature of the method + */ + public InterfaceConstant(ClassConstant constantClz, SignatureConstant constantSig) + { + super(CONSTANT_INTERFACEMETHODREF, constantClz, constantSig); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Interface)->" + super.toString(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "InterfaceConstant"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InvokeDynamicConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InvokeDynamicConstant.java new file mode 100644 index 0000000000000..b5f7c0a5595fa --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/InvokeDynamicConstant.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.dev.assembler; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** +* InvokeDynamicConstant represents a method invocation to a 'free' method +* bound at runtime by a CallSite as prescribed by the invokedynamic feature. +*

+* The InvokeDynamic Constant was defined by JDK 1.7 under bytecode +* version 51.0. The structure is defined as: +*

+*

+* CONSTANT_InvokeDynamic_info
+*     {
+*     u1 tag;
+*     u2 bootstrap_method_attr_index;
+*     u2 name_and_type_index;
+*     }
+* 
+* +* @author hr 2012.08.06 +*/ +public class InvokeDynamicConstant + extends Constant + { + + // ----- constructors --------------------------------------------------- + + /** + * Construct an InvokeDynamicConstant instance (tag = 18). + */ + protected InvokeDynamicConstant() + { + this(0, null); + } + + /** + * Construct an InvokeDynamicConstant instance (tag = 18). + * + * @param nBootstrapMethodIndex an index into the BootstrapMethods + * ClassFile attribute + * @param methodNameDesc {@link SignatureConstant} representing a + * method name and signature + */ + public InvokeDynamicConstant(int nBootstrapMethodIndex, SignatureConstant methodNameDesc) + { + super(CONSTANT_INVOKEDYNAMIC); + + m_nBootstrapIndex = nBootstrapMethodIndex; + m_methodNameDesc = methodNameDesc; + } + + // ----- Constant methods ----------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + m_nBootstrapIndex = stream.readUnsignedShort(); + m_nMethodNameDescIndex = stream.readUnsignedShort(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void postdisassemble(ConstantPool pool) + { + m_methodNameDesc = (SignatureConstant) pool.getConstant(m_nMethodNameDescIndex); + } + + /** + * {@inheritDoc} + */ + @Override + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_methodNameDesc); + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_nBootstrapIndex); + stream.writeShort(pool.findConstant(m_methodNameDesc)); + } + + // ----- Comparable methods --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(Object obj) + { + return obj instanceof InvokeDynamicConstant + ? ((InvokeDynamicConstant) obj).m_nBootstrapIndex - m_nBootstrapIndex + : 1; + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return "(InvokeDynamic)->[bootstrap_method_attr_index = " + m_nBootstrapIndex + ", " + + "method_name_desc = " + m_methodNameDesc + "]"; + } + + /** + * {@inheritDoc} + */ + @Override + public String format() + { + return m_methodNameDesc.format(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) + { + return obj instanceof InvokeDynamicConstant + ? m_nBootstrapIndex == ((InvokeDynamicConstant) obj).m_nBootstrapIndex + && m_methodNameDesc.equals(((InvokeDynamicConstant) obj).m_methodNameDesc) + : false; + } + + // ----- accessors ------------------------------------------------------ + + /** + * Returns an index into the BootstrapMethods ClassFile Attribute + * referring to a BootstrapMethod instance. + * + * @return an index into the BootstrapMethods ClassFile Attribute + */ + public int getBootstrapMethodIndex() + { + return m_nBootstrapIndex; + } + + /** + * Sets an index into the BootstrapMethods ClassFile Attribute + * referring to a BootstrapMethod instance. + * + * @param nBootstrapMethodIndex an index into the BootstrapMethods + * ClassFile Attribute + */ + public void setBootstrapMethodIndex(int nBootstrapMethodIndex) + { + m_nBootstrapIndex = nBootstrapMethodIndex; + } + + /** + * Returns the method name and signature represented by a + * {@link SignatureConstant}. In version 51.0 this constant has no + * runtime affect on the linking procedure induced by an invokedynamic + * instruction. + * + * @return method name and signature represented by a SignatureConstant + */ + public SignatureConstant getMethodNameAndDescription() + { + return m_methodNameDesc; + } + + /** + * Sets the method name and signature represented by a + * {@link SignatureConstant}. In version 51.0 this constant has no + * runtime affect on the linking procedure induced by an invokedynamic + * instruction. + * + * @param methodNameAndDescription method name and signature represented + * by a SignatureConstant + */ + public void setMethodNameAndDescription(SignatureConstant methodNameAndDescription) + { + m_methodNameDesc = methodNameAndDescription; + } + + // ----- data members --------------------------------------------------- + + /** + * An index into the BootstrapMethods ClassFile Attribute. + */ + private int m_nBootstrapIndex; + + /** + * A ConstantPool index of the {@link SignatureConstant} describing the + * method name and signature. + */ + private int m_nMethodNameDescIndex; + + /** + * The method name and signature represented by a SignatureConstant + */ + private SignatureConstant m_methodNameDesc; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokedynamic.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokedynamic.java new file mode 100644 index 0000000000000..742cae7d4fb85 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokedynamic.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.dev.assembler; + +import java.io.DataOutput; +import java.io.IOException; + +/** +* The INVOKEDYNAMIC op invokes a method against a class bound at runtime +* via a MethodHandle. +*

+* JASM op         :  INVOKEDYNAMIC    (0xba)
+* JVM byte code(s):  INVOKEDYNAMIC    (0xba)
+* Details         :
+* 
+* +* @author hr 2012.08.06 +*/ +public class Invokedynamic + extends OpConst + implements Constants + { + + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the InvokeDynamicConstant + */ + public Invokedynamic(InvokeDynamicConstant constant) + { + super(INVOKEDYNAMIC, constant); + } + + // ----- VMStructure operations ----------------------------------------- + + /** + * {@inheritDoc} + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeByte(INVOKEDYNAMIC); + stream.writeShort(pool.findConstant(super.getConstant())); + stream.writeByte(0); + stream.writeByte(0); + } + + // ----- Op operations -------------------------------------------------- + + /** + * {@inheritDoc} + */ + protected void calculateSize(ConstantPool pool) + { + setSize(5); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokeinterface.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokeinterface.java new file mode 100644 index 0000000000000..e2fd75a5b76b1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokeinterface.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The INVOKEINTERFACE op invokes a method (virtually) using an interface +* reference. +*

+* JASM op         :  INVOKEINTERFACE    (0xb9)
+* JVM byte code(s):  INVOKEINTERFACE    (0xb9)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Invokeinterface extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the InterfaceConstant + */ + public Invokeinterface(InterfaceConstant constant) + { + super(INVOKEINTERFACE, constant); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + // the byte code that invokes an interface method is really + // non-standard -- it sticks out like a sore thumb in the JVM + // specification because it reserves space for an optimization + // done only in Sun's JVM and it also carries redundant data + // (the number of arguments) + InterfaceConstant constant = (InterfaceConstant) super.getConstant(); + String[] asType = Method.toTypes(constant.getType()); + int cTypes = asType.length; + + // calculate the number of arguments, including the reference, + // in "words", where long and double types use two words and + // all other types use one word + int cArgs = 1; // "this" + for (int i = 1; i < cTypes; ++i) + { + char chType = asType[i].charAt(0); + cArgs += OpDeclare.getJavaWidth(chType); + } + + stream.writeByte(INVOKEINTERFACE); + stream.writeShort(pool.findConstant(constant)); + stream.writeByte(cArgs); + stream.writeByte(0); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + setSize(5); + } + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // pops reference and parameters + // pushes return value + InterfaceConstant constant = (InterfaceConstant) getConstant(); + return -1 - constant.getTotalParameterSize() + constant.getVariableSize(0); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Invokeinterface"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokespecial.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokespecial.java new file mode 100644 index 0000000000000..8e033484de42a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokespecial.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The INVOKESPECIAL op invokes a method (non-virtually) using a reference. +*

+* JASM op         :  INVOKESPECIAL    (0xb7)
+* JVM byte code(s):  INVOKESPECIAL    (0xb7)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Invokespecial extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + *

+ * As of JDK8 (52.0) InvokeSpecial may call an interface method to support + * default methods. + * + * @param constant the MethodConstant + */ + public Invokespecial(MethodConstant constant) + { + super(INVOKESPECIAL, constant); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // pops reference and parameters + // pushes return value + MethodConstant constant = (MethodConstant) getConstant(); + return -1 - constant.getTotalParameterSize() + constant.getVariableSize(0); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Invokespecial"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokestatic.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokestatic.java new file mode 100644 index 0000000000000..ce82621d79047 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokestatic.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The INVOKESTATIC op invokes a class (static) method. +*

+* JASM op         :  INVOKESTATIC    (0xb8)
+* JVM byte code(s):  INVOKESTATIC    (0xb8)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Invokestatic extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + *

+ * As of JDK8 (52.0) InvokeSpecial may call an interface method to support + * default methods. + * + * @param constant the MethodConstant + */ + public Invokestatic(MethodConstant constant) + { + super(INVOKESTATIC, constant); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // pops parameters + // pushes return value + MethodConstant constant = (MethodConstant) getConstant(); + return -constant.getTotalParameterSize() + constant.getVariableSize(0); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Invokestatic"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokevirtual.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokevirtual.java new file mode 100644 index 0000000000000..3448d239754b1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Invokevirtual.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The INVOKEVIRTUAL op invokes a virtual method using a reference. +*

+* JASM op         :  INVOKEVIRTUAL    (0xb6)
+* JVM byte code(s):  INVOKEVIRTUAL    (0xb6)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Invokevirtual extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the MethodConstant + */ + public Invokevirtual(MethodConstant constant) + { + super(INVOKEVIRTUAL, constant); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // pops reference and parameters + // pushes return value + MethodConstant constant = (MethodConstant) getConstant(); + return -1 - constant.getTotalParameterSize() + constant.getVariableSize(0); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Invokevirtual"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ior.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ior.java new file mode 100644 index 0000000000000..ce323edd68591 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ior.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IOR simple op bitwise or's the bits of the first and second integers +* in the stack. +*

+* JASM op         :  IOR  (0x80)
+* JVM byte code(s):  IOR  (0x80)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ior extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ior() + { + super(IOR); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ior"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Irem.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Irem.java new file mode 100644 index 0000000000000..293e0a95c112a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Irem.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IREM simple op computes the remainder when the second integer in the +* stack is divided by the first. +*

+* JASM op         :  IREM  (0x70)
+* JVM byte code(s):  IREM  (0x70)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Irem extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Irem() + { + super(IREM); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Irem"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ireturn.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ireturn.java new file mode 100644 index 0000000000000..f26ab4d597fab --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ireturn.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IRETURN simple op returns an integer value from the method. +*

+* JASM op         :  IRETURN  (0xac)
+* JVM byte code(s):  IRETURN  (0xac)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ireturn extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ireturn() + { + super(IRETURN); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ireturn"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ishl.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ishl.java new file mode 100644 index 0000000000000..07926f61ae049 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ishl.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ISHL simple op left-shifts the bits of the second integer in the +* stack by the number specified by the first integer in the stack. +*

+* JASM op         :  ISHL  (0x78)
+* JVM byte code(s):  ISHL  (0x78)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ishl extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ishl() + { + super(ISHL); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ishl"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ishr.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ishr.java new file mode 100644 index 0000000000000..3497be5b067a2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ishr.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ISHR simple op right-shifts the bits of the second integer in the +* stack by the number specified by the first integer in the stack. +*

+* JASM op         :  ISHR  (0x7a)
+* JVM byte code(s):  ISHR  (0x7a)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ishr extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ishr() + { + super(ISHR); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ishr"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Istore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Istore.java new file mode 100644 index 0000000000000..95bc29ebe4eeb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Istore.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ISTORE variable-size op stores an integer variable. +*

+* JASM op         :  ISTORE    (0x36)
+* JVM byte code(s):  ISTORE    (0x36)
+*                    ISTORE_0  (0x3b)
+*                    ISTORE_1  (0x3c)
+*                    ISTORE_2  (0x3d)
+*                    ISTORE_3  (0x3e)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Istore extends OpStore implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to pop + */ + public Istore(Ivar var) + { + super(ISTORE, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Istore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Isub.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Isub.java new file mode 100644 index 0000000000000..ee815c8715ae6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Isub.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ISUB simple op subtracts the second integer in the stack from the +* first. +*

+* JASM op         :  ISUB  (0x64)
+* JVM byte code(s):  ISUB  (0x64)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Isub extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Isub() + { + super(ISUB); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Isub"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iushr.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iushr.java new file mode 100644 index 0000000000000..f5134d87e3165 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Iushr.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IUSHR simple op right-shifts the bits (unsigned) of the second +* integer in the stack by the number specified by the first integer in the +* stack. +*

+* JASM op         :  IUSHR  (0x7c)
+* JVM byte code(s):  IUSHR  (0x7c)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Iushr extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Iushr() + { + super(IUSHR); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Iushr"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ivar.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ivar.java new file mode 100644 index 0000000000000..31dae45a144de --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ivar.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IVAR pseudo-op declares an integer variable. The variable can +* optionally be named. +*

+* JASM op         :  IVAR  (0xec)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ivar extends OpDeclare implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ivar() + { + super(IVAR, null, null); + } + + /** + * Construct the op. + * + * @param sName the name of the variable + */ + public Ivar(String sName) + { + super(IVAR, sName, null); + } + + /** + * Construct the op. + * + * @param sName the name of the variable + * @param sSig the signature of the reference type + * ('Z', 'B', 'C', 'S', or 'I') + */ + public Ivar(String sName, String sSig) + { + super(IVAR, sName, sSig); + } + + /** + * Construct the op. Used by disassembler. + * + * @param iVar the variable index + */ + protected Ivar(int iVar) + { + super(IVAR, null, null, iVar); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ivar"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ixor.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ixor.java new file mode 100644 index 0000000000000..87406971392cb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ixor.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The IXOR simple op bitwise exclusive or's the bits of the first and +* second integers in the stack. +*

+* JASM op         :  IXOR  (0x82)
+* JVM byte code(s):  IXOR  (0x82)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ixor extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ixor() + { + super(IXOR); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ixor"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Jsr.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Jsr.java new file mode 100644 index 0000000000000..00a6452198f6b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Jsr.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The JSR op branches to the label such that a RET statement returns to the +* byte code following the JSR statement. The JSR byte code causes a return +* address to be pushed onto the stack; this behavior is extraordinary when +* contrasted with other byte codes. +*

+* JASM op         :  JSR    (0xa8)
+* JVM byte code(s):  JSR    (0xa8)
+*                    JSR_W  (0xc9)
+* Details         :  The JSR_W byte code is currently not produced by the
+*                    assembler.
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Jsr extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the label to branch to + */ + public Jsr(Label label) + { + super(JSR, label); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + // if a JSR does not reach a RET, then assemble it as a GOTO + Label label = getLabel(); + int iOp = (label.getRet() == null ? GOTO : JSR); + + int ofBranch = label.getOffset() - this.getOffset(); + if (ofBranch < Short.MIN_VALUE || ofBranch > Short.MAX_VALUE) + { + throw new IllegalStateException(CLASS + + ".assemble: Branch offset out of range!"); + } + + stream.writeByte(iOp); + stream.writeShort(ofBranch); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // JSR is the worst exception to the rule ... the stack size after + // the JSR byte code executes has no relation to the stack size + // before it executes because the subroutine can modify the stack; + // when this method is called, the JSR expected stack height has been + // set up already and the LABEL has already been traced to its RET + // with the net change from the LABEL to the RET being stamped into + // the LABEL + int cwBefore = getStackHeight(); + int cwAfter = getLabel().getRetHeight(); + + if (cwBefore == UNKNOWN || cwAfter == UNKNOWN) + { + return UNKNOWN; + } + + return cwAfter - cwBefore; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + getLabel().getOffset(); + } + + + // ----- context + + /** + * Determine the code context of the JSR. + * + * @return the label that starts the code context within which the JSR is + * reached + */ + protected Label getContext() + { + return m_labelContext; + } + + /** + * Specify the code context for the JSR op. + * + * @param label the label that starts the code context within which the + * JSR is reached + */ + protected void setContext(Label label) + { + m_labelContext = label; + } + + /** + * Determine the subroutine depth of the subroutine called by the JSR. + * + * @return the maximum depth in the subroutine call chain that the + * subroutine invoked by the JSR op will be found + */ + protected int getDepth() + { + Label labelContext = m_labelContext; + return (labelContext == null ? 0 : labelContext.getDepth() + 1); + } + + + // ----- for variable allocation + + /** + * Get the first available variable slot. + * + * @return the first available variable slot + */ + protected int getFirstSlot() + { + return m_iSlot; + } + + /** + * Set the first available variable slot. + * + * @param iSlot the first available variable slot + */ + protected void setFirstSlot(int iSlot) + { + m_iSlot = iSlot; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Jsr"; + + /** + * The main/subroutine context containing this JSR. + */ + private Label m_labelContext; + + /** + * The JSR remembers how many variables were assigned when it was reached; + * this is used to determine the register (var slot) base for subroutines. + */ + private int m_iSlot = UNKNOWN; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/L2d.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/L2d.java new file mode 100644 index 0000000000000..50a7b2125b230 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/L2d.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The L2D simple op converts the long on the top of the stack to a double. +*

+* JASM op         :  L2D  (0x8a)
+* JVM byte code(s):  L2D  (0x8a)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class L2d extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public L2d() + { + super(L2D); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "L2d"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/L2f.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/L2f.java new file mode 100644 index 0000000000000..88ebb9c0caf9c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/L2f.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The L2F simple op converts the long on the top of the stack to a float. +*

+* JASM op         :  L2F  (0x89)
+* JVM byte code(s):  L2F  (0x89)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class L2f extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public L2f() + { + super(L2F); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "L2f"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/L2i.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/L2i.java new file mode 100644 index 0000000000000..72aca9cdf8bde --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/L2i.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The L2I simple op converts the long on the top of the stack to an +* integer. +*

+* JASM op         :  L2I  (0x88)
+* JVM byte code(s):  L2I  (0x88)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class L2i extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public L2i() + { + super(L2I); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "L2i"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Label.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Label.java new file mode 100644 index 0000000000000..1e12c0cebca1e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Label.java @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.util.Set; +import java.util.Enumeration; +import java.util.HashSet; + +import com.tangosol.util.NullImplementation; +import com.tangosol.util.SimpleEnumerator; + + +/** +* The LABEL op is a target of a branching instruction, switch case, or +* exception catch. A label can also be referred to as a code context in +* the following situations: +*

+*

    +*
  1. The label is the first op in the code (it is the "main" context) +*
  2. The label is a target of a JSR op (it is a subroutine context) +*
+*

+* JASM op         :  LABEL       (0xfb)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Label extends Op implements Constants, Comparable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Label(String sName) + { + super(LABEL); + m_sName = sName; + } + + /** + * Construct the op. + */ + public Label() + { + super(LABEL); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Determine if the op is discardable. Label ops, like Begin/End, are + * never considered discardable since they carry additional required + * information and since they do not affect execution. + * + * @return false always + */ + protected boolean isDiscardable() + { + return false; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(format(), null, null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return null; + } + + /** + * Produce a label name. + * + * @return the label name + */ + public String format() + { + String sName = m_sName; + + // hash code should be unique enough + if (sName == null) + { + sName = String.valueOf(hashCode()); + } + + return sName; + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + // order by depth + Label that = (Label) obj; + + int nThis = this.getDepth(); + int nThat = that.getDepth(); + + return (nThis < nThat ? -1 : (nThis > nThat ? +1 : 0)); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine if the label is the start of a subroutine. + * + * @return true if a reachable JSR op specifies this label as a target + */ + protected boolean isSubroutine() + { + return m_fSub; + } + + /** + * Determine the subroutine depth. + * + * @return the maximum depth in the subroutine call chain that this + * subroutine could be found + */ + protected int getDepth() + { + int iDepth = m_iDepth; + + if (iDepth == UNKNOWN) + { + iDepth = 0; + + // determine the depth by asking each of the JSR's how deep this + // subroutine is + for (Enumeration enmr = getCallers(); enmr.hasMoreElements(); ) + { + Jsr jsr = (Jsr) enmr.nextElement(); + int iNewDepth = jsr.getDepth(); + if (iNewDepth > iDepth) + { + iDepth = iNewDepth; + } + } + + m_iDepth = iDepth; + } + + return iDepth; + } + + + // ----- subroutine: terminating RET op + + /** + * Get the RET op which returns from this subroutine. This method is + * used only during assembly. + * + * @return the RET op which returns from this subroutine, or null if this + * label is not the entry point of a subroutine or if the RET op + * for the subroutine is not reachable + */ + protected Ret getRet() + { + return m_ret; + } + + /** + * Set the RET op which returns from this subroutine. This method is used + * only during assembly. + * + * @param ret the RET op which returns from this subroutine + */ + protected void setRet(Ret ret) + { + if (!m_fSub) + { + throw new IllegalStateException(CLASS + ".setRet: " + + "RET without JSR!"); + } + else if (m_ret == null) + { + m_ret = ret; + } + else if (m_ret != ret) + { + throw new IllegalStateException(CLASS + ".setRet: " + + "It is illegal to have multiple RET instructions for a JSR!"); + } + } + + + // ----- subroutine: stack height when RET op encountered + + /** + * Determine the stack height when the terminating RET op encountered. + * + * @return the height when the JSR op continues to the op following it + */ + protected int getRetHeight() + { + return m_cwRetHeight; + } + + /** + * Specify the current stack height when the terminating RET op for the + * subroutine is encountered. + * + * @param cw the height of the stack being returned to the JSR op + */ + protected void setRetHeight(int cw) + { + m_cwRetHeight = cw; + } + + + // ----- callers (JSR ops that call this label) + + /** + * Specify that the label is the start of a subroutine invoked by the + * specified JSR op. + * + * @param jsr a reachable op that invokes this subroutine + */ + protected void addCaller(Jsr jsr) + { + if (!m_fSub) + { + m_fSub = true; + m_setCallers = new HashSet(); + } + + m_setCallers.add(jsr); + m_iDepth = UNKNOWN; + } + + /** + * Enumeration the JSR ops which call this subroutine. + * + * @return an enumeration of JSR ops which specify this label + */ + protected Enumeration getCallers() + { + Set set = m_setCallers; + if (set == null) + { + return NullImplementation.getEnumeration(); + } + else + { + return new SimpleEnumerator(set.toArray()); + } + } + + + // ----- for identifying code contexts + + /** + * Get the code context's index. + * + * @return the index assigned to the code context + */ + protected int getContextIndex() + { + return m_iCtx; + } + + /** + * Set the code context's index. + * + * @param iCtx the index assigned to the code context + */ + protected void setContextIndex(int iCtx) + { + m_iCtx = iCtx; + } + + + // ----- for variable allocation + + /** + * Get the first available variable slot. + * + * @return the first available variable slot + */ + protected int getFirstSlot() + { + return m_iSlot; + } + + /** + * Set the first available variable slot. + * + * @param iSlot the first available variable slot + */ + protected void setFirstSlot(int iSlot) + { + m_iSlot = iSlot; + } + + + // ----- code context + + /** + * Determine the code context of the label. + * + * @return the label that starts the code context containing this label + */ + protected Label getContext() + { + return m_labelContext; + } + + /** + * Specify the code context for the label. + * + * @param label the label that starts the code context containing this + * label + */ + protected void setContext(Label label) + { + m_labelContext = label; + } + + + // ----- for definite assignment + + /** + * Internal use by assembler. For all labels. + */ + protected HashSet getVariables() + { + return m_setVars; + } + + /** + * Internal use by assembler. For all labels. + */ + protected void setVariables(HashSet setVars) + { + m_setVars = setVars; + } + + /** + * Determine if the label has been visited by the definite assignment + * pass. + * + * @return true if visited + */ + protected boolean isVisited() + { + return m_fVisited; + } + + /** + * Specify that the label has been visited by the definite assignment + * pass. + * + * @param fVisited true if visited + */ + protected void setVisited(boolean fVisited) + { + m_fVisited = fVisited; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Label"; + + /** + * For all labels, the optional name of the label. + */ + private String m_sName; + + /** + * For all labels, the code context which this label is reachable within. + */ + private Label m_labelContext; + + /** + * For all labels, what variables are currently in scope and (for definite + * assignment determination) which variables do and do not have a value. + */ + private HashSet m_setVars; + + /** + * For all labels, has this label been visited by the definite assignment + * determination pass? + */ + private boolean m_fVisited; + + /** + * For a code context (a label which starts either the main code context + * or a label which is the entry point of a subroutine), this value is an + * arbitrary index assigned by the assembler to differentiate contexts. + */ + private int m_iCtx = UNKNOWN; + + /** + * For a code context (a label which starts either the main code context + * or a label which is the entry point of a subroutine), this value is + * the first absolute register slot that will be assigned to a variable. + */ + private int m_iSlot = UNKNOWN; + + /** + * Is this label the entry point of a subroutine? + */ + private boolean m_fSub; + + /** + * If this label is the entry point of a subroutine, how deep is the + * subroutine? + */ + private int m_iDepth = UNKNOWN; + + /** + * If this label is the entry point of a subroutine, this is the RET + * instruction that returns from the subroutine or null if no RET + * instruction is reachable. + */ + private Ret m_ret; + + /** + * If this label is the entry point of a subroutine, this is the height + * of the stack when the RET is encountered. + */ + private int m_cwRetHeight = UNKNOWN; + + /** + * If this label is the entry point of a subroutine, this is the set of + * reachable JSR ops which invoke this subroutine. + */ + private Set m_setCallers; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ladd.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ladd.java new file mode 100644 index 0000000000000..0db04327c31fb --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ladd.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LADD simple op adds the top two longs in the stack. +*

+* JASM op         :  LADD  (0x61)
+* JVM byte code(s):  LADD  (0x61)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ladd extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ladd() + { + super(LADD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ladd"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Laload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Laload.java new file mode 100644 index 0000000000000..7e43901dd58f8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Laload.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LALOAD simple op pushes a long element (two words) from an array onto +* the stack. +*

+* JASM op         :  LALOAD (0x2f)
+* JVM byte code(s):  LALOAD (0x2f)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Laload extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Laload() + { + super(LALOAD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Laload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Land.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Land.java new file mode 100644 index 0000000000000..653df3ec0e6e0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Land.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LAND simple op bitwise and's the bits of the first and second longs +* in the stack. +*

+* JASM op         :  LAND  (0x7f)
+* JVM byte code(s):  LAND  (0x7f)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Land extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Land() + { + super(LAND); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Land"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lastore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lastore.java new file mode 100644 index 0000000000000..ba9aa71fbb08b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lastore.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LASTORE simple op stores a long array element. +*

+* JASM op         :  LASTORE (0x50)
+* JVM byte code(s):  LASTORE (0x50)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lastore extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lastore() + { + super(LASTORE); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lastore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lcmp.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lcmp.java new file mode 100644 index 0000000000000..53c7e1cde6708 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lcmp.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LCMP simple op compares the two longs on the top of the stack. +*

+* JASM op         :  LCMP  (0x94)
+* JVM byte code(s):  LCMP  (0x94)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lcmp extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lcmp() + { + super(LCMP); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lcmp"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lconst.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lconst.java new file mode 100644 index 0000000000000..9606c8772b749 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lconst.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LCONST variable-size op pushes a long integer constant onto the stack. +*

+* JASM op         :  LCONST    (0xe6)
+* JVM byte code(s):  LCONST_0  (0x09)
+*                    LCONST_1  (0x0a)
+*                    LDC2_W    (0x14????)
+* Details         :
+* 
+* +* @version 0.50, 06/11/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lconst extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param l the long value + */ + public Lconst(long l) + { + this(new LongConstant(l)); + } + + /** + * Construct the op. + * + * @param constant the LongConstant to push + */ + public Lconst(LongConstant constant) + { + super(LCONST); + m_constant = constant; + + if (constant == null) + { + throw new IllegalArgumentException(CLASS + ": Constant must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_constant.format(), null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + m_constant.format(); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + // 0 and 1 are optimizable + LongConstant constant = m_constant; + long n = constant.getValue(); + if (n != 0L && n != 1L) + { + pool.registerConstant(constant); + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + LongConstant constant = m_constant; + long n = constant.getValue(); + + // 0L and 1L are optimizable + if (n == 0L || n == 1L) + { + stream.writeByte(LCONST_0 + (int)n); + } + else + { + stream.writeByte(LDC2_W); + stream.writeShort(pool.findConstant(constant)); + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + long n = m_constant.getValue(); + setSize(n == 0L || n == 1L ? 1 : 3); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lconst"; + + /** + * The long integer constant loaded by this op. + */ + private LongConstant m_constant; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ldiv.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ldiv.java new file mode 100644 index 0000000000000..709dbdf8d6cd0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ldiv.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LDIV simple op divides the second long in the stack by the first. +*

+* JASM op         :  LDIV  (0x6d)
+* JVM byte code(s):  LDIV  (0x6d)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ldiv extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Ldiv() + { + super(LDIV); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ldiv"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LineNumberTableAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LineNumberTableAttribute.java new file mode 100644 index 0000000000000..6c3231e405dcf --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LineNumberTableAttribute.java @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Arrays; +import java.util.Enumeration; +import java.util.NoSuchElementException; + + +/** +* Represents a Java Virtual Machine byte-code "LineNumberTable" attribute +* which cross-references between byte-code pc and source file line number. +* +* @version 0.50, 05/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class LineNumberTableAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a LineNumberTable attribute. + * + * @param context the JVM structure containing the attribute + */ + protected LineNumberTableAttribute(VMStructure context) + { + super(context, ATTR_LINENUMBERS); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + + int count = stream.readUnsignedShort(); + if (count > 0) + { + // disassemble the line number information + Entry[] aln = new Entry[count]; + for (int i = 0; i < count; ++i) + { + Entry ln = new Entry(); + ln.disassemble(stream, pool); + aln[i] = ln; + } + + // sort the line number information by offset (to assist in + // the op decompilation process) + Arrays.sort(aln); + + // build a linked list + for (int i = 1; i < count; ++i) + { + aln[i-1].setNext(aln[i]); + } + + m_first = aln[0]; + m_last = aln[count-1]; + m_count = count; + } + else + { + m_first = null; + m_last = null; + m_count = 0; + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(2 + 4 * m_count); + stream.writeShort(m_count); + + for (Entry ln = m_first; ln != null; ln = ln.getNext()) + { + ln.assemble(stream, pool); + } + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + * + * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Discard any line number information. + */ + public void clear() + { + m_first = null; + m_last = null; + m_fModified = true; + } + + /** + * Determine if there is no line number information. + * + * @return true if there is no line number information, false otherwise + */ + public boolean isEmpty() + { + return m_first == null; + } + + /** + * Add the specified line number information. + * + * @param iLine the line number + * @param of the offset (pc) of the byte code + */ + protected void add(int iLine, int of) + { + add(new Entry(iLine, of)); + } + + /** + * A line change was detected on this op; add its line number info. + * + * @param op + */ + protected void add(Op op) + { + add(new Entry(op)); + } + + /** + * Add the specified line number information. + */ + protected void add(Entry ln) + { + if (m_first == null) + { + m_first = ln; + m_last = ln; + } + else + { + m_last.setNext(ln); + m_last = ln; + } + + ++m_count; + m_fModified = true; + } + + /** + * Enumerate the line number information. + * + * @return an enumeration of Entry objects + */ + public Enumeration entries() + { + return new Enumeration() + { + public boolean hasMoreElements() + { + return cur != null; + } + + public Object nextElement() + { + if (cur == null) + { + throw new NoSuchElementException(); + } + + Entry ln = cur; + cur = ln.getNext(); + return ln; + } + + private Entry cur = m_first; + }; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "LineNumberTableAttribute"; + + /** + * Tracks modification to this object. + */ + private boolean m_fModified; + + /** + * The first line number. + */ + private Entry m_first; + + /** + * The last line number. + */ + private Entry m_last; + + /** + * The count of line number objects. + */ + private int m_count; + + + // ----- inner classes -------------------------------------------------- + + /** + * Represents a piece of debugging information that cross-references a + * Java Virtual Machine byte-code offset with a line of source code. + * + * @version 0.50, 06/22/98, assembler/dis-assembler + * @author Cameron Purdy + */ + public static class Entry extends VMStructure implements Constants, Comparable + { + // ----- constructors ---------------------------------------------- + + /** + * Construct a Entry object. + */ + protected Entry() + { + } + + /** + * Construct a Entry object. Used during disassembly. + * + * @param iLine + * @param of + */ + protected Entry(int iLine, int of) + { + m_iLine = iLine; + m_of = of; + } + + /** + * Construct a Entry object. Used during assembly. + * + * @param op + */ + protected Entry(Op op) + { + m_iLine = op.getLine(); + m_op = op; + } + + + // ----- VMStructure operations ------------------------------------ + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_of = stream.readUnsignedShort(); + m_iLine = stream.readUnsignedShort(); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(getOffset()); + stream.writeShort(getLine()); + } + + + // ----- Comparable operations ------------------------------------- + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + // order by PC + Entry that = (Entry) obj; + + int nThis = this.m_of; + int nThat = that.m_of; + + return (nThis < nThat ? -1 : (nThis > nThat ? +1 : 0)); + } + + + // ----- accessors ------------------------------------------------- + + /** + * Get the op. + * + * @return the op which starts this line number + */ + public Op getOp() + { + return m_op; + } + + /** + * Set the op which the line number starts on. This is used during + * the disassembly process when the op is constructed that corresponds + * to the offset that this line number contains. + * + * @param op the first op for this line number + */ + protected void setOp(Op op) + { + m_op = op; + } + + /** + * Get the line number. + * + * @return the line number + */ + public int getLine() + { + return m_iLine; + } + + /** + * Get the byte code offset ("pc") of the line number + * + * @return the byte code offset + */ + protected int getOffset() + { + return (m_op == null ? m_of : m_op.getOffset()); + } + + /** + * Get the next Entry object in the linked list. + * + * @return the next Entry object + */ + protected Entry getNext() + { + return m_next; + } + + /** + * Set the next Entry object in the linked list. + * + * @param next the next Entry object + */ + protected void setNext(Entry next) + { + m_next = next; + } + + + // ----- data members ---------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "LineNumberTableAttribute.Entry"; + + /** + * The line number. + */ + private int m_iLine; + + /** + * The op. + */ + private Op m_op; + + /** + * The byte code offset (if op is not available). + */ + private int m_of; + + /** + * The next line number object in the linked list. + */ + private Entry m_next; + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lload.java new file mode 100644 index 0000000000000..6d1c492e5dfec --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lload.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LLOAD variable-size op pushes a long variable (two words) onto the +* stack. +*

+* JASM op         :  LLOAD    (0x16)
+* JVM byte code(s):  LLOAD    (0x16)
+*                    LLOAD_0  (0x1e)
+*                    LLOAD_1  (0x1f)
+*                    LLOAD_2  (0x20)
+*                    LLOAD_3  (0x21)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lload extends OpLoad implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to push + */ + public Lload(Lvar var) + { + super(LLOAD, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lmul.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lmul.java new file mode 100644 index 0000000000000..9e93da2977fd0 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lmul.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LMUL simple op multiplies the second long in the stack by the first. +*

+* JASM op         :  LMUL  (0x69)
+* JVM byte code(s):  LMUL  (0x69)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lmul extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lmul() + { + super(LMUL); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lmul"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lneg.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lneg.java new file mode 100644 index 0000000000000..7ce0fbd9f1eae --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lneg.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LNEG simple op computes the arithmetic negative of the long on the +* top of the stack. +*

+* JASM op         :  LNEG  (0x75)
+* JVM byte code(s):  LNEG  (0x75)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lneg extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lneg() + { + super(LNEG); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lneg"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lnewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lnewarray.java new file mode 100644 index 0000000000000..450213d342f8e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lnewarray.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LNEWARRAY pseudo-op instantiates an array of long. +*

+* JASM op         :  LNEWARRAY    (0xf8)
+* JVM byte code(s):  NEWARRAY     (0xbc)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lnewarray extends OpArray implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lnewarray() + { + super(LNEWARRAY); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lnewarray"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LocalVariableTableAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LocalVariableTableAttribute.java new file mode 100644 index 0000000000000..e1549a7cc01dc --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LocalVariableTableAttribute.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.TreeSet; +import java.util.Iterator; + + +/** +* Represents a Java Virtual Machine byte-code "LocalVariableTable" attribute +* which cross-references between byte-code variable references by index and +* source file variable references by name; also includes scope information +* of which portion of the code the variable "exists" in. +* +* @version 0.50, 05/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class LocalVariableTableAttribute + extends AbstractLocalVariableTableAttribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a LocalVariableTable attribute. + * + * @param context the JVM structure containing the attribute + */ + protected LocalVariableTableAttribute(VMStructure context) + { + super(context, ATTR_VARIABLES); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LocalVariableTypeTableAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LocalVariableTypeTableAttribute.java new file mode 100644 index 0000000000000..d54fec41123a4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LocalVariableTypeTableAttribute.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.TreeSet; +import java.util.Iterator; + + +/** +* Represents a Java Virtual Machine byte-code "LocalVariableTypeTable" +* attribute which cross-references between byte-code variable +* references by index and source file variable references by type; +* also includes scope information of which portion of the code the +* variable "exists" in. +* +* @author rhl 2008.09.23 +*/ +public class LocalVariableTypeTableAttribute extends AbstractLocalVariableTableAttribute + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a LocalVariableTypeTable attribute. + * + * @param context the JVM structure containing the attribute + */ + protected LocalVariableTypeTableAttribute(VMStructure context) + { + super(context, ATTR_VARIABLETYPES); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LongConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LongConstant.java new file mode 100644 index 0000000000000..ca477643e415a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/LongConstant.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represent a Java Virtual Machine long constant. +* +* @version 0.50, 05/13/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class LongConstant extends Constant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected LongConstant() + { + super(CONSTANT_LONG); + } + + /** + * Construct a constant whose value is a java long. + * + * @param lVal the java long + */ + public LongConstant(long lVal) + { + this(); + m_lVal = lVal; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_lVal = stream.readLong(); + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeLong(m_lVal); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + LongConstant that = (LongConstant) obj; + + long lThis = this.m_lVal; + long lThat = that.m_lVal; + + return (lThis < lThat ? -1 : (lThis > lThat ? +1 : 0)); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Long) " + m_lVal; + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + return String.valueOf(m_lVal) + 'L'; + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + LongConstant that = (LongConstant) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_lVal == that.m_lVal; + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the value of the constant. + * + * @return the constant's long value + */ + public long getValue() + { + return m_lVal; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "LongConstant"; + + /** + * The constant long value. + */ + private long m_lVal; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lookupswitch.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lookupswitch.java new file mode 100644 index 0000000000000..86318768549dd --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lookupswitch.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LOOKUPSWITCH op is a binary searchable list of values with associated +* jumps with an additional default jump. +*

+* JASM op         :  LOOKUPSWITCH   (0xab)
+* JVM byte code(s):  LOOKUPSWITCH   (0xab)
+* Details         :
+* 
+* +* @version 0.50, 06/17/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lookupswitch extends OpSwitch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the default label to branch to if no cases match + */ + public Lookupswitch(Label label) + { + super(LOOKUPSWITCH, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lookupswitch"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lor.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lor.java new file mode 100644 index 0000000000000..b41420d5144c4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lor.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LOR simple op bitwise or's the bits of the first and second longs in +* the stack. +*

+* JASM op         :  LOR  (0x81)
+* JVM byte code(s):  LOR  (0x81)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lor extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lor() + { + super(LOR); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lor"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lrem.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lrem.java new file mode 100644 index 0000000000000..40b0ba2adaf31 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lrem.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LREM simple op computes the remainder when the second long in the +* stack is divided by the first. +*

+* JASM op         :  LREM  (0x71)
+* JVM byte code(s):  LREM  (0x71)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lrem extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lrem() + { + super(LREM); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lrem"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lreturn.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lreturn.java new file mode 100644 index 0000000000000..7cdbc4e900f23 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lreturn.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LRETURN simple op returns a long value from the method. +*

+* JASM op         :  LRETURN  (0xad)
+* JVM byte code(s):  LRETURN  (0xad)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lreturn extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lreturn() + { + super(LRETURN); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lreturn"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lshl.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lshl.java new file mode 100644 index 0000000000000..d3f8cde2bc0f1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lshl.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LSHL simple op left-shifts the bits of the second long in the stack +* by the number specified by the first integer in the stack. +*

+* JASM op         :  LSHL  (0x79)
+* JVM byte code(s):  LSHL  (0x79)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lshl extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lshl() + { + super(LSHL); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lshl"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lshr.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lshr.java new file mode 100644 index 0000000000000..46cebfabb4f0b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lshr.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LSHR simple op right-shifts the bits of the second long in the stack +* by the number specified by the first integer in the stack. +*

+* JASM op         :  LSHR  (0x7b)
+* JVM byte code(s):  LSHR  (0x7b)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lshr extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lshr() + { + super(LSHR); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lshr"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lstore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lstore.java new file mode 100644 index 0000000000000..49a2d2443a619 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lstore.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LSTORE variable-size op stores a long variable. +*

+* JASM op         :  LSTORE    (0x37)
+* JVM byte code(s):  LSTORE    (0x37)
+*                    LSTORE_0  (0x3f)
+*                    LSTORE_1  (0x40)
+*                    LSTORE_2  (0x41)
+*                    LSTORE_3  (0x42)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lstore extends OpStore implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to pop + */ + public Lstore(Lvar var) + { + super(LSTORE, var); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lstore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lsub.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lsub.java new file mode 100644 index 0000000000000..7bc5c22e0135d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lsub.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LSUB simple op subtracts the second long in the stack from the +* first. +*

+* JASM op         :  LSUB  (0x65)
+* JVM byte code(s):  LSUB  (0x65)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lsub extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lsub() + { + super(LSUB); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lsub"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lushr.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lushr.java new file mode 100644 index 0000000000000..48b64c128ef2a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lushr.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LUSHR simple op right-shifts the bits (unsigned) of the second long +* in the stack by the number specified by the first integer in the stack. +*

+* JASM op         :  LUSHR  (0x7d)
+* JVM byte code(s):  LUSHR  (0x7d)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lushr extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lushr() + { + super(LUSHR); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lushr"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lvar.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lvar.java new file mode 100644 index 0000000000000..e335d20e26e92 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lvar.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LVAR pseudo-op declares a long variable. The variable can +* optionally be named. +*

+* JASM op         :  LVAR  (0xed)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lvar extends OpDeclare implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lvar() + { + super(LVAR, null, null); + } + + /** + * Construct the op. + * + * @param sName the name of the variable + */ + public Lvar(String sName) + { + super(LVAR, sName, null); + } + + /** + * Construct the op. Used by disassembler. + * + * @param iVar the variable index + */ + protected Lvar(int iVar) + { + super(LVAR, null, null, iVar); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lvar"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lxor.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lxor.java new file mode 100644 index 0000000000000..ab180a625bb8c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Lxor.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The LXOR simple op bitwise exclusive or's the bits of the first and +* second longs in the stack. +*

+* JASM op         :  LXOR  (0x83)
+* JVM byte code(s):  LXOR  (0x83)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Lxor extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Lxor() + { + super(LXOR); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Lxor"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Method.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Method.java new file mode 100644 index 0000000000000..90b7c544ace9b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Method.java @@ -0,0 +1,1274 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import com.tangosol.util.NullImplementation; +import com.tangosol.util.StringTable; + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Enumeration; +import java.util.Vector; + + +/** +* Represents a Java Virtual Machine Method structure as defined by the Java +* Virtual Machine (JVM) Specification. +* +* @version 0.50, 05/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Method extends VMStructure implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Construct a method structure. Used by ClassFile disassembly. + * + * @param sClass the class name containing this method + * @param fInterface whether this method is defined on an interface + */ + protected Method(String sClass, boolean fInterface) + { + m_sClass = sClass; + f_fInterface = fInterface; + } + + /** + * Construct a method structure. + * + * @param sName the method name + * @param sSig the method signature + * @param fInterface whether this method is defined on an interface + */ + protected Method(String sName, String sSig, boolean fInterface) + { + this(new UtfConstant(sName), new UtfConstant(sSig.replace('.','/')), fInterface); + } + + /** + * Construct a method which references the passed UTF constants. + * + * @param constantName the referenced UTF constant which contains the + * name of the method + * @param constantSig the referenced UTF constant which contains the + * method signature + * @param fInterface whether this method is defined on an interface + */ + protected Method(UtfConstant constantName, UtfConstant constantSig, boolean fInterface) + { + if (constantName == null || constantSig == null) + { + throw new IllegalArgumentException(CLASS + ": Values cannot be null!"); + } + + m_utfName = constantName; + m_utfSig = constantSig; + f_fInterface = fInterface; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + // access flags + m_flags.disassemble(stream, pool); + + // name and signature + m_utfName = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + m_utfSig = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + + // attributes + m_tblAttribute.clear(); + int cAttr = stream.readUnsignedShort(); + for (int i = 0; i < cAttr; ++i) + { + Attribute attr = Attribute.loadAttribute(this, stream, pool); + m_tblAttribute.put(attr.getIdentity(), attr); + } + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utfName); + pool.registerConstant(m_utfSig ); + + m_flags.preassemble(pool); + + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + Attribute attr = (Attribute) enmr.nextElement(); + try + { + attr.preassemble(pool); + } + catch (Throwable e) + { + if (attr.getName().equals(ATTR_CODE)) + { + out("Code pre-assembly error in: " + toString()); + ((CodeAttribute) attr).print(); + } + + if (e instanceof RuntimeException) + { + throw (RuntimeException) e; + } + else + { + throw (Error) e; + } + } + } + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + m_flags.assemble(stream, pool); + + stream.writeShort(pool.findConstant(m_utfName)); + stream.writeShort(pool.findConstant(m_utfSig )); + + stream.writeShort(m_tblAttribute.getSize()); + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + Attribute attr = (Attribute) enmr.nextElement(); + try + { + attr.assemble(stream, pool); + } + catch (Throwable e) + { + if (attr.getName().equals(ATTR_CODE)) + { + out("Code assembly error in: " + toString()); + ((CodeAttribute) attr).print(); + } + + if (e instanceof RuntimeException) + { + throw (RuntimeException) e; + } + else + { + throw (Error) e; + } + } + } + } + + /** + * Determine the identity of the VM structure (if applicable). + * + * @return the string identity of the VM structure + */ + public String getIdentity() + { + return m_utfName.getValue() + m_utfSig.getValue(); + } + + /** + * Determine if the VM structure (or any contained VM structure) has been + * modified. + * + * @return true if the VM structure has been modified + */ + public boolean isModified() + { + if (m_fModified || m_flags.isModified()) + { + return true; + } + + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + Attribute attr = (Attribute) enmr.nextElement(); + if (attr.isModified()) + { + return true; + } + } + + return false; + } + + /** + * Reset the modified state of the VM structure. + */ + protected void resetModified() + { + m_flags.resetModified(); + + Enumeration enmr = m_tblAttribute.elements(); + while (enmr.hasMoreElements()) + { + ((Attribute) enmr.nextElement()).resetModified(); + } + + m_fModified = false; + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + Method that = (Method) obj; + int nResult = this.m_utfName.compareTo(that.m_utfName); + if (nResult == 0) + { + nResult = this.m_utfSig.compareTo(that.m_utfSig); + } + return nResult; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the method. + * + * @return a string describing the method + */ + public String toString() + { + String sMods = m_flags.toString(ACC_METHOD); + String sName = m_utfName.getValue(); + String[] asType = toTypeStrings(m_utfSig.getValue()); + int cType = asType.length; + + StringBuffer sb = new StringBuffer(); + if (sMods.length() > 0) + { + sb.append(sMods) + .append(' '); + } + + sb.append(asType[0]) + .append(' ') + .append(sName) + .append('('); + + for (int i = 1; i < cType; ++i) + { + if (i > 1) + { + sb.append(", "); + } + sb.append(asType[i]); + } + + sb.append(')'); + return sb.toString(); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + Method that = (Method) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_utfName .equals(that.m_utfName ) + && this.m_utfSig .equals(that.m_utfSig ) + && this.m_flags .equals(that.m_flags ) + && this.m_tblAttribute.equals(that.m_tblAttribute); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- Method operations ---------------------------------------------- + + /** + * Add a description (name and access flags) to a parameter at index + * {@code iParam} in the method signature. + *

+ * Access flags is restricted to the following bit values: + *

    + *
  1. {@link MethodParametersAttribute.MethodParameter#ACC_FINAL final}
  2. + *
  3. {@link MethodParametersAttribute.MethodParameter#ACC_SYNTHETIC synthetic}
  4. + *
  5. {@link MethodParametersAttribute.MethodParameter#ACC_MANDATED mandated}
  6. + *
+ * + * @param iParam an index to the parameter in the method signature + * @param sName the name of the parameter + * @param nFlags the access flags for the parameter; bit-mask of the following + * bits: {@link MethodParametersAttribute.MethodParameter#ACC_FINAL final}, + * {@link MethodParametersAttribute.MethodParameter#ACC_SYNTHETIC synthetic}, + * {@link MethodParametersAttribute.MethodParameter#ACC_MANDATED mandated} + * + * @return whether the parameter description was added + */ + public boolean addParameter(int iParam, String sName, int nFlags) + { + String[] asTypes = getTypes(); + int cParams = asTypes.length - 1; + if (iParam > cParams) + { + return false; + } + MethodParametersAttribute attrParams = ensureMethodParameters(cParams); + + return attrParams.addParameter(iParam, sName, nFlags); + } + + /** + * Parse the method signature into discrete return type and parameter + * signatures as they would appear in Java source. + * + * @param sSig the JVM method signature + * + * @return an array of Java type strings, where [0] is the return + * type and [1]..[c] are the parameter types. + */ + public static String[] toTypeStrings(String sSig) + { + String[] asType = toTypes(sSig); + int cTypes = asType.length; + for (int i = 0; i < cTypes; ++i) + { + asType[i] = Field.toTypeString(asType[i]); + } + return asType; + } + + /** + * Parse the method signature into discrete return type and parameter + * signatures as they appear in Java .class structures. + * + * @param sSig the JVM method signature + * + * @return an array of JVM type signatures, where [0] is the return + * type and [1]..[c] are the parameter types. + */ + public static String[] toTypes(String sSig) + { + // check for start of signature + char[] ach = sSig.toCharArray(); + if (ach[0] != '(') + { + throw new IllegalArgumentException("JVM Method Signature must start with '('"); + } + + // reserve the first element for the return value + Vector vect = new Vector(); + vect.addElement(null); + + // parse parameter signatures + int of = 1; + while (ach[of] != ')') + { + int cch = getTypeLength(ach, of); + vect.addElement(new String(ach, of, cch)); + of += cch; + } + + // return value starts after the parameter-stop character + // and runs to the end of the method signature + ++of; + vect.setElementAt(new String(ach, of, ach.length - of), 0); + + String[] asSig = new String[vect.size()]; + vect.copyInto(asSig); + + return asSig; + } + + private static int getTypeLength(char[] ach, int of) + { + switch (ach[of]) + { + case 'V': + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + return 1; + + case '[': + { + int cch = 1; + while (isDecimal(ach[++of])) + { + ++cch; + } + return cch + getTypeLength(ach, of); + } + + case 'L': + { + int cch = 2; + while (ach[++of] != ';') + { + ++cch; + } + return cch; + } + + default: + throw new IllegalArgumentException("JVM Type Signature cannot start with '" + ach[of] + "'"); + } + } + + + + // ----- accessors: name and type -------------------------------------- + + /** + * Get the name of the method as a string. + * + * @return the method name + */ + public String getName() + { + return m_utfName.getValue(); + } + + /** + * Get the signature of the method as a string. (This is not called + * "getSignature" because that could imply a "SignatureConstant", which + * is itself both name and type.) + * + * @return the method signature + */ + public String getType() + { + return m_utfSig.getValue(); + } + + /** + * Get the types of the method parameters and return value as they would + * appear in JVM .class structures. + * + * @return an array of JVM type signatures, [0] for the return value type + * and [1..n] for the parameter types + */ + public String[] getTypes() + { + return toTypes(m_utfSig.getValue()); + } + + /** + * Get the types of the method parameters and return value as they would + * appear in Java source. + * + * @return an array of strings, [0] for the return value type and [1..n] + * for the parameter types + */ + public String[] getTypeStrings() + { + return toTypeStrings(m_utfSig.getValue()); + } + + /** + * Get the names of the method parameters as they appear in source code. + * This only works for code which has been assembled or disassembled, + * not for code which has been created but not yet assembled. + * + * @return an array of parameter names, [0] for the return value and + * [1..n] for the parameters; the return value name is always + * null and the other names are null if debugging information + * is not available + */ + public String[] getNames() + { + String[] as = getTypes(); + int c = as.length; + + CodeAttribute code = (CodeAttribute) m_tblAttribute.get(ATTR_CODE); + if (code != null) + { + LocalVariableTableAttribute vars = + (LocalVariableTableAttribute) code.getAttribute(ATTR_VARIABLES); + if (vars != null) + { + int cwBase = isStatic() ? 0 : 1; + int cwParams = cwBase; + for (int i = 1; i < c; ++i) + { + switch (as[i].charAt(0)) + { + default: + cwParams += 1; + break; + + case 'D': + case 'J': + cwParams += 2; + break; + } + } + + int[] aiSlotToParam = new int[cwParams]; + cwParams = cwBase; + for (int i = 1; i < c; ++i) + { + // the slot number (cwParams) corresponds to the 1-based + // parameter number (i) + aiSlotToParam[cwParams] = i; + switch (as[i].charAt(0)) + { + default: + cwParams += 1; + break; + + case 'D': + case 'J': + cwParams += 2; + break; + } + + // clear param type + as[i] = null; + } + + // clear return type + as[0] = null; + + for (Enumeration enmr = vars.ranges(); enmr.hasMoreElements(); ) + { + AbstractLocalVariableTableAttribute.Range range = + (AbstractLocalVariableTableAttribute.Range) enmr.nextElement(); + + if (range.getSlot() < cwParams) + { + int i = aiSlotToParam[range.getSlot()]; + if (i > 0 && as[i] == null) + { + as[i] = range.getVariableName(); + } + } + } + + return as; + } + } + + // clear names + for (int i = 0; i < c; ++i) + { + as[i] = null; + } + + return as; + } + + /** + * Get the UTF constant which holds the method name. + * + * @return the UTF constant which contains the name + */ + public UtfConstant getNameConstant() + { + return m_utfName; + } + + /** + * Get the UTF constant which holds the method signature. + * + * @return the UTF constant which contains the signature + */ + public UtfConstant getTypeConstant() + { + return m_utfSig; + } + + + // ----- accessor: access ---------------------------------------------- + + /** + * Get the method accessibility value. + * + * @return one of ACC_PUBLIC, ACC_PROTECTED, ACC_PRIVATE, or ACC_PACKAGE + */ + public int getAccess() + { + return m_flags.getAccess(); + } + + /** + * Set the method accessibility value. + * + * @param nAccess should be one of ACC_PUBLIC, ACC_PROTECTED, + * ACC_PRIVATE, or ACC_PACKAGE + */ + public void setAccess(int nAccess) + { + m_flags.setAccess(nAccess); + } + + /** + * Determine if the accessibility is public. + * + * @return true if the accessibility is public + */ + public boolean isPublic() + { + return m_flags.isPublic(); + } + + /** + * Set the accessibility to public. + */ + public void setPublic() + { + m_flags.setPublic(); + } + + /** + * Determine if the accessibility is protected. + * + * @return true if the accessibility is protected + */ + public boolean isProtected() + { + return m_flags.isProtected(); + } + + /** + * Set the accessibility to protected. + */ + public void setProtected() + { + m_flags.setProtected(); + } + + /** + * Determine if the accessibility is package private. + * + * @return true if the accessibility is package private + */ + public boolean isPackage() + { + return m_flags.isPackage(); + } + + /** + * Set the accessibility to package private. + */ + public void setPackage() + { + m_flags.setPackage(); + } + + /** + * Determine if the accessibility is private. + * + * @return true if the accessibility is private + */ + public boolean isPrivate() + { + return m_flags.isPrivate(); + } + + /** + * Set the accessibility to private. + */ + public void setPrivate() + { + m_flags.setPrivate(); + } + + + // ----- accessor: static ------------------------------------------- + + /** + * Determine if the Static attribute is set. + * + * @return true if Static + */ + public boolean isStatic() + { + return m_flags.isStatic(); + } + + /** + * Set the Static attribute. + * + * @param fStatic true to set to Static, false otherwise + */ + public void setStatic(boolean fStatic) + { + m_flags.setStatic(fStatic); + } + + + // ----- accessor: final ------------------------------------------- + + /** + * Determine if the Final attribute is set. + * + * @return true if Final + */ + public boolean isFinal() + { + return m_flags.isFinal(); + } + + /** + * Set the Final attribute. + * + * @param fFinal true to set to Final, false otherwise + */ + public void setFinal(boolean fFinal) + { + m_flags.setFinal(fFinal); + } + + + // ----- accessor: synchronized ------------------------------------------- + + /** + * Determine if the synchronized attribute is set. + * + * @return true if synchronized + */ + public boolean isSynchronized() + { + return m_flags.isSynchronized(); + } + + /** + * Set the synchronized attribute. + * + * @param fSynchronized true to set to synchronized, false otherwise + */ + public void setSynchronized(boolean fSynchronized) + { + m_flags.setSynchronized(fSynchronized); + } + + + // ----- accessor: native ------------------------------------------- + + /** + * Determine if the native attribute is set. + * + * @return true if native + */ + public boolean isNative() + { + return m_flags.isNative(); + } + + /** + * Set the native attribute. + * + * @param fNative true to set to native, false otherwise + */ + public void setNative(boolean fNative) + { + m_flags.setNative(fNative); + } + + + // ----- accessor: abstract ------------------------------------------- + + /** + * Determine if the abstract attribute is set. + * + * @return true if abstract + */ + public boolean isAbstract() + { + return m_flags.isAbstract(); + } + + /** + * Set the abstract attribute. + * + * @param fAbstract true to set to abstract, false otherwise + */ + public void setAbstract(boolean fAbstract) + { + m_flags.setAbstract(fAbstract); + } + + // ----- accessor: bridge ---------------------------------------------- + + /** + * Determine if the bridge attribute is set. + * + * @return true if bridge + */ + public boolean isBridge() + { + return m_flags.isBridge(); + } + + /** + * Set the bridge attribute. + * + * @param fBridge true to set to bridge, false otherwise + */ + public void setBridge(boolean fBridge) + { + m_flags.setBridge(fBridge); + } + + // ----- accessor: varargs --------------------------------------------- + + /** + * Determine if the varargs attribute is set. + * + * @return true if varargs + */ + public boolean isVarArgs() + { + return m_flags.isVarArgs(); + } + + /** + * Set the varargs attribute. + * + * @param fVarArgs true to set to varargs, false otherwise + */ + public void setVarArgs(boolean fVarArgs) + { + m_flags.setVarArgs(fVarArgs); + } + + + // ----- accessor: strict ---------------------------------------------- + + /** + * Determine if the strict attribute is set. + * + * @return true if strict + */ + public boolean isStrict() + { + return m_flags.isStrict(); + } + + /** + * Set the strict attribute. + * + * @param fStrict true to set to strict, false otherwise + */ + public void setStrict(boolean fStrict) + { + m_flags.setStrict(fStrict); + } + + + // ----- accessor: attribute ------------------------------------------- + + /** + * Access a Java .class attribute structure. + * + * @param sName the attribute name + * + * @return the specified attribute or null if the attribute does not exist + */ + public Attribute getAttribute(String sName) + { + return (Attribute) m_tblAttribute.get(sName); + } + + /** + * Add a Java .class attribute structure. + * + * @param sName the attribute name + * + * @return the new attribute + */ + public Attribute addAttribute(String sName) + { + Attribute attribute; + if (sName.equals(ATTR_CODE)) + { + attribute = new CodeAttribute(this); + } + else if (sName.equals(ATTR_EXCEPTIONS)) + { + attribute = new ExceptionsAttribute(this); + } + else if (sName.equals(ATTR_DEPRECATED)) + { + attribute = new DeprecatedAttribute(this); + } + else if (sName.equals(ATTR_SYNTHETIC)) + { + attribute = new SyntheticAttribute(this); + } + else if (sName.equals(ATTR_SIGNATURE)) + { + attribute = new SignatureAttribute(this); + } + else if (sName.equals(ATTR_RTVISANNOT)) + { + attribute = new RuntimeVisibleAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTINVISANNOT)) + { + attribute = new RuntimeInvisibleAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTVISPARAMANNOT)) + { + attribute = new RuntimeVisibleParameterAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTINVISPARAMANNOT)) + { + attribute = new RuntimeInvisibleParameterAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTVISTANNOT)) + { + attribute = new RuntimeVisibleTypeAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_RTINVISTANNOT)) + { + attribute = new RuntimeInvisibleTypeAnnotationsAttribute(this); + } + else if (sName.equals(ATTR_METHODPARAMS)) + { + attribute = new MethodParametersAttribute(this); + } + else + { + attribute = new Attribute(this, sName); + } + + m_tblAttribute.put(attribute.getIdentity(), attribute); + m_fModified = true; + + return attribute; + } + + /** + * Remove a attribute. + * + * @param sName the attribute name + */ + public void removeAttribute(String sName) + { + m_tblAttribute.remove(sName); + m_fModified = true; + } + + /** + * Access the set of attributes. + * + * @return an enumeration of attributes (not attribute names) + */ + public Enumeration getAttributes() + { + return m_tblAttribute.elements(); + } + + + // ----- accessor: attribute helpers ----------------------------------- + + /** + * Get the code attribute. + * + * @return the code attribute, creating one if necessary + */ + public CodeAttribute getCode() + { + CodeAttribute attr = (CodeAttribute) m_tblAttribute.get(ATTR_CODE); + return (attr == null ? (CodeAttribute) addAttribute(ATTR_CODE) : attr); + } + + /** + * Determine if the method is deprecated. + * + * @return true if deprecated, false otherwise + */ + public boolean isDeprecated() + { + return m_tblAttribute.contains(ATTR_DEPRECATED); + } + + /** + * Toggle if the method is deprecated. + * + * @param fDeprecated pass true to deprecate, false otherwise + */ + public void setDeprecated(boolean fDeprecated) + { + if (fDeprecated) + { + addAttribute(ATTR_DEPRECATED); + } + else + { + removeAttribute(ATTR_DEPRECATED); + } + } + + /** + * Determine if the method is synthetic. + * + * @return true if synthetic, false otherwise + */ + public boolean isSynthetic() + { + return m_tblAttribute.contains(ATTR_SYNTHETIC) || + m_flags.isSynthetic(); + } + + /** + * Toggle if the method is synthetic. + * + * @param fSynthetic pass true to set synthetic, false otherwise + */ + public void setSynthetic(boolean fSynthetic) + { + if (fSynthetic) + { + addAttribute(ATTR_SYNTHETIC); + } + else + { + removeAttribute(ATTR_SYNTHETIC); + } + } + + /** + * Add an exception. + * + * @param sClz the class name of the exception + */ + public void addException(String sClz) + { + ExceptionsAttribute attr = (ExceptionsAttribute) getAttribute(ATTR_EXCEPTIONS); + if (attr == null) + { + attr = (ExceptionsAttribute) addAttribute(ATTR_EXCEPTIONS); + } + attr.addException(sClz); + } + + /** + * Remove an exception. + * + * @param sClz the class name of the exception + */ + public void removeException(String sClz) + { + ExceptionsAttribute attr = (ExceptionsAttribute) m_tblAttribute.get(ATTR_EXCEPTIONS); + if (attr != null) + { + attr.removeException(sClz); + } + } + + /** + * Access the set of exceptions. + * + * @return an enumeration of exception class names + */ + public Enumeration getExceptions() + { + ExceptionsAttribute attr = (ExceptionsAttribute) getAttribute(ATTR_EXCEPTIONS); + return attr == null ? NullImplementation.getEnumeration() : attr.getExceptions(); + } + + + // ----- helpers -------------------------------------------------------- + + /** + * The class name if this method is from disassembly. + * + * @return the class name as it was found in the constant pool + */ + protected String getClassName() + { + return m_sClass; + } + + /** + * Ensure a {@link MethodParametersAttribute} exists as method_info + * attribute. + * + * @param cParams the number of parameters defined by this method's signature + * + * @return a MethodParametersAttribute linked to this Method + */ + protected MethodParametersAttribute ensureMethodParameters(int cParams) + { + MethodParametersAttribute attrParams = (MethodParametersAttribute) + getAttribute(ATTR_METHODPARAMS); + if (attrParams == null) + { + attrParams = (MethodParametersAttribute) addAttribute(ATTR_METHODPARAMS); + attrParams.setParameterCount(cParams); + } + return attrParams; + } + + + // ----- constants ------------------------------------------------------ + + /** + * The name of this class. + */ + private static final String CLASS = "Method"; + + /** + * Access flags applicable to a method. + */ + public static final int ACC_METHOD = AccessFlags.ACC_PUBLIC | + AccessFlags.ACC_PRIVATE | + AccessFlags.ACC_PROTECTED | + AccessFlags.ACC_STATIC | + AccessFlags.ACC_FINAL | + AccessFlags.ACC_SYNCHRONIZED | + AccessFlags.ACC_BRIDGE | + AccessFlags.ACC_VARARGS | + AccessFlags.ACC_NATIVE | + AccessFlags.ACC_ABSTRACT | + AccessFlags.ACC_STRICT | + AccessFlags.ACC_SYNTHETIC; + + + // ----- data members --------------------------------------------------- + + /** + * Whether this method is defined against an interface or a class. + */ + private final boolean f_fInterface; + + /** + * The name of the class if this method is the result of disassembly. + */ + private String m_sClass; + + /** + * The name of the method. + */ + private UtfConstant m_utfName; + + /** + * The signature of the method. + */ + private UtfConstant m_utfSig; + + /** + * The AccessFlags structure contained in the method. + */ + private AccessFlags m_flags = new AccessFlags() + { + @Override + protected void preassemble(ConstantPool pool) + { + if (f_fInterface) + { + if (isMaskSet(ACC_PROTECTED | ACC_FINAL | ACC_SYNCHRONIZED | ACC_NATIVE)) + { + throw new IllegalStateException("Interface method " + this + " can not be " + + "protected, final, synchronized, or native"); + } + if (pool.getClassFile().getMajorVersion() < 52) + { + setPublic(); + setAbstract(true); + } + else if (!isPublic() && !isPrivate()) + { + throw new IllegalStateException("Interface method " + this + " must be " + + "either public or private"); + } + } + if (isAbstract() && isMaskSet(ACC_PRIVATE | ACC_STATIC | ACC_FINAL | + ACC_SYNCHRONIZED | ACC_NATIVE | ACC_STRICT)) + { + throw new IllegalStateException("Abstract Method " + this + " can not be " + + "private, static, final, synchronized, native, or strict"); + } + + super.preassemble(pool); + } + }; + + /** + * The Attribute structures contained in the method. + */ + private StringTable m_tblAttribute = new StringTable(); + + /** + * Tracks changes to the method. + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodConstant.java new file mode 100644 index 0000000000000..e6bfee3543507 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodConstant.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + + +/** +* Represents the method of a class. +* +* @version 0.50, 05/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class MethodConstant extends RefConstant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the InterfaceConstant class. + */ + protected MethodConstant(int nTag) + { + super(nTag); + } + + /** + * Constructor used internally by the InterfaceConstant class. + */ + protected MethodConstant(int nTag, String sClass, String sName, String sType) + { + super(nTag, sClass, sName, sType); + } + + /** + * Constructor used internally by the InterfaceConstant class. + */ + protected MethodConstant(int nTag, ClassConstant constantClz, SignatureConstant constantSig) + { + super(nTag, constantClz, constantSig); + } + + /** + * Constructor used internally by the Constant class. + */ + protected MethodConstant() + { + super(CONSTANT_METHODREF); + } + + /** + * Construct a constant which specifies a class method. + * + * @param sClass the class name + * @param sName the method name + * @param sType the method signature + */ + public MethodConstant(String sClass, String sName, String sType) + { + super(CONSTANT_METHODREF, sClass, sName, sType); + } + + /** + * Construct a constant which references the passed constants. + * + * @param constantClz the referenced Class constant which contains the + * name of the class + * @param constantSig the referenced Signature constant which contains + * the name and signature of the method + */ + public MethodConstant(ClassConstant constantClz, SignatureConstant constantSig) + { + super(CONSTANT_METHODREF, constantClz, constantSig); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Method)->" + super.toString(); + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + String sClass = getClassConstant().format(); + if (sClass.startsWith("java")) + { + int of = sClass.lastIndexOf('.'); + if (of != -1) + { + sClass = sClass.substring(of + 1); + } + } + sClass = sClass.replace('$', '.'); + + SignatureConstant sig = getSignatureConstant(); + String sName = sig.getName(); + String sType = sig.getType(); + + String[] asType = Method.toTypeStrings(sType); + for (int i = 0, c = asType.length; i < c; ++i) + { + sType = asType[i]; + int of = sType.lastIndexOf('.'); + if (of != -1) + { + sType = sType.substring(of + 1); + } + sType = sType.replace('$', '.'); + asType[i] = sType; + } + + // format as: + // return-type class.name(param-types) + + StringBuffer sb = new StringBuffer(); + sb.append(asType[0]) + .append(' ') + .append(sClass) + .append('.') + .append(sName) + .append('('); + + for (int i = 1, c = asType.length; i < c; ++i) + { + if (i > 1) + { + sb.append(','); + } + + sb.append(asType[i]); + } + + sb.append(')'); + return sb.toString(); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the Java signature types of the parameters. + * + * @return an array of JVM type signatures, where [0] is the return + * type and [1]..[c] are the parameter types + */ + public String[] getTypes() + { + String[] asType = m_asType; + if (asType == null) + { + m_asType = asType = Method.toTypes(getType()); + } + return asType; + } + + /** + * Get the variable types of the parameters. + * + * @return an array of JVM variables types (I, F, L, D, A, R) or 0x00 for + * void, where [0] is the return type and [1]..[c] are the + * parameter types + */ + public char[] getVariableType() + { + char[] achType = m_achType; + if (achType == null) + { + String[] asType = getTypes(); + int cTypes = asType.length; + + achType = new char[cTypes]; + for (int i = 0; i < cTypes; ++i) + { + achType[i] = FieldConstant.getVariableType(asType[i]); + } + + m_achType = achType; + } + + return achType; + } + + /** + * Get the variable types of the specified parameter. + * + * @param i [0] for the return type and [1]..[c] for the parameter types + * + * @return the JVM variable type (I, F, L, D, A, R) or 0x00 for void + */ + public char getVariableType(int i) + { + return getVariableType()[i]; + } + + /** + * Determine if the referenced method is void. + * + * @return true if void + */ + public boolean isVoid() + { + return (getVariableType(0) == 0x00); + } + + /** + * Get the variable size of the specified parameter. + * + * @param i [0] for the return type and [1]..[c] for the parameter types + * + * @return the number of words on the stack used by the parameter + */ + public int getVariableSize(int i) + { + if (i == 0 && isVoid()) + { + return 0; + } + + return OpDeclare.getWidth(getVariableType(i)); + } + + /** + * Get the net stack change in words of pushing all of the parameters. + * + * @return the number of parameter words pushed onto the stack in order + * to invoke the method + */ + public int getTotalParameterSize() + { + char[] achType = getVariableType(); + int cTypes = achType.length; + int cWords = 0; + for (int i = 1; i < cTypes; ++i) + { + cWords += OpDeclare.getWidth(achType[i]); + } + + return cWords; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "MethodConstant"; + + /** + * Cached type info: JVM signatures. + */ + private String[] m_asType; + + /** + * Cached type info: variable types. + */ + private char[] m_achType; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodHandleConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodHandleConstant.java new file mode 100644 index 0000000000000..443ae23ae031c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodHandleConstant.java @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.dev.assembler; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** +* MethodHandleConstant represents a method that returns a CallSite as +* prescribed by the invokedynamic feature. +*

+* The MethodHandle Constant was defined by JDK 1.7 under bytecode +* version 51.0. The structure is defined as: +*

+*

+* CONSTANT_MethodHandle_info
+*     {
+*     u1 tag;
+*     u1 reference_kind;
+*     u2 reference_index;
+*     }
+* 
+* +* @author hr 2012.08.06 +*/ +public class MethodHandleConstant + extends Constant + { + + // ----- constructors --------------------------------------------------- + + /** + * Construct a MethodHandleConstant (tag = 15). + * + * @param fAllowInterface whether interface references are permitted when + * nReferenceKind is either {@link #KIND_REF_INVOKESTATIC} + * or {@link #KIND_REF_INVOKESPECIAL} + */ + protected MethodHandleConstant(boolean fAllowInterface) + { + this(fAllowInterface, 0, null); + } + + /** + * Construct a MethodHandleConstant instance with the provided + * {@code reference_kind} and {@link RefConstant}. + * + * @param fAllowInterface whether interface references are permitted when + * nReferenceKind is either {@link #KIND_REF_INVOKESTATIC} + * or {@link #KIND_REF_INVOKESPECIAL} + * @param nReferenceKind one of the KIND_REF* constants + * @param ref the reference to the constant pool + */ + public MethodHandleConstant(boolean fAllowInterface, int nReferenceKind, RefConstant ref) + { + super(CONSTANT_METHODHANDLE); + + m_fAllowInterface = fAllowInterface; + m_nReferenceKind = nReferenceKind; + m_ref = ref; + } + + // ----- Constant methods ----------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + m_nReferenceKind = stream.readUnsignedByte(); + m_nReferenceIndex = stream.readUnsignedShort(); + + ClassFile cf = pool.getClassFile(); + m_fAllowInterface = cf != null && cf.getMajorVersion() >= 52; + } + + /** + * {@inheritDoc} + */ + @Override + protected void postdisassemble(ConstantPool pool) + { + RefConstant ref = (RefConstant) pool.getConstant(m_nReferenceIndex); + + // postdisassemble may not have been called on dependent constant + ref.postdisassemble(pool); + + if (isValid(ref)) + { + m_ref = ref; + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_ref); + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + super.assemble(stream, pool); + + stream.writeByte(m_nReferenceKind); + stream.writeShort(pool.findConstant(m_ref)); + } + + // ----- Comparable methods --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(Object obj) + { + return obj instanceof MethodHandleConstant + ? m_ref.compareTo(((MethodHandleConstant) obj).m_ref) + : 1; + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return "(MethodHandle)->[reference_kind = " + m_nReferenceKind + ", " + + "reference = " + m_ref + "]"; + } + + /** + * {@inheritDoc} + */ + @Override + public String format() + { + return m_ref.format(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) + { + return obj instanceof MethodHandleConstant + ? m_ref.equals(((MethodHandleConstant) obj).m_ref) + : false; + } + + // ----- accessors ------------------------------------------------------ + + /** + * Returns the value of {@code reference_kind}. + * + * @return the value of {@code reference_kind} + */ + public int getKind() + { + return m_nReferenceKind; + } + + /** + * Sets the value of {@code reference_kind}. + * + * @param nReferenceKind the value of {@code reference_kind} + */ + public void setKind(int nReferenceKind) + { + if (isValid(nReferenceKind, m_ref)) + { + m_nReferenceKind = nReferenceKind; + } + } + + /** + * Returns the {@link RefConstant} this MethodHandle links to which in + * turn will return a CallSite to the runtime linked method. + * + * @return the RefConstant this MethodHandle links to + */ + public RefConstant getReference() + { + return m_ref; + } + + /** + * Sets the {@link RefConstant} this MethodHandle links to which in + * turn will return a CallSite to the runtime linked method. + * + * @param ref the RefConstant this MethodHandle links to + */ + public void setReference(RefConstant ref) + { + if (!isValid(ref)) + { + throw new IllegalArgumentException(String.format( + "Constant is not valid with the current reference_kind, " + + "[constant = %s, reference_kind = %d]", ref, m_nReferenceKind)); + } + m_ref = ref; + } + + /** + * Return whether interface references are permitted when reference kind + * is either {@link #KIND_REF_INVOKESTATIC} or {@link #KIND_REF_INVOKESPECIAL}. + *

+ * This method will return true if either the disassembled ClassFile has a + * major version {@code >= 52}, or is explicitly set due to the assembled + * version being targeted to a ClassFile major version {@code >= 52}. + * + * @return true if interface references are permitted + */ + public boolean isInterfaceRefAllowed() + { + return m_fAllowInterface; + } + + /** + * Set whether interface references are permitted when reference kind + * is either {@link #KIND_REF_INVOKESTATIC} or {@link #KIND_REF_INVOKESPECIAL}. + *

+ * This should only be set to true if the assembled version is targeted + * to a ClassFile major version {@code >= 52}. + * + * @return true if interface references are permitted + */ + public void setInterfaceRefAllowed(boolean fAllowInterface) + { + m_fAllowInterface = fAllowInterface; + } + + // ----- helpers -------------------------------------------------------- + + /** + * Determines whether the provided {@link RefConstant} is valid based on + * the {@link #getReference() reference_kind}. Therefore {@code reference_kind} + * must be set prior to {@link #setReference(RefConstant)}. + * + * @param ref the requested {@link RefConstant} to be validated + * + * @return whether the given ref is valid with the set reference kind + */ + protected boolean isValid(RefConstant ref) + { + return isValid(m_nReferenceKind, ref); + } + + /** + * Determines whether the provided {@link RefConstant} is valid based on + * the provided {@code nKind}. + * + * @param nKind the kind value + * @param ref the requested RefConstant to be validated + * + * @return whether the given ref is valid with the set reference kind + */ + protected boolean isValid(int nKind, RefConstant ref) + { + if (ref == null) + { + return true; + } + + Class clz = MethodConstant.class; + switch (nKind) + { + case KIND_REF_GETFIELD: + case KIND_REF_GETSTATIC: + case KIND_REF_PUTFIELD: + case KIND_REF_PUTSTATIC: + return FieldConstant.class == ref.getClass(); + case KIND_REF_INVOKEINTERFACE: + clz = InterfaceConstant.class; + case KIND_REF_INVOKESTATIC: + case KIND_REF_INVOKESPECIAL: + return (clz == ref.getClass() || isInterfaceRefAllowed() && clz == InterfaceConstant.class) + && !ref.getName().contains("") + && !ref.getName().contains(""); + case KIND_REF_INVOKEVIRTUAL: + case KIND_REF_NEWINVOKESPECIAL: + return MethodConstant.class == ref.getClass() + && (nKind == KIND_REF_NEWINVOKESPECIAL && ref.getName().contains("") || + !ref.getName().contains("") && + !ref.getName().contains("")); + default: + throw new IllegalStateException("Constant MethodHandle must " + + "have a reference kind value between 0 - 9"); + } + } + + // ----- constants ------------------------------------------------------ + + /** + * Reference Kind (getField). + */ + public static final int KIND_REF_GETFIELD = 1; + + /** + * Reference Kind (getStatic). + */ + public static final int KIND_REF_GETSTATIC = 2; + + /** + * Reference Kind (putField). + */ + public static final int KIND_REF_PUTFIELD = 3; + + /** + * Reference Kind (putStatic). + */ + public static final int KIND_REF_PUTSTATIC = 4; + + /** + * Reference Kind (invokeVirtual). + */ + public static final int KIND_REF_INVOKEVIRTUAL = 5; + + /** + * Reference Kind (invokeStatic). + */ + public static final int KIND_REF_INVOKESTATIC = 6; + + /** + * Reference Kind (invokeSpecial). + */ + public static final int KIND_REF_INVOKESPECIAL = 7; + + /** + * Reference Kind (newInvokeSpecial). + */ + public static final int KIND_REF_NEWINVOKESPECIAL = 8; + + /** + * Reference Kind (invokeInterface). + */ + public static final int KIND_REF_INVOKEINTERFACE = 9; + + // ----- data members --------------------------------------------------- + + /** + * The reference_kind value of this MethodHandleConstant. + */ + private int m_nReferenceKind; + + /** + * The ConstantPool index of the RefConstant. + */ + private int m_nReferenceIndex; + + /** + * The RefConstant this MethodHandle links to. + */ + private RefConstant m_ref; + + /** + * This field being true allows for the constant pool reference to be an {@link + * InterfaceConstant} when reference kind is either {@link #KIND_REF_INVOKESTATIC} + * or {@link #KIND_REF_INVOKESPECIAL}. + */ + private boolean m_fAllowInterface; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodParametersAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodParametersAttribute.java new file mode 100644 index 0000000000000..b82740781ad8e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodParametersAttribute.java @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** + * Represents a Java Virtual Machine "MethodParameters" attribute which contains + * information of the various parameters of a method. This attribute can only + * reside on method_info. + *

+ * The MethodParameters Attribute is defined by the JDK 1.8 documentation as: + *

+ *

+ *     MethodParameters_attribute
+ *         {
+ *         u2 attribute_name_index;
+ *         u4 attribute_length;
+ *         u1 parameters_count;
+ *             {
+ *             u2 name_index;
+ *             u2 access_flags;
+ *             } parameters[parameters_count];
+ *         }
+ * 
+ * + * Usages of this attribute are expected to call {@link #setParameterCount(int)} + * prior to calling {@link #addParameter(int, String, int)}. + * + * @author hr 2014.05.28 + */ +public class MethodParametersAttribute + extends Attribute + implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a MethodParameters attribute with the provided context. + * + * @param context the JVM structure containing the attribute + */ + protected MethodParametersAttribute(VMStructure context) + { + super(context, ATTR_METHODPARAMS); + } + + // ----- VMStructure operations ----------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + // attribute_name_index has been read + stream.readInt(); + int cParam = stream.readUnsignedByte(); + + MethodParameter[] aParam = ensureMethodParams(cParam); + for (int i = 0; i < cParam; ++i) + { + MethodParameter param = new MethodParameter(i); + param.disassemble(stream, pool); + + aParam[i] = param; + } + } + + @Override + protected void preassemble(ConstantPool pool) + { + super.preassemble(pool); + + // disallow partial parameter descriptions + MethodParameter[] aParam = m_aParam; + for (int i = 0, cParam = aParam.length; i < cParam; ++i) + { + if (aParam[i] == null) + { + throw new IllegalStateException( + "Either all parameters or none should be described"); + } + } + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + + MethodParameter[] aParam = m_aParam; + int cParam = aParam.length; + + stream.writeInt(4 * cParam); + stream.writeByte(cParam); + for (int i = 0; i < cParam; ++i) + { + aParam[i].assemble(stream, pool); + } + } + + // ----- public methods ------------------------------------------------- + + /** + * Add a parameter description (name and access flags) to the MethodParameters + * attribute. + * + * @param iParam the index of the parameter being described + * @param sName the name of the parameter + * @param nFlags a bit mask of access flags; restricted to the bits defined + * by the {@link MethodParameter}#ACC_* constants + * + * @return whether the parameter description was added / applied + */ + public boolean addParameter(int iParam, String sName, int nFlags) + { + MethodParameter[] aParam = m_aParam; + if (aParam == null || iParam >= aParam.length) + { + return false; + } + MethodParameter param = aParam[iParam]; + if (param == null) + { + param = aParam[iParam] = new MethodParameter(iParam); + } + param.setParameterName(sName); + param.setAccessFlags(nFlags); + + return true; + } + + /** + * Set the number of parameters within a method definition. + *

+ * Note: this method should be called prior to {@link #addParameter(int, String, int)} + * + * @param cParam the number of parameters within a method definition + */ + public void setParameterCount(int cParam) + { + ensureMethodParams(cParam); + } + + // ----- helpers -------------------------------------------------------- + + /** + * Return a {@link MethodParameter} array to hold method parameter + * descriptions. + * + * @param cParam the maximum number of parameters + * + * @return a MethodParameter array to hold method parameter descriptions + */ + protected MethodParameter[] ensureMethodParams(int cParam) + { + MethodParameter[] aParam = m_aParam; + if (aParam == null) + { + aParam = m_aParam = new MethodParameter[cParam]; + } + return aParam; + } + + // ----- inner class: MethodParameter ----------------------------------- + + /** + * MethodParameter holds description information for each parameter including + * the parameter's name and access flags. + */ + protected class MethodParameter + extends VMStructure + implements Constants + { + // ----- constructors ----------------------------------------------- + + /** + * Construct a MethodParameter that refers to the ith + * parameter, where {@code i == iParam}. + * + * @param iParam the index of the parameter in the method descriptor + */ + protected MethodParameter(int iParam) + { + m_iParam = iParam; + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + int iName = stream.readUnsignedShort(); + if (iName > 0) + { + m_name = (UtfConstant) pool.getConstant(iName); + } + m_nFlags = stream.readUnsignedShort(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + if (m_name != null) + { + pool.registerConstant(m_name); + } + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + stream.writeShort(pool.findConstant(m_name)); + stream.writeShort(m_nFlags); + } + + // ----- parameter name --------------------------------------------- + + /** + * Return the name of the parameter. + * + * @return the name of the parameter + */ + public String getParameterName() + { + return m_name.getValue(); + } + + /** + * Set the name of the parameter. + * + * @param sName the name of the parameter + */ + public void setParameterName(String sName) + { + m_name = new UtfConstant(sName); + } + + // ----- access flags methods --------------------------------------- + + /** + * Return whether the parameter is marked as final. + * + * @return whether the parameter is marked as final + */ + public boolean isFinal() + { + return (m_nFlags & ACC_FINAL) == ACC_FINAL; + } + + /** + * Set whether the parameter is marked as final. + * + * @param fFinal whether the parameter is marked as final + */ + public void setFinal(boolean fFinal) + { + m_nFlags = fFinal ? m_nFlags | ACC_FINAL + : m_nFlags & ~ACC_FINAL; + } + + /** + * Return whether the parameter is marked as synthetic. + * + * @return whether the parameter is marked as synthetic + */ + public boolean isSynthetic() + { + return (m_nFlags & ACC_SYNTHETIC) == ACC_SYNTHETIC; + } + + /** + * Set whether the parameter is marked as synthetic. + * + * @param fSynthetic whether the parameter is marked as synthetic + */ + public void setSynthetic(boolean fSynthetic) + { + m_nFlags = fSynthetic ? m_nFlags | ACC_SYNTHETIC + : m_nFlags & ~ACC_SYNTHETIC; + } + + /** + * Return whether the parameter is marked as mandated. + * + * @return whether the parameter is marked as mandated + */ + public boolean isMandated() + { + return (m_nFlags & ACC_MANDATED) == ACC_MANDATED; + } + + /** + * Set whether the parameter is marked as mandated. + * + * @param fMandated whether the parameter is marked as mandated + */ + public void setMandated(boolean fMandated) + { + m_nFlags = fMandated ? m_nFlags | ACC_MANDATED + : m_nFlags & ~ACC_MANDATED; + } + + /** + * Set a bit mask representing the access flags for this parameter. + * + * @param nFlags a bit mask representing the access flags for this + * parameter + */ + public void setAccessFlags(int nFlags) + { + if ((nFlags & ~(ACC_FINAL | ACC_SYNTHETIC | ACC_MANDATED)) != 0) + { + throw new IllegalArgumentException("Invalid access flag set; " + + "only final, synthetic or mandated are permitted"); + } + m_nFlags = nFlags; + } + + /** + * Return the type for the parameter using internal type signatures. + * + * @return the type for the parameter using internal type signatures + */ + public String getType() + { + Method method = (Method) MethodParametersAttribute.this.getContext(); + return method.getTypes()[m_iParam + 1]; + } + + // ----- constants -------------------------------------------------- + + /** + * Final parameter. + */ + public static final int ACC_FINAL = 0x0010; + + /** + * Synthetic parameter. + */ + public static final int ACC_SYNTHETIC = 0x1000; + + /** + * Mandated parameter. + */ + public static final int ACC_MANDATED = 0x8000; + + // ----- data members ----------------------------------------------- + + /** + * The name of the parameter. + */ + protected UtfConstant m_name; + + /** + * The access flags of the parameter. + */ + protected int m_nFlags; + + /** + * The index of the parameter in the method descriptor. + */ + protected int m_iParam; + } + + // ----- data members --------------------------------------------------- + + /** + * An array of MethodParameters with each element describing the + * ith parameter as defined in the associated + * MethodDescriptor structure. + */ + protected MethodParameter[] m_aParam; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodTypeConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodTypeConstant.java new file mode 100644 index 0000000000000..b63f3b1846ba2 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/MethodTypeConstant.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.dev.assembler; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +/** +* MethodTypeConstant represents a method type. +*

+* The MethodType Constant was defined by JDK 1.7 under bytecode +* version 51.0. The structure is defined as: +*

+*

+* CONSTANT_MethodType_info
+*     {
+*     u1 tag;
+*     u2 descriptor_index;
+*     }
+* 
+* +* @author hr 2012.08.06 +*/ +public class MethodTypeConstant + extends Constant + { + + // ----- constructors --------------------------------------------------- + + /** + * Construct a MethodTypeConstant (tag = 16). + */ + protected MethodTypeConstant() + { + super(CONSTANT_METHODTYPE); + } + + // ----- Constant methods ----------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + m_nDescriptorIndex = stream.readUnsignedShort(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void postdisassemble(ConstantPool pool) + { + m_descriptor = (UtfConstant) pool.getConstant(m_nDescriptorIndex); + } + + /** + * {@inheritDoc} + */ + @Override + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_descriptor); + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(pool.findConstant(m_descriptor)); + } + + // ----- Comparable methods --------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public int compareTo(Object obj) + { + return obj instanceof MethodTypeConstant + ? m_descriptor.compareTo(((MethodTypeConstant) obj).m_descriptor) + : 1; + } + + // ----- Object methods ------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return "(MethodType)->[descriptor = " + m_descriptor + "]"; + } + + /** + * {@inheritDoc} + */ + @Override + public String format() + { + return m_descriptor.format(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) + { + return obj instanceof MethodTypeConstant + ? m_descriptor.equals(((MethodTypeConstant) obj).m_descriptor) + : false; + } + + // ----- accessors ------------------------------------------------------ + + /** + * Returns the method description. + * + * @return the method description + */ + public UtfConstant getDescriptor() + { + return m_descriptor; + } + + /** + * Sets the method description. + * + * @return the method description + */ + public void setDescriptor(UtfConstant descriptor) + { + m_descriptor = descriptor; + } + + // ----- data members --------------------------------------------------- + + /** + * The ConstantPool index of the method description. + */ + private int m_nDescriptorIndex; + + /** + * The Constant that describes the method. + */ + private UtfConstant m_descriptor; + } \ No newline at end of file diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Monitorenter.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Monitorenter.java new file mode 100644 index 0000000000000..81dc4f861c77f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Monitorenter.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The MONITORENTER simple op enters a synchronized section of code. +*

+* JASM op         :  MONITORENTER  (0xc2)
+* JVM byte code(s):  MONITORENTER  (0xc2)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Monitorenter extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Monitorenter() + { + super(MONITORENTER); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Monitorenter"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Monitorexit.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Monitorexit.java new file mode 100644 index 0000000000000..066caceeeb199 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Monitorexit.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The MONITOREXIT simple op leaves a synchronized section of code. +*

+* JASM op         :  MONITOREXIT  (0xc3)
+* JVM byte code(s):  MONITOREXIT  (0xc3)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Monitorexit extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Monitorexit() + { + super(MONITOREXIT); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Monitorexit"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Multianewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Multianewarray.java new file mode 100644 index 0000000000000..0f507e5710da9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Multianewarray.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The MULTIANEWARRAY op creates an multi-dimensioned array of references of +* a type specified by the ClassConstant. +*

+* JASM op         :  MULTIANEWARRAY    (0xc5)
+* JVM byte code(s):  MULTIANEWARRAY    (0xc5)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Multianewarray extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the ClassConstant + * @param cDims the number of dimensions + */ + public Multianewarray(ClassConstant constant, int cDims) + { + super(MULTIANEWARRAY, constant); + m_cDims = cDims; + + if (cDims < 0x01 || cDims > 0xFF) + { + throw new IllegalArgumentException(CLASS + + ": Dimensions must be in the range 0x01..0xFF!"); + } + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + ClassConstant constant = (ClassConstant) super.getConstant(); + stream.writeByte(MULTIANEWARRAY); + stream.writeShort(pool.findConstant(constant)); + stream.writeByte(m_cDims); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + setSize(4); + } + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // int size of each dimension is popped + // resulting array is pushed + return 1 - m_cDims; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Multianewarray"; + + /** + * The number of dimensions to allocate. + */ + private int m_cDims; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/New.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/New.java new file mode 100644 index 0000000000000..099369cbf1b43 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/New.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The NEW op creates an instance of a class as specified by the +* ClassConstant. +*

+* JASM op         :  NEW    (0xbb)
+* JVM byte code(s):  NEW    (0xbb)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class New extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the ClassConstant + */ + public New(ClassConstant constant) + { + super(NEW, constant); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "New"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Nop.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Nop.java new file mode 100644 index 0000000000000..4ddb69e6a91ec --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Nop.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The NOP simple op does nothing. +*

+* JASM op         :  NOP (0x00)
+* JVM byte code(s):  NOP (0x00)
+* Details         :
+* 
+* +* @version 0.50, 06/11/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Nop extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Nop() + { + super(NOP); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Nop"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Op.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Op.java new file mode 100644 index 0000000000000..db9172e6eefa3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Op.java @@ -0,0 +1,2030 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.EOFException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Enumeration; +import java.util.Vector; + +import com.tangosol.io.ByteArrayReadBuffer; +import com.tangosol.io.ReadBuffer; + +import com.tangosol.util.NullImplementation; + + +/** +* Represents a Java Virtual Machine assembly (JASM) operation, which is +* disassembled from and/or assembled to a Java byte code. +* +* @version 0.50, 05/20/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class Op extends VMStructure implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the specified JASM op. + * + * @param iOp the JASM op value + */ + protected Op(int iOp) + { + m_iOp = iOp; + m_iHash = sm_iLastHash = (int) (((long) sm_iLastHash + BIGPRIME) % INTLIMIT); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Determine if the passed value is a legal JVM byte code. + * + * @param ub the unsigned byte code value + * + * @return true if the value is a byte code defined by the JVM spec + */ + public static boolean isByteCode(int ub) + { + // 0x00 (NOP) through 0xc9 (JSR_W) except for 0xba + return ub >= NOP && ub <= JSR_W && ub != 0xba; + } + + /** + * For disassembly of ops, provide the requested variable. + * + * @param iOp the variable declaration op value (IVAR, ...) + * @param iVar the variable index + * + * @return the requested variable + */ + protected static OpDeclare getVariable(int iOp, int iVar, OpDeclare[][] aavar) + { + int iType = iOp - IVAR; + OpDeclare decl = aavar[iType][iVar]; + + if (decl == null) + { + switch (iOp) + { + case IVAR: + decl = new Ivar(iVar); + break; + case LVAR: + decl = new Lvar(iVar); + break; + case FVAR: + decl = new Fvar(iVar); + break; + case DVAR: + decl = new Dvar(iVar); + break; + case AVAR: + decl = new Avar(iVar); + break; + case RVAR: + decl = new Rvar(iVar); + break; + } + + aavar[iType][iVar] = decl; + } + + return decl; + } + + /** + * For disassembly of ops, get the requested label. + * + * @param of byte code offset + * @param alabel an array of labels per byte code offset + * + * @exception IOException + */ + private static Label getLabel(int of, Op[] alabel) + throws IOException + { + try + { + Label label = (Label) alabel[of]; + if (label == null) + { + label = new Label(String.valueOf(of)); + label.setOffset(of); + alabel[of] = label; + } + return label; + } + catch (ArrayIndexOutOfBoundsException e) + { + throw new IOException(CLASS + ".getLabel: Illegal label offset -- " + of); + } + } + + /** + * For disassembly of ops, add the try at the specified offset. + * + * @param of byte code offset + * @param opTry the try op + * @param aopDefer the deferred ops, by offset + * + * @exception IOException + */ + private static void addTry(int of, Try opTry, Op[] aopDefer) + throws IOException + { + // store the offset of the op + opTry.setOffset(of); + + // see if any deferred ops are already at that offset + Op op; + try + { + op = aopDefer[of]; + } + catch (ArrayIndexOutOfBoundsException e) + { + throw new IOException(CLASS + ".addTry: Illegal Try offset!"); + } + + // place the try after any instances of catch and after any labels + // but before any instances of try + if (op == null || op instanceof Try) + { + // link the try in at the beginning + aopDefer[of] = opTry; + opTry.setNext(op); + } + else + { + // link the try in immediately after the catch's and label's + Op opPrev = op; + Op opNext = opPrev.getNext(); + while (opNext instanceof Catch || opNext instanceof Label) + { + opPrev = opNext; + opNext = opPrev.getNext(); + } + opPrev.setNext(opTry ); + opTry .setNext(opNext); + } + } + + /** + * For disassembly of ops, add the catch at the specified offset. + * + * @param of byte code offset + * @param opCatch the catch op + * @param aopDefer the deferred ops, by offset + * + * @exception IOException + */ + private static void addCatch(int of, Catch opCatch, Op[] aopDefer) + throws IOException + { + // store the offset of the op + opCatch.setOffset(of); + + // see if any deferred ops are already at that offset + Op op; + try + { + op = aopDefer[of]; + } + catch (ArrayIndexOutOfBoundsException e) + { + throw new IOException(CLASS + ".addCatch: Illegal Catch offset!"); + } + + // see if the deferred ops are instances of Catch + if (op instanceof Catch) + { + // link the try in after the catch's + Op opPrev = op; + Op opNext = opPrev.getNext(); + while (opNext instanceof Catch) + { + opPrev = opNext; + opNext = opPrev.getNext(); + } + opPrev .setNext(opCatch); + opCatch.setNext(opNext ); + } + else + { + // link the catch in at the beginning + aopDefer[of] = opCatch; + opCatch.setNext(op); + } + } + + + /** + * For disassembly of ops, update the LocalVariable/LocalVariableType + * tables to point to the disassembled ops. + * + * @param attrTable the local variable table attribute + * @param aopLabel labels by op offset + */ + private static void disassembleVarTable(AbstractLocalVariableTableAttribute attrTable, Op[] aopLabel) + throws IOException + { + for (Enumeration enmr = attrTable.ranges(); enmr.hasMoreElements(); ) + { + AbstractLocalVariableTableAttribute.Range range = + (AbstractLocalVariableTableAttribute.Range) enmr.nextElement(); + + // determine byte code offsets of the range + int ofInit = range.getOffset(); + int ofStop = ofInit + range.getLength(); + + // translate offsets to ops + range.setInit(getLabel(ofInit, aopLabel)); + range.setStop(getLabel(ofStop, aopLabel)); + } + } + + + /** + * Based on the byte code, which is encountered first in the stream, + * construct the correct Op class and disassemble it. + * + * @param abCode the byte array containing the byte code portion of + * the "Code" attribute + * @param cVars the number of local variable words + * @param asParam an array of JVM types for the method parameters + * @param vectCatch the table of try/catch data + * @param attrLine the line number table, if available, null otherwise + * @param attrVar the local variable table, if available + * @param attrVarType the local variable type table, if available + * @param pool the constant pool for the class + * @param aopBounds an array to return the first and last op in + * + * @exception IOException + */ + protected static void disassembleOps(byte[] abCode, int cVars, String[] asParam, Vector vectCatch, LineNumberTableAttribute attrLine, LocalVariableTableAttribute attrVar, LocalVariableTypeTableAttribute attrVarType, ConstantPool pool, Op[] aopBounds) + throws IOException + { + ReadBuffer.BufferInput stream = new ByteArrayReadBuffer(abCode).getBufferInput(); + + // prime the line number enumeration + Enumeration enmrLines = (attrLine == null ? NullImplementation.getEnumeration() + : attrLine.entries() ); + LineNumberTableAttribute.Entry line = + (LineNumberTableAttribute.Entry) + (enmrLines.hasMoreElements() ? enmrLines.nextElement() : null); + int iLine = 0; + + // build a table of variables by type/by slot + OpDeclare[][] aavar = new OpDeclare[6][cVars]; + + // labels by op offset (collected as necessary) + Op[] aopDefer = new Op[abCode.length + 1]; + + // support for switch parsing + boolean fSwitchOp = false; + OpSwitch opSwitch = null; + int iOpSwitch = 0; + int cCases = 0; + int iCase = 0; + + Op opFirst = null; + Op opLast = null; + while (true) + { + // get the offset of the byte code instruction + int ofOp = stream.getOffset(); + Op op = null; + + if (fSwitchOp) + { + // lookupswitch cases have case values in the byte code + // (tableswitch cases are ordered; their values are implied) + if (iOpSwitch == LOOKUPSWITCH) + { + iCase = stream.readInt(); + } + + // case branches to a label + Label label = getLabel(((Op) opSwitch).m_of + stream.readInt(), aopDefer); + + // only create a case op if the case label is not the default + if (label != opSwitch.getLabel()) + { + op = new Case(iCase, label); + } + + ++iCase; + if (--cCases <= 0) + { + fSwitchOp = false; + } + } + else + { + // check for line change + if (line != null && ofOp >= line.getOffset()) + { + iLine = line.getLine(); + line = (LineNumberTableAttribute.Entry) + (enmrLines.hasMoreElements() ? enmrLines.nextElement() : null); + } + + // read the byte code instruction + int iOp; + try + { + // read and validate the op + iOp = stream.readUnsignedByte(); + } + catch (EOFException e) + { + break; + } + + // create the op for the byte code + switch (iOp) + { + case NOP: + op = new Nop(); + break; + + case ACONST_NULL: + op = new Aconst(); + break; + + case ICONST_M1: + op = new Iconst(CONSTANT_ICONST_M1); + break; + + case ICONST_0: + op = new Iconst(CONSTANT_ICONST_0); + break; + + case ICONST_1: + op = new Iconst(CONSTANT_ICONST_1); + break; + + case ICONST_2: + op = new Iconst(CONSTANT_ICONST_2); + break; + + case ICONST_3: + op = new Iconst(CONSTANT_ICONST_3); + break; + + case ICONST_4: + op = new Iconst(CONSTANT_ICONST_4); + break; + + case ICONST_5: + op = new Iconst(CONSTANT_ICONST_5); + break; + + case LCONST_0: + op = new Lconst(CONSTANT_LCONST_0); + break; + + case LCONST_1: + op = new Lconst(CONSTANT_LCONST_1); + break; + + case FCONST_0: + op = new Fconst(CONSTANT_FCONST_0); + break; + + case FCONST_1: + op = new Fconst(CONSTANT_FCONST_1); + break; + + case FCONST_2: + op = new Fconst(CONSTANT_FCONST_2); + break; + + case DCONST_0: + op = new Dconst(CONSTANT_DCONST_0); + break; + + case DCONST_1: + op = new Dconst(CONSTANT_DCONST_1); + break; + + case BIPUSH: + op = new Iconst(new IntConstant(stream.readByte())); + break; + + case SIPUSH: + op = new Iconst(new IntConstant(stream.readShort())); + break; + + case LDC: + case LDC_W: + { + int iConst = (iOp == LDC ? stream.readUnsignedByte() + : stream.readUnsignedShort()); + Constant constant = pool.getConstant(iConst); + if (constant instanceof StringConstant) + { + op = new Aconst((StringConstant) constant); + } + else if (constant instanceof IntConstant) + { + op = new Iconst((IntConstant) constant); + } + else if (constant instanceof FloatConstant) + { + op = new Fconst((FloatConstant) constant); + } + else if (constant instanceof ClassConstant) + { + op = new Aconst((ClassConstant) constant); + } + else + { + throw new IOException(CLASS + ".disassembleOps: " + + "Invalid LDC/LDC_W constant type!"); + } + } + break; + + case LDC2_W: + { + Constant constant = pool.getConstant(stream.readUnsignedShort()); + if (constant instanceof LongConstant) + { + op = new Lconst((LongConstant) constant); + } + else if (constant instanceof DoubleConstant) + { + op = new Dconst((DoubleConstant) constant); + } + else + { + throw new IOException(CLASS + ".disassembleOps: " + + "Invalid LDC2_W constant type!"); + } + } + break; + + case ILOAD: + op = new Iload((Ivar) getVariable(IVAR, stream.readUnsignedByte(), aavar)); + break; + + case LLOAD: + op = new Lload((Lvar) getVariable(LVAR, stream.readUnsignedByte(), aavar)); + break; + + case FLOAD: + op = new Fload((Fvar) getVariable(FVAR, stream.readUnsignedByte(), aavar)); + break; + + case DLOAD: + op = new Dload((Dvar) getVariable(DVAR, stream.readUnsignedByte(), aavar)); + break; + + case ALOAD: + op = new Aload((Avar) getVariable(AVAR, stream.readUnsignedByte(), aavar)); + break; + + case ILOAD_0: + case ILOAD_1: + case ILOAD_2: + case ILOAD_3: + op = new Iload((Ivar) getVariable(IVAR, iOp - ILOAD_0, aavar)); + break; + + case LLOAD_0: + case LLOAD_1: + case LLOAD_2: + case LLOAD_3: + op = new Lload((Lvar) getVariable(LVAR, iOp - LLOAD_0, aavar)); + break; + + case FLOAD_0: + case FLOAD_1: + case FLOAD_2: + case FLOAD_3: + op = new Fload((Fvar) getVariable(FVAR, iOp - FLOAD_0, aavar)); + break; + + case DLOAD_0: + case DLOAD_1: + case DLOAD_2: + case DLOAD_3: + op = new Dload((Dvar) getVariable(DVAR, iOp - DLOAD_0, aavar)); + break; + + case ALOAD_0: + case ALOAD_1: + case ALOAD_2: + case ALOAD_3: + op = new Aload((Avar) getVariable(AVAR, iOp - ALOAD_0, aavar)); + break; + + case IALOAD: + op = new Iaload(); + break; + + case LALOAD: + op = new Laload(); + break; + + case FALOAD: + op = new Faload(); + break; + + case DALOAD: + op = new Daload(); + break; + + case AALOAD: + op = new Aaload(); + break; + + case BALOAD: + op = new Baload(); + break; + + case CALOAD: + op = new Caload(); + break; + + case SALOAD: + op = new Saload(); + break; + + case ISTORE: + op = new Istore((Ivar) getVariable(IVAR, stream.readUnsignedByte(), aavar)); + break; + + case LSTORE: + op = new Lstore((Lvar) getVariable(LVAR, stream.readUnsignedByte(), aavar)); + break; + + case FSTORE: + op = new Fstore((Fvar) getVariable(FVAR, stream.readUnsignedByte(), aavar)); + break; + + case DSTORE: + op = new Dstore((Dvar) getVariable(DVAR, stream.readUnsignedByte(), aavar)); + break; + + case ASTORE: + op = new Astore((Avar) getVariable(AVAR, stream.readUnsignedByte(), aavar)); + break; + + case ISTORE_0: + case ISTORE_1: + case ISTORE_2: + case ISTORE_3: + op = new Istore((Ivar) getVariable(IVAR, iOp - ISTORE_0, aavar)); + break; + + case LSTORE_0: + case LSTORE_1: + case LSTORE_2: + case LSTORE_3: + op = new Lstore((Lvar) getVariable(LVAR, iOp - LSTORE_0, aavar)); + break; + + case FSTORE_0: + case FSTORE_1: + case FSTORE_2: + case FSTORE_3: + op = new Fstore((Fvar) getVariable(FVAR, iOp - FSTORE_0, aavar)); + break; + + case DSTORE_0: + case DSTORE_1: + case DSTORE_2: + case DSTORE_3: + op = new Dstore((Dvar) getVariable(DVAR, iOp - DSTORE_0, aavar)); + break; + + case ASTORE_0: + case ASTORE_1: + case ASTORE_2: + case ASTORE_3: + op = new Astore((Avar) getVariable(AVAR, iOp - ASTORE_0, aavar)); + break; + + case IASTORE: + op = new Iastore(); + break; + + case LASTORE: + op = new Lastore(); + break; + + case FASTORE: + op = new Fastore(); + break; + + case DASTORE: + op = new Dastore(); + break; + + case AASTORE: + op = new Aastore(); + break; + + case BASTORE: + op = new Bastore(); + break; + + case CASTORE: + op = new Castore(); + break; + + case SASTORE: + op = new Sastore(); + break; + + case POP: + op = new Pop(); + break; + + case POP2: + op = new Pop2(); + break; + + case DUP: + op = new Dup(); + break; + + case DUP_X1: + op = new Dup_x1(); + break; + + case DUP_X2: + op = new Dup_x2(); + break; + + case DUP2: + op = new Dup2(); + break; + + case DUP2_X1: + op = new Dup2_x1(); + break; + + case DUP2_X2: + op = new Dup2_x2(); + break; + + case SWAP: + op = new Swap(); + break; + + case IADD: + op = new Iadd(); + break; + + case LADD: + op = new Ladd(); + break; + + case FADD: + op = new Fadd(); + break; + + case DADD: + op = new Dadd(); + break; + + case ISUB: + op = new Isub(); + break; + + case LSUB: + op = new Lsub(); + break; + + case FSUB: + op = new Fsub(); + break; + + case DSUB: + op = new Dsub(); + break; + + case IMUL: + op = new Imul(); + break; + + case LMUL: + op = new Lmul(); + break; + + case FMUL: + op = new Fmul(); + break; + + case DMUL: + op = new Dmul(); + break; + + case IDIV: + op = new Idiv(); + break; + + case LDIV: + op = new Ldiv(); + break; + + case FDIV: + op = new Fdiv(); + break; + + case DDIV: + op = new Ddiv(); + break; + + case IREM: + op = new Irem(); + break; + + case LREM: + op = new Lrem(); + break; + + case FREM: + op = new Frem(); + break; + + case DREM: + op = new Drem(); + break; + + case INEG: + op = new Ineg(); + break; + + case LNEG: + op = new Lneg(); + break; + + case FNEG: + op = new Fneg(); + break; + + case DNEG: + op = new Dneg(); + break; + + case ISHL: + op = new Ishl(); + break; + + case LSHL: + op = new Lshl(); + break; + + case ISHR: + op = new Ishr(); + break; + + case LSHR: + op = new Lshr(); + break; + + case IUSHR: + op = new Iushr(); + break; + + case LUSHR: + op = new Lushr(); + break; + + case IAND: + op = new Iand(); + break; + + case LAND: + op = new Land(); + break; + + case IOR: + op = new Ior(); + break; + + case LOR: + op = new Lor(); + break; + + case IXOR: + op = new Ixor(); + break; + + case LXOR: + op = new Lxor(); + break; + + case IINC: + { + int iVar = stream.readUnsignedByte(); + Ivar var = (Ivar) getVariable(IVAR, iVar, aavar); + short sInc = (short) stream.readByte(); + op = new Iinc(var, sInc); + } + break; + + case I2L: + op = new I2l(); + break; + + case I2F: + op = new I2f(); + break; + + case I2D: + op = new I2d(); + break; + + case L2I: + op = new L2i(); + break; + + case L2F: + op = new L2f(); + break; + + case L2D: + op = new L2d(); + break; + + case F2I: + op = new F2i(); + break; + + case F2L: + op = new F2l(); + break; + + case F2D: + op = new F2d(); + break; + + case D2I: + op = new D2i(); + break; + + case D2L: + op = new D2l(); + break; + + case D2F: + op = new D2f(); + break; + + case I2B: + op = new I2b(); + break; + + case I2C: + op = new I2c(); + break; + + case I2S: + op = new I2s(); + break; + + case LCMP: + op = new Lcmp(); + break; + + case FCMPL: + op = new Fcmpl(); + break; + + case FCMPG: + op = new Fcmpg(); + break; + + case DCMPL: + op = new Dcmpl(); + break; + + case DCMPG: + op = new Dcmpg(); + break; + + case IFEQ: + op = new Ifeq(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IFNE: + op = new Ifne(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IFLT: + op = new Iflt(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IFGE: + op = new Ifge(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IFGT: + op = new Ifgt(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IFLE: + op = new Ifle(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IF_ICMPEQ: + op = new If_icmpeq(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IF_ICMPNE: + op = new If_icmpne(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IF_ICMPLT: + op = new If_icmplt(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IF_ICMPGE: + op = new If_icmpge(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IF_ICMPGT: + op = new If_icmpgt(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IF_ICMPLE: + op = new If_icmple(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IF_ACMPEQ: + op = new If_acmpeq(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IF_ACMPNE: + op = new If_acmpne(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case GOTO: + op = new Goto(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case JSR: + op = new Jsr(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case RET: + op = new Ret((Rvar) getVariable(RVAR, stream.readUnsignedByte(), aavar)); + break; + + case TABLESWITCH: + case LOOKUPSWITCH: + { + // skip allignment padding + int cbPad = 3 - ofOp % 4; + for (int i = 0; i < cbPad; ++i) + { + int iPad = stream.readUnsignedByte(); + if (iPad != 0x00) + { + throw new IOException(CLASS + ".disassembleOps: " + + "Illegal padding! (" + iPad + ")"); + } + } + + // read the "default:" label + Label labelDefault = getLabel(ofOp + stream.readInt(), aopDefer); + + // determine the number of cases + if (iOp == TABLESWITCH) + { + int iLow = stream.readInt(); + int iHigh = stream.readInt(); + + cCases = iHigh - iLow + 1; + iCase = iLow; + } + else + { + cCases = stream.readInt(); + } + + // create a generic switch op + op = opSwitch = new Switch(labelDefault); + + // remember the switch type + iOpSwitch = iOp; + + // change the parsing mode to parse the case ops + // 2001.05.03 cp switch can have 0 cases, in which + // case we are already done parsing it + fSwitchOp = cCases > 0; + } + break; + + case IRETURN: + op = new Ireturn(); + break; + + case LRETURN: + op = new Lreturn(); + break; + + case FRETURN: + op = new Freturn(); + break; + + case DRETURN: + op = new Dreturn(); + break; + + case ARETURN: + op = new Areturn(); + break; + + case RETURN: + op = new Return(); + break; + + case GETSTATIC: + op = new Getstatic((FieldConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case PUTSTATIC: + op = new Putstatic((FieldConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case GETFIELD: + op = new Getfield((FieldConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case PUTFIELD: + op = new Putfield((FieldConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case INVOKEVIRTUAL: + op = new Invokevirtual((MethodConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case INVOKESPECIAL: + { + op = new Invokespecial((MethodConstant)pool.getConstant( + stream.readUnsignedShort())); + } + break; + + case INVOKESTATIC: + op = new Invokestatic((MethodConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case INVOKEINTERFACE: + // the invoke interface construct has the byte code + // followed by a two-byte constant index, a single-byte + // "number of arguments" value, and a zero; the number + // of arguments is redundant and the zero is meaningless + op = new Invokeinterface((InterfaceConstant)pool.getConstant( + stream.readUnsignedShort())); + stream.readUnsignedByte(); // nargs is redundant + stream.readUnsignedByte(); // zero + break; + + case INVOKEDYNAMIC: + op = new Invokedynamic((InvokeDynamicConstant) + pool.getConstant(stream.readUnsignedShort())); + stream.readUnsignedByte(); // zero + stream.readUnsignedByte(); // zero + break; + + default: + throw new IOException(CLASS + ".disassembleOps: " + + "Illegal byte code! (" + iOp + ")"); + + case NEW: + op = new New((ClassConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case NEWARRAY: + { + int iType = stream.readUnsignedByte(); + switch (iType) + { + case 4: + op = new Znewarray(); + break; + case 5: + op = new Cnewarray(); + break; + case 6: + op = new Fnewarray(); + break; + case 7: + op = new Dnewarray(); + break; + case 8: + op = new Bnewarray(); + break; + case 9: + op = new Snewarray(); + break; + case 10: + op = new Inewarray(); + break; + case 11: + op = new Lnewarray(); + break; + default: + throw new IOException(CLASS + ".disassembleOps: Unexpected NEWARRAY type (" + iType + ")"); + } + } + break; + + case ANEWARRAY: + op = new Anewarray((ClassConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case ARRAYLENGTH: + op = new Arraylength(); + break; + + case ATHROW: + op = new Athrow(); + break; + + case CHECKCAST: + op = new Checkcast((ClassConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case INSTANCEOF: + op = new Instanceof((ClassConstant)pool.getConstant( + stream.readUnsignedShort())); + break; + + case MONITORENTER: + op = new Monitorenter(); + break; + + case MONITOREXIT: + op = new Monitorexit(); + break; + + case WIDE: + iOp = stream.readUnsignedByte(); + switch (iOp) + { + case IINC: + { + int iVar = stream.readUnsignedShort(); + Ivar var = (Ivar) getVariable(IVAR, iVar, aavar); + short sInc = (short) stream.readShort(); + op = new Iinc(var, sInc); + } + break; + + case ILOAD: + op = new Iload((Ivar) getVariable(IVAR, stream.readUnsignedShort(), aavar)); + break; + + case LLOAD: + op = new Lload((Lvar) getVariable(LVAR, stream.readUnsignedShort(), aavar)); + break; + + case FLOAD: + op = new Fload((Fvar) getVariable(FVAR, stream.readUnsignedShort(), aavar)); + break; + + case DLOAD: + op = new Dload((Dvar) getVariable(DVAR, stream.readUnsignedShort(), aavar)); + break; + + case ALOAD: + op = new Aload((Avar) getVariable(AVAR, stream.readUnsignedShort(), aavar)); + break; + + case ISTORE: + op = new Istore((Ivar) getVariable(IVAR, stream.readUnsignedShort(), aavar)); + break; + + case LSTORE: + op = new Lstore((Lvar) getVariable(LVAR, stream.readUnsignedShort(), aavar)); + break; + + case FSTORE: + op = new Fstore((Fvar) getVariable(FVAR, stream.readUnsignedShort(), aavar)); + break; + + case DSTORE: + op = new Dstore((Dvar) getVariable(DVAR, stream.readUnsignedShort(), aavar)); + break; + + case ASTORE: + op = new Astore((Avar) getVariable(AVAR, stream.readUnsignedShort(), aavar)); + break; + + case RET: + op = new Ret((Rvar) getVariable(RVAR, stream.readUnsignedShort(), aavar)); + break; + + default: + throw new IOException(CLASS + ".disassembleOps: " + + "Illegal byte code modified by WIDE! (" + iOp + ")"); + } + break; + + case MULTIANEWARRAY: + { + int iConst = stream.readUnsignedShort(); + int cDims = stream.readUnsignedByte(); + ClassConstant constant = (ClassConstant) pool.getConstant(iConst); + op = new Multianewarray(constant, cDims); + } + break; + + case IFNULL: + op = new Ifnull(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case IFNONNULL: + op = new Ifnonnull(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case GOTO_W: + op = new Goto(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + + case JSR_W: + op = new Jsr(getLabel(ofOp + stream.readShort(), aopDefer)); + break; + } + } + + // determine how much was read from the stream (i.e. size of op) + int cbOp = stream.getOffset() - ofOp; + + if (op == null) + { + // give this op's size to the previous op + // (e.g. a case op which is the same as the switch default) + opLast.m_cb += cbOp; + } + else + { + // op line number, offset and length + op.m_iLine = iLine; + op.m_of = ofOp; + op.m_cb = cbOp; + + // add the op to the linked list of ops + if (opFirst == null) + { + opFirst = op; + opLast = op; + } + else + { + // append op + opLast.m_opNext = op; + opLast = op; + } + } + } + + // the local variable table offsets/lengths need to be turned into ops; + // the easiest way is to use labels since we keep labels by offset + if (attrVar != null) + { + disassembleVarTable(attrVar, aopDefer); + } + if (attrVarType != null) + { + disassembleVarTable(attrVarType, aopDefer); + } + + + // the labels encounted in the code are already arranged by offset + // (aopDefer) but the labels implied by guarded sections are not + for (Enumeration enmr = vectCatch.elements(); enmr.hasMoreElements(); ) + { + GuardedSection section = (GuardedSection) enmr.nextElement(); + section.setHandler(getLabel(section.getHandlerOffset(), aopDefer)); + } + + // using the guarded section informtion, add try and catch ops; + // for each try, there is one or more catch; + // for each guarded section, there is one catch; + // multiple catch ops share a try op if and only if the guarded + // section start and end PC's are identical; + // guarded sections are arranged in order of the catch ops + int ofTryPrev = -1; + int ofCatchPrev = -1; + Try opTryPrev = null; + for (Enumeration enmr = vectCatch.elements(); enmr.hasMoreElements(); ) + { + GuardedSection section = (GuardedSection) enmr.nextElement(); + int ofTry = section.getTryOffset(); + int ofCatch = section.getCatchOffset(); + if (ofTry == ofTryPrev && ofCatch == ofCatchPrev) + { + // use previous try but create a new catch + Try opTry = opTryPrev; + Catch opCatch = new Catch(opTry, section.getException(), section.getHandler()); + // add the catch to the defered ops + addCatch(ofCatch, opCatch, aopDefer); + } + else + { + // create a new try and catch + Try opTry = new Try(); + Catch opCatch = new Catch(opTry, section.getException(), section.getHandler()); + // add the try and catch to the defered ops + addTry (ofTry , opTry , aopDefer); + addCatch(ofCatch, opCatch, aopDefer); + // remember the bounds of the guarded section in case the + // next one is based on the same try + ofTryPrev = ofTry; + ofCatchPrev = ofCatch; + opTryPrev = opTry; + } + } + + // ensure that all parameters are declared + for (int iParam = 0, cParams = asParam.length, iVar = 0; iParam < cParams; ++iParam) + { + // determine the parameter type + int nOp; + char chType = asParam[iParam].charAt(0); + switch (chType) + { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + nOp = IVAR; + break; + + case 'J': + nOp = LVAR; + break; + + case 'F': + nOp = FVAR; + break; + + case 'D': + nOp = DVAR; + break; + + case '[': + case 'L': + nOp = AVAR; + break; + + case 'V': + throw new IllegalStateException("Parameter cannot be void"); + + default: + throw new IllegalStateException("JVM Type Signature cannot start with '" + + chType + "'"); + } + + // make sure that the parameter is declared + getVariable(nOp, iVar, aavar); + + // iVar is the slot number, which reflects the width (1 or 2) of + // the parameter + iVar += OpDeclare.getJavaWidth(chType); + } + + // preface: open the global scope, add variable declarations + // (including parameters) + Op opPreInit = new Begin(); + Op opPreStop = opPreInit; + for (int iVar = 0; iVar < cVars; ++iVar) + { + for (int iType = 0; iType < 6; ++iType) + { + OpDeclare decl = aavar[iType][iVar]; + if (decl != null) + { + opPreStop.m_opNext = decl; + opPreStop = decl; + } + } + } + + // postlogue: close the global scope + Op opPost = new End(); + opPost.m_of = opLast.m_of + opLast.m_cb; + opPost.m_iLine = opLast.m_iLine; + opLast.m_opNext = opPost; + opLast = opPost; + + // insert labels, try's, catch's + int ofPrev = -1; + for (Op opPrev = null, op = opFirst; op != null; op = (opPrev = op).m_opNext) + { + int of = op.m_of; + if (of > ofPrev) + { + // verify that no ops are being missed + for (int ofSkip = ofPrev + 1; ofSkip < of; ++ofSkip) + { + if (aopDefer[ofSkip] != null) + { + Op opLabel = aopDefer[ofSkip]; + StringBuffer sb = new StringBuffer(); + while (opLabel != null) + { + sb.append("\n ") + .append(opLabel.getClass().getName()) + .append(' ') + .append(opLabel); + opLabel = opLabel.m_opNext; + } + throw new IOException(CLASS + ".disassembleOps: " + + "Non-alligned label, try, or catch at offset " + ofSkip + ":" + sb + + "\n opPrev=" + opPrev.getClass().getName() + " " + opPrev + " @" + opPrev.m_of + + "\n op=" + op.getClass().getName() + " " + op + " @" + op.m_of); + } + } + ofPrev = of; + + // check if any ops are present to insert + Op opInsert = aopDefer[of]; + if (opInsert != null) + { + // find the last op in the linked list of ops to insert + // (also set offset for each op) + opInsert.m_of = of; + opInsert.m_iLine = op.m_iLine; + Op opLastInsert = opInsert; + while (opLastInsert.m_opNext != null) + { + opLastInsert = opLastInsert.m_opNext; + opLastInsert.m_of = of; + opLastInsert.m_iLine = op.m_iLine; + } + + // link in the deferred ops + if (opPrev == null) + { + // insert at the front of the entire set of ops + opFirst = opInsert; + } + else + { + // insert before the current op + opPrev.m_opNext = opInsert; + } + opLastInsert.m_opNext = op; + } + } + } + + // link the preface code into the list of ops + opPreStop.m_opNext = opFirst; + opFirst = opPreInit; + + aopBounds[0] = opFirst; + aopBounds[1] = opLast; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassemble method for an op is not used. The logic is centralized + * in the disassembleOps method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * The implementation provided here is for a simple op. A simple op is + * a single-byte op that exists as both a JASM instruction and a Java + * byte code. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * The implementation provided here is for simple ops and simple pseudo- + * ops. A simple op is a single-byte op that exists as both a JASM + * instruction and a Java byte code. A simple pseudo-op assembles to + * nothing. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + // JSR_W is the last byte code; assume everything higher is a zero + // length pseudo op and everything below is a one length byte code + if (m_iOp <= JSR_W) + { + stream.write(m_iOp); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, OPNAME[m_iOp], null); + } + + /** + * Format a line of assembly. + * + * @return a formatted 3-column string (label, instruction, comment) + */ + protected static String format(String sLabel, String sInstruction, String sComment) + { + StringBuffer sb = new StringBuffer(); + + if (sLabel == null) + { + sLabel = BLANK; + } + if (sInstruction == null) + { + sInstruction = BLANK; + } + if (sComment == null) + { + sComment = BLANK; + } + + boolean fLabel = (sLabel .length() > 0); + boolean fInstruction = (sInstruction.length() > 0); + boolean fComment = (sComment .length() > 0); + + boolean fOverflow = false; + if (fLabel) + { + sb.append(sLabel) + .append(':'); + fOverflow = (sb.length() > BLANK_LABEL.length()); + } + + if (fInstruction || fComment) + { + if (fOverflow) + { + sb.append('\n') + .append(BLANK_LABEL) + .append(SEPARATOR); + } + else + { + sb.append(BLANK_LABEL.substring(sb.length())) + .append(SEPARATOR); + } + + fOverflow = false; + if (fInstruction) + { + sb.append(sInstruction); + fOverflow = (sInstruction.length() >= BLANK_INSTRUCTION.length()); + } + + if (fComment) + { + if (fOverflow) + { + sb.append('\n') + .append(BLANK_LABEL) + .append(SEPARATOR) + .append(BLANK_INSTRUCTION) + .append(SEPARATOR); + } + else + { + sb.append(BLANK_INSTRUCTION.substring(sInstruction.length())) + .append(SEPARATOR); + } + + sb.append("// ") + .append(sComment); + } + } + + return sb.toString(); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return OPNAME[m_iOp]; + } + + /** + * Produce a fairly unique hash code. + * + * @return the hash code for this object + */ + public int hashCode() + { + return m_iHash; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Access the JASM op value. + * + * @return the JASM op value + */ + public int getValue() + { + return m_iOp; + } + + /** + * Set the JASM op value. + * + * @param iOp the JASM op value + */ + protected void setValue(int iOp) + { + m_iOp = iOp; + } + + /** + * Returns the offset of the byte code. If the op was disassembled, the + * offset reflects the location that the byte code was read from the + * stream. If the op was assembled, the offset is the location assigned + * for the op to assemble to. Otherwise the offset is meaningless. + * + * @return the op's byte code offset + */ + public int getOffset() + { + return m_of; + } + + /** + * Sets the byte code offset for the op. + * + * @param of the op's new byte code offset + */ + protected void setOffset(int of) + { + m_of = of; + } + + /** + * Determines if the op results in one or more byte codes. + * + * @return true if the op has a size when assembled + */ + public boolean hasSize() + { + // JSR_W is the last byte code + if (m_iOp <= JSR_W) + { + return true; + } + + // some pseudo-ops don't produce code + switch (m_iOp) + { + case BEGIN: + case END: + case IVAR: + case LVAR: + case FVAR: + case DVAR: + case AVAR: + case RVAR: + case CASE: + case LABEL: + case TRY: + case CATCH: + return false; + + default: + return true; + } + } + + /** + * Returns the size of the byte code. If the op was disassembled, the + * size is the number of bytes read from the stream. If the op was + * assembled, the size is the number of bytes written to the stream. + * Otherwise, the size is meaningless. + * + * @return the size of the op + */ + public int getSize() + { + return m_cb; + } + + /** + * Sets the byte code length for the op. + * + * @param cb the op's new byte code length + */ + protected void setSize(int cb) + { + m_cb = cb; + } + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * The implementation provided here supports simple ops and zero-length + * pseudo-ops. A simple op is a single-byte op that exists as both a + * JASM instruction and a Java byte code. A pseudo-op is an op which + * does not have a one-to-one correlation to any Java byte code; it + * either assembles to one of several byte codes or is used to build + * non-byte-code structures contained within a Code attribute. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + // JSR_W is the last byte code; assume everything higher is a zero + // length pseudo op and everything below is a one length byte code + setSize(m_iOp > JSR_W ? 0 : 1); + } + + /** + * Returns the effect of the byte code on the height of the stack. This + * method is overridden by ops which dynamically calculate their effect + * on the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + return OPEFFECT[m_iOp]; + } + + /** + * Get the expected stack height for this op. If the expected stack size + * is still not calculated (UNKNOWN) after calculating the max stack, + * then the op is considered "not reachable". + */ + protected int getStackHeight() + { + return m_cwStack; + } + + /** + * Set the expected stack height for this op. + * + * @param cwStack the number of words in the stack when this op is + * executed + */ + protected void setStackHeight(int cwStack) + { + // verify that this op always has the same stack size when it is + // executed + int cwStackPrev = m_cwStack; + if (cwStackPrev != UNKNOWN && cwStackPrev != cwStack) + { + throw new IllegalStateException(CLASS + ".setStackHeight: " + + "Height mismatch (" + cwStack + " vs " + cwStackPrev + + ") on " + toString() + "!"); + } + + // store the stack size + m_cwStack = cwStack; + } + + /** + * Determine if the op is reachable; this is only valid after calculating + * the max stack. + * + * @return true if the op was reached by the stack size calculating + * algorithm + */ + protected boolean isReachable() + { + return m_cwStack != UNKNOWN; + } + + /** + * Determine if the op is discardable; by default, this is dependent on + * determining if the op is reachable. + * + * @return true if the op can be discarded from assembly + */ + protected boolean isDiscardable() + { + return !isReachable(); + } + + /** + * Returns the line number of the source code which produced the op. + * + * @return the source code line number of the op + */ + public int getLine() + { + return m_iLine; + } + + /** + * Sets the source code line number for the op. + * + * @param iLine the op's new source code line number + */ + protected void setLine(int iLine) + { + m_iLine = iLine; + } + + /** + * Returns the name of the op. This is for descriptive purposes only. + * + * @return the name of the op + */ + public String getName() + { + return OPNAME[m_iOp]; + } + + /** + * Get the op that follows this one. + * + * @return the next op + */ + public Op getNext() + { + return m_opNext; + } + + /** + * Set the op that follows this one. This is used internally by the + * assembler. + * + * @param op the next op + */ + protected void setNext(Op op) + { + m_opNext = op; + } + + /** + * For debugging or listing purposes, print the op details. + */ + public void print() + { + out('[' + String.valueOf(getOffset()) + "] (" + getLine() + ") " + toString()); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Op"; + + /** + * An empty byte array. + */ + private static final byte[] NO_BYTES = new byte[0]; + + /** + * A big prime number. + * @see + * http://www.utm.edu/research/primes/lists/small/small.html + */ + private static final long BIGPRIME = 1500450271L; + + /** + * A number just a little too big to be an int. + */ + private static final long INTLIMIT = 0x80000000L; + + /** + * A blank string. + */ + private static final String BLANK = ""; + + /** + * The formatted space reserved for a label. + */ + private static final String BLANK_LABEL = " "; + + /** + * The formatted space reserved for an instruction. + */ + private static final String BLANK_INSTRUCTION = " "; + + /** + * The formatted space between columns (label, instruction, comment). + */ + private static final String SEPARATOR = " "; + + /** + * The last hash code given out. + */ + private static int sm_iLastHash; + + /** + * The JASM op value. + */ + private int m_iOp; + + /** + * The offset of the byte code. If the op is disassembled from byte code, + * this value is set to the offset within the disassembled byte code of + * the byte code instruction which disassembled to this op. When the op + * is assembled, this value is set to the expected offset where the op + * will produce byte code. + */ + private int m_of; + + /** + * The length of the byte code. If the op is disassembled from byte code, + * this value is set to the number of bytes that were disassembled to make + * this op. When the op is assembled, this value is set to the expected + * length of byte code that the op will produce. + */ + private int m_cb; + + /** + * The source code line number. + */ + private int m_iLine; + + /** + * The next op. + */ + private Op m_opNext; + + /** + * Count of words on the stack when this op is reached. + */ + private int m_cwStack = UNKNOWN; + + /** + * Hash code. + */ + private int m_iHash; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpArray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpArray.java new file mode 100644 index 0000000000000..e112e095e8056 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpArray.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* This abstract class implements: +*
    +*
  • ZNEWARRAY (0xf3) +*
  • CNEWARRAY (0xf5) +*
  • FNEWARRAY (0xf9) +*
  • DNEWARRAY (0xfa) +*
  • BNEWARRAY (0xf4) +*
  • SNEWARRAY (0xf6) +*
  • INEWARRAY (0xf7) +*
  • LNEWARRAY (0xf8) +*
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class OpArray extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param iOp the op value + */ + public OpArray(int iOp) + { + super(iOp); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + int iOp = super.getValue(); + + stream.writeByte(NEWARRAY); + stream.writeByte(TYPES[iOp-ZNEWARRAY]); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + setSize(2); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "OpArray"; + + /** + * Array types converted to JVM immediate values. + */ + private static final char[] TYPES = {4, 8, 5, 9, 10, 11, 6, 7}; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpBranch.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpBranch.java new file mode 100644 index 0000000000000..549f28530adf5 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpBranch.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* This abstract class implements IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, +* IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, +* IF_ACMPEQ, IF_ACMPNE, GOTO (including the wide version), JSR (including +* the wide version), IFNULL, and IFNONNULL. +*

+* Currently, this class does not assemble to either GOTO_W and JSR_W, +* which means that the assembler can reliably produce 32K of byte code +* per method. Due to the JVM architecture, method code is limited to 64k +* anyway. To support 32k up to 64k of code, additional "macro" support +* would have to be added to each of the branching instructions, since only +* GOTO and JSR have wide versions. +* +* @version 0.50, 06/14/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class OpBranch extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param iOp the op value + * @param label the Label to push + */ + protected OpBranch(int iOp, Label label) + { + super(iOp); + m_label = label; + + if (label == null) + { + throw new IllegalArgumentException(CLASS + + ": Label must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_label.format(), null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + " goto " + m_label.getOffset(); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + int iOp = super.getValue(); + int ofBranch = m_label.getOffset() - this.getOffset(); + + if (ofBranch < Short.MIN_VALUE || ofBranch > Short.MAX_VALUE) + { + throw new IllegalStateException(CLASS + + ".assemble: Branch offset out of range!"); + } + + stream.writeByte(iOp); + stream.writeShort(ofBranch); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + // byte code plus 2-byte signed offset of label + setSize(3); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Access the label which the branching op branches to. + * + * @return the label branched to + */ + public Label getLabel() + { + return m_label; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "OpBranch"; + + /** + * The label branched to by this op. + */ + private Label m_label; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpConst.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpConst.java new file mode 100644 index 0000000000000..97f2bb9bb3f91 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpConst.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* This abstract class implements the field operators (GETSTATIC, PUTSTATIC, +* GETFIELD, and PUTFIELD), the method operators (INVOKEVIRTUAL, +* INVOKESPECIAL, INVOKESTATIC, and INVOKEINTERFACE), NEW, ANEWARRAY, +* CHECKCAST, INSTANCEOF, and MULTIANEWARRAY. +* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class OpConst extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param iOp the op value + * @param constant the Constant referenced by the op + */ + public OpConst(int iOp, Constant constant) + { + super(iOp); + m_constant = constant; + + if (constant == null) + { + throw new IllegalArgumentException(CLASS + + ": Constant must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_constant.format(), null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + m_constant.format(); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_constant); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + int iOp = super.getValue(); + int iConst = pool.findConstant(m_constant); + + stream.writeByte(iOp); + stream.writeShort(iConst); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + // op + 2-byte constant index + setSize(3); + } + + + // ----- accessors ------------------------------------------------------ + + public Constant getConstant() + { + return m_constant; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "OpConst"; + + /** + * The Constant referenced by this op. + */ + private Constant m_constant; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpDeclare.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpDeclare.java new file mode 100644 index 0000000000000..afc6d6ff7fac9 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpDeclare.java @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.assembler; + + +import com.tangosol.java.type.Type; + + +/** +* This abstract class implements IVAR, LVAR, FVAR, DVAR, AVAR, and RVAR. +* +* @version 0.50, 06/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class OpDeclare extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param iOp the op value + * @param sName the variable name or null if no name or temporary + * @param sSig the JVM signature for debugging information + */ + protected OpDeclare(int iOp, String sName, String sSig) + { + this(iOp, sName, sSig, UNKNOWN); + } + + /** + * Construct the op. + * + * @param iOp the op value + * @param sName the variable name or null if no name or temporary + * @param sSig the JVM signature for debugging information + * @param iVar the variable index or UNKNOWN + */ + protected OpDeclare(int iOp, String sName, String sSig, int iVar) + { + super(iOp); + + if (sSig == null) + { + sSig = SIGS[iOp - IVAR]; + } + + if (sName != null && sName.length() == 0) + { + sName = null; + } + + m_sName = sName; + m_sSig = sSig; + m_iVar = iVar; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + StringBuffer sbInstruction = new StringBuffer(getName()); + + // only show non-default signatures + if (m_sSig != null && !m_sSig.equals(SIGS[getValue() - IVAR])) + { + sbInstruction.append(' ') + .append(m_sSig); + } + + sbInstruction.append(' ') + .append(format()); + + String sInstruction = sbInstruction.toString(); + + String sComment = null; + if (m_iVar != UNKNOWN) + { + sComment = "reg[" + m_iVar + "]"; + } + + return format(null, sInstruction, sComment); + } + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toJasm() + { + StringBuffer sb = new StringBuffer(getName()); + + // only show non-default signatures + if (m_sSig != null && !m_sSig.equals(SIGS[getValue() - IVAR])) + { + sb.append(' ') + .append(m_sSig); + } + + sb.append(' ') + .append(format()); + + return sb.toString(); + } + + /** + * Get the variable name or make one up. + * + * @return a displayable variable name (prefixed with # if made up) + */ + public String format() + { + String sName = m_sName; + + if (sName == null) + { + sName = new StringBuffer() + .append('#') + .append(getName().charAt(0)) + .append(m_iVar == UNKNOWN ? hashCode() : m_iVar) + .toString(); + } + + return sName; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the variable type declared by this op. + * + * @return the variable type ('I', 'L', 'F', 'D', 'A', 'R') + */ + public char getType() + { + return TYPES[getValue() - IVAR]; + } + + /** + * Determine the variable name declared by this op. + * + * @return the variable name or null if a temporary + */ + public String getVariableName() + { + return m_sName; + } + + /** + * Determine the variable JVM signature for this op. + * + * @return the variable signature + */ + public String getSignature() + { + return m_sSig; + } + + /** + * Determine if the variable will show up in a debug table. + * + * @return true if the variable is named and not an explicit temporary + */ + public boolean hasDebugInfo() + { + String sName = m_sName; + return sName != null && sName.charAt(0) != '#'; + } + + + // ----- variable ("register") slot + + /** + * Determine the variable slot for the variable declared by this op. + * + * @return the variable slot for this variable declaration + */ + public int getSlot() + { + return m_iVar; + } + + /** + * Set the variable slot for the variable declared by this op. + * + * @param iVar the variable slot for this variable declaration + */ + protected void setSlot(int iVar) + { + m_iVar = iVar; + } + + /** + * Get the variable width. The width of a variable is defined as the + * number of slots used by the variable in the local variable storage + * or the affect on the stack by pushing the variable. + * + * @return the number of words used by this variable + */ + public int getWidth() + { + int iOp = getValue(); + return (iOp == LVAR || iOp == DVAR ? 2 : 1); + } + + /** + * Get the variable width. The width of a variable is defined as the + * number of slots used by the variable in the local variable storage + * or the affect on the stack by pushing the variable. + * + * @param ch the variable type + * + * @return the number of words used by the variable type + */ + public static int getWidth(char ch) + { + return (ch == 'L' || ch == 'D' ? 2 : 1); + } + + /** + * Get the variable width. The width of a variable is defined as the + * number of slots used by the variable in the local variable storage + * or the affect on the stack by pushing the variable. + * + * @param ch the variable type (J for Long, L for reference, etc.) + * + * @return the number of words used by the variable type + */ + public static int getJavaWidth(char ch) + { + return (ch == 'J' || ch == 'D' ? 2 : 1); + } + + + // ----- variable scope + + /** + * Determine the beginning of the scope. + * + * @return the op representing the beginning of the scope + */ + protected Begin getBegin() + { + return m_begin; + } + + /** + * Set the scope within which the variable is declared by this op. + * + * @param begin the op representing the beginning of the scope + */ + protected void setBegin(Begin begin) + { + m_begin = begin; + } + + /** + * Determine the end of the scope. + * + * @return the op representing the end of the scope + */ + protected End getEnd() + { + return m_begin.getEnd(); + } + + + // ----- code context + + /** + * Determine the code context of the variable. + * + * @return the label that starts the code context within which the + * variable is declared + */ + protected Label getContext() + { + return m_labelContext; + } + + /** + * Specify the code context for the variable. + * + * @param label the label that starts the code context within which the + * variable is declared + */ + protected void setContext(Label label) + { + m_labelContext = label; + } + + /** + * Determine the subroutine depth of the variable. + * + * @return the maximum depth in the subroutine call chain that this + * variable will be declared in + */ + protected int getDepth() + { + Label labelContext = m_labelContext; + return (labelContext == null ? 0 : labelContext.getDepth()); + } + + + // ----- helpers -------------------------------------------------------- + + /** + * Get a new variable for the specified Java signature type + * + * @param sType a JVM type signature + * @param sName variable name + */ + public static OpDeclare getDeclareVar(String sType, String sName) + { + switch (sType.charAt(0)) + { + case 'Z': + return new Ivar(sName, "Z"); + case 'B': + return new Ivar(sName, "B"); + case 'C': + return new Ivar(sName, "C"); + case 'S': + return new Ivar(sName, "S"); + case 'I': + return new Ivar(sName, "I"); + case 'J': + return new Lvar(sName); + case 'F': + return new Fvar(sName); + case 'D': + return new Dvar(sName); + default: + return new Avar(sName, sType); + } + } + + // Note: the following methods should be "virtualized", but since + // for now is coded in one place due to the simplicity + /** + * Get a "load variable" op for this variable + */ + public Op getLoadOp() + { + return getLoadOp(this); + } + + // Note: the following methods should be "virtualized", but since + // for now is coded in one place due to the simplicity + /** + * Get a "load variable" op for this variable + */ + public static OpLoad getLoadOp(OpDeclare opDeclare) + { + switch (opDeclare.getSignature().charAt(0)) + { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + return new Iload((Ivar) opDeclare); + case 'J': + return new Lload((Lvar) opDeclare); + case 'F': + return new Fload((Fvar) opDeclare); + case 'D': + return new Dload((Dvar) opDeclare); + default: + return new Aload((Avar) opDeclare); + } + } + + /** + * Get a "store variable" op for this variable + */ + public Op getStoreOp() + { + switch (getSignature().charAt(0)) + { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + return new Istore((Ivar) this); + case 'J': + return new Lstore((Lvar) this); + case 'F': + return new Fstore((Fvar) this); + case 'D': + return new Dstore((Dvar) this); + default: + return new Astore((Avar) this); + } + } + + /** + * Get a "return" op for this variable + */ + public Op getReturnOp() + { + return getReturnOp(getSignature()); + } + + /** + * Get a "return" op for this variable + */ + public static Op getReturnOp(String sSig) + { + switch (sSig.charAt(0)) + { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + return new Ireturn(); + case 'J': + return new Lreturn(); + case 'F': + return new Freturn(); + case 'D': + return new Dreturn(); + case 'V': + return new Return(); + default: + return new Areturn(); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "OpDeclare"; + + /** + * Variable types. + */ + private static final char[] TYPES = {'I','L','F','D','A','R'}; + + /** + * Default JVM signature per type. + */ + private static final String[] SIGS = + { + Type.INT .getSignature(), + Type.LONG .getSignature(), + Type.FLOAT .getSignature(), + Type.DOUBLE.getSignature(), + Type.OBJECT.getSignature(), + null, + }; + + /** + * Variable name. + */ + private String m_sName; + + /** + * The JVM signature of the variable. + */ + private String m_sSig; + + /** + * The variable slot for the variable declared by this op. + */ + private int m_iVar = UNKNOWN; + + /** + * The scope creation op. + */ + private Begin m_begin; + + /** + * The scope destruction op. + */ + private End m_end; + + /** + * The code context (main or subroutine) this variable is declared in. + */ + private Label m_labelContext; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpLoad.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpLoad.java new file mode 100644 index 0000000000000..a19ea68273827 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpLoad.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* This abstract class implements ILOAD, LLOAD, FLOAD, DLOAD, and ALOAD. +* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class OpLoad extends Op implements Constants, OpVariable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param iOp the op value + * @param var the variable to push + */ + public OpLoad(int iOp, OpDeclare var) + { + super(iOp); + m_var = var; + + if (var == null) + { + throw new IllegalArgumentException(CLASS + + ": Variable must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_var.format(), null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + m_var.format(); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + int n = m_var.getSlot(); + int iOp = super.getValue(); + + // 0..3 are optimized to a byte code + if (n <= 3) + { + // luckily this is just math ... the ops are symmetrical + stream.writeByte((iOp - ILOAD) * 4 + ILOAD_0 + n); + } + // up to 0xFF uses the normal load + else if (n <= 0xFF) + { + stream.writeByte(iOp); + stream.writeByte(n); + } + // otherwise use the WIDE modifier + else + { + stream.writeByte(WIDE); + stream.writeByte(iOp); + stream.writeShort(n); + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + int n = m_var.getSlot(); + + // 0..3 are optimized to a byte code + if (n <= 3) + { + setSize(1); + } + // up to 0xFF uses the normal load + else if (n <= 0xFF) + { + setSize(2); + } + // otherwise use the WIDE modifier + else + { + setSize(4); + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the variable type loaded by this op. + * + * @return the variable type loaded by the op ('I', 'L', 'F', 'D', or 'A') + */ + public char getType() + { + return TYPES[super.getValue() - ILOAD]; + } + + /** + * Determine the variable loaded by this op. + * + * @return the variable + */ + public OpDeclare getVariable() + { + return m_var; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "OpLoad"; + + /** + * Variable types. + */ + private static final char[] TYPES = {'I','L','F','D','A'}; + + /** + * The variable loaded by this op. + */ + private OpDeclare m_var; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpStore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpStore.java new file mode 100644 index 0000000000000..562c409d646c4 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpStore.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* This abstract class implements ISTORE, LSTORE, FSTORE, DSTORE, and ASTORE. +* Additionally, the pseudo op RSTORE is supported, assembling to ASTORE. +* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class OpStore extends Op implements Constants, OpVariable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param iOp the op value + * @param var the variable to store + */ + public OpStore(int iOp, OpDeclare var) + { + super(iOp); + m_var = var; + + if (var == null) + { + throw new IllegalArgumentException(CLASS + + ": Variable must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_var.format(), null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + m_var.format(); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + int n = m_var.getSlot(); + int iOp = super.getValue(); + + // RSTORE is not a real op -- return address actually uses ASTORE + if (iOp == RSTORE) + { + iOp = ASTORE; + } + + // 0..3 are optimized to a byte code + if (n <= 3) + { + // luckily this is just math ... the ops are symmetrical + stream.writeByte((iOp - ISTORE) * 4 + ISTORE_0 + n); + } + // up to 0xFF uses the normal store + else if (n <= 0xFF) + { + stream.writeByte(iOp); + stream.writeByte(n); + } + // otherwise use the WIDE modifier + else + { + stream.writeByte(WIDE); + stream.writeByte(iOp); + stream.writeShort(n); + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + int n = m_var.getSlot(); + + // 0..3 are optimized to a byte code + if (n <= 3) + { + setSize(1); + } + // up to 0xFF uses the normal store + else if (n <= 0xFF) + { + setSize(2); + } + // otherwise use the WIDE modifier + else + { + setSize(4); + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the variable type stored by this op. + * + * @return the variable type stored by the op + * ('I', 'L', 'F', 'D', 'A', or 'R') + */ + public char getType() + { + int iOp = super.getValue(); + return iOp == RSTORE ? 'R' : TYPES[iOp - ISTORE]; + } + + /** + * Determine the variable affected by this op. + * + * @return the variable + */ + public OpDeclare getVariable() + { + return m_var; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "OpStore"; + + /** + * Variable types. + */ + private static final char[] TYPES = {'I','L','F','D','A','R'}; + + /** + * The variable stored by this op. + */ + private OpDeclare m_var; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpSwitch.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpSwitch.java new file mode 100644 index 0000000000000..1bb098f88ece1 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpSwitch.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.TreeSet; + + +/** +* The SWITCH op implements the general multi-case switch. It assembles to +* either a TABLESWITCH or a LOOKUPSWITCH depending on which is more +* "efficient". Since a TABLESWITCH is always more efficient for performance, +* it is always selected unless the amount of additional code generated for +* the TABLESWITCH compared to the LOOKUPSWITCH is considered to be too +* expensive in terms of size. +*

+* JASM op         :  SWITCH        (0xfc)
+*                    TABLESWITCH   (0xaa)
+*                    LOOKUPSWITCH  (0xab)
+* JVM byte code(s):  TABLESWITCH   (0xaa)
+*                    LOOKUPSWITCH  (0xab)
+* Details         :  The Java byte code TABLESWITCH has the following format:
+*                       ub   TABLESWITCH      (0xaa)
+*                       ub[] [0-3 byte pad]   (0x00..)
+*                       s4   default offset
+*                       s4   low case value
+*                       s4   high case value
+*                       s4[] branch offsets
+*                       
+*                    The byte code LOOKUPSWITCH has the following format:
+*                       ub   LOOKUPSWITCH     (0xab)
+*                       ub[] [0-3 byte pad]   (0x00..)
+*                       s4   default offset
+*                       u4   number of cases
+*                       struct[]
+*                           {
+*                           s4  case value
+*                           s4  branch offset
+*                           }
+* 
+* +* @version 0.50, 06/17/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class OpSwitch extends OpBranch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the default label to branch to if no cases match + */ + protected OpSwitch(int iOp, Label label) + { + super(iOp, label); + setSwitch(iOp); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + // write the byte code + int iOp = getSwitch(); + stream.writeByte(iOp); + + // align to a 4-byte boundary + int ofOp = getOffset(); + int cbPad = 3 - ofOp % 4; + for (int i = 0; i < cbPad; ++i) + { + stream.writeByte(0x00); + } + + // default branch + int ofDefault = getLabel().getOffset() - ofOp; + stream.writeInt(ofDefault); + + // cases + Case[] acase = getCases(); + int cCases = acase.length; + switch (iOp) + { + case TABLESWITCH: + { + if (cCases == 0) + { + throw new IllegalStateException(CLASS + ".assemble: " + + "TABLESWITCH requires at least one case!"); + } + + int iLow = acase[0].getCase(); + stream.writeInt(iLow); + + int iHigh = acase[cCases - 1].getCase(); + stream.writeInt(iHigh); + + for (int i = iLow, iCase = 0; i <= iHigh; ++i) + { + Case op = acase[iCase]; + if (i == op.getCase()) + { + // this particular case is specified; use its label + stream.writeInt(op.getLabel().getOffset() - ofOp); + ++iCase; + } + else + { + // this particular case value is not specified; go to + // the default label + stream.writeInt(ofDefault); + } + } + } + break; + + case LOOKUPSWITCH: + stream.writeInt(cCases); + for (int i = 0; i < cCases; ++i) + { + Case op = acase[i]; + stream.writeInt(op.getCase()); + stream.writeInt(op.getLabel().getOffset() - ofOp); + } + break; + + default: + throw new IllegalStateException(CLASS + ".assemble: " + + "Illegal byte code! (" + iOp + ")"); + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + Case[] acase = getCases(); + int cCases = acase.length; + + // determine padding which aligns the 4-byte values in the complex + // byte code structure to a 4-byte boundary + int cbPad = 3 - getOffset() % 4; + + int iOp = getSwitch(); + switch (iOp) + { + case TABLESWITCH: + if (cCases > 1) + { + cCases = acase[cCases-1].getCase() - acase[0].getCase() + 1; + } + setSize(cbPad + 13 + 4 * cCases); + break; + + case LOOKUPSWITCH: + setSize(cbPad + 9 + 8 * cCases); + break; + + default: + throw new IllegalStateException(CLASS + ".calculateSize: " + + "Illegal byte code! (" + iOp + ")"); + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + " default: goto " + getLabel().getOffset(); + } + + /** + * Get the complete set of cases. + * + * @return an ordered array of case ops + */ + protected Case[] getCases() + { + Case[] acase = m_acase; + if (acase == null) + { + TreeSet setCase = new TreeSet(); + for (Op op = this.getNext(); op instanceof Case; op = op.getNext()) + { + // only keep non-default case's + Case opCase = (Case) op; + if (opCase.getLabel() != this.getLabel()) + { + setCase.add(opCase); + } + } + + m_acase = acase = (Case[]) setCase.toArray(CASE_ARRAY); + } + + return acase; + } + + /** + * Gets the op used to implement the switch. + * + * @return one of SWITCH, TABLESWITCH, LOOKUPSWITCH + */ + protected int getSwitch() + { + return m_iSwitchOp; + } + + /** + * Sets the op used to implement the switch. By the time this op + * assembles, it must be set to TABLESWITCH or LOOKUPSWITCH. + * + * @param iOp one of SWITCH, TABLESWITCH, LOOKUPSWITCH + */ + protected void setSwitch(int iOp) + { + switch (iOp) + { + case SWITCH: + case TABLESWITCH: + case LOOKUPSWITCH: + m_iSwitchOp = iOp; + break; + default: + throw new IllegalArgumentException(CLASS + ".setSwitch: Illegal op (" + iOp + ")"); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "OpSwitch"; + + /** + * An array of type Case[]. + */ + private static final Case[] CASE_ARRAY = new Case[0]; + + /** + * Which byte code will be used to assemble the op; SWITCH means "not yet + * decided". + */ + private int m_iSwitchOp; + + /** + * Cached list of cases. + */ + private Case[] m_acase; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpVariable.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpVariable.java new file mode 100644 index 0000000000000..a28a72640bc7f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/OpVariable.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +/** +* The OpVariable interface is implemented by ops which reference a variable. +* +* @version 0.50, 06/26/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public interface OpVariable + { + /** + * Determine the variable referenced or affected by this op. + * + * @return the variable + */ + public OpDeclare getVariable(); + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Pop.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Pop.java new file mode 100644 index 0000000000000..f607f5045fa90 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Pop.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The POP simple op pops one word off of the stack. +*

+* JASM op         :  POP  (0x57)
+* JVM byte code(s):  POP  (0x57)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Pop extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Pop() + { + super(POP); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Pop"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Pop2.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Pop2.java new file mode 100644 index 0000000000000..4f37628a9b9e3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Pop2.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The POP2 simple op pops two words (a long or double) off of the stack. +*

+* JASM op         :  POP2  (0x58)
+* JVM byte code(s):  POP2  (0x58)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Pop2 extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Pop2() + { + super(POP2); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Pop2"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Putfield.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Putfield.java new file mode 100644 index 0000000000000..d05ab7e1168b7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Putfield.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The PUTFIELD op stores an object (instance) field. +*

+* JASM op         :  PUTFIELD    (0xb5)
+* JVM byte code(s):  PUTFIELD    (0xb5)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Putfield extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the FieldConstant + */ + public Putfield(FieldConstant constant) + { + super(PUTFIELD, constant); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // pops reference, value + return -1 - ((FieldConstant) getConstant()).getVariableSize(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Putfield"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Putstatic.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Putstatic.java new file mode 100644 index 0000000000000..e9c4ba6859662 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Putstatic.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The PUTSTATIC op stores a class (static) field. +*

+* JASM op         :  PUTSTATIC    (0xb3)
+* JVM byte code(s):  PUTSTATIC    (0xb3)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Putstatic extends OpConst implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param constant the FieldConstant + */ + public Putstatic(FieldConstant constant) + { + super(PUTSTATIC, constant); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Returns the effect of the byte code on the height of the stack. + * + * @return the number of words pushed (if positive) or popped (if + * negative) from the stack by the op + */ + public int getStackChange() + { + // pops value + return -((FieldConstant) getConstant()).getVariableSize(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Putstatic"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RefConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RefConstant.java new file mode 100644 index 0000000000000..a19bb067c8df8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RefConstant.java @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents the class, name and type of a Java Virtual Machine method or +* field. This constant type supports the byte code get/put operations for +* fields and the invoke operations for methods. +*

+* This constant type references a ClassConstant and a SignatureConstant. +* +* @version 0.50, 05/13/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class RefConstant extends Constant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected RefConstant(int nTag) + { + super(nTag); + } + + /** + * Construct a constant which specifies a class/interface field/method. + * This constructor is a helper which creates the necessary constants + * from the passed strings. + * + * @param sClass the class name + * @param sName the method/field name + * @param sType the method signature/field type + */ + protected RefConstant(int nTag, String sClass, String sName, String sType) + { + this(nTag, new ClassConstant(sClass), new SignatureConstant(sName, sType)); + } + + /** + * Construct a constant which references the passed constants. + * + * @param constantClz the referenced Class constant which contains the + * name of the class + * @param constantSig the referenced Signature constant which contains + * the type/name information for the field/method + */ + protected RefConstant(int nTag, ClassConstant constantClz, SignatureConstant constantSig) + { + this(nTag); + + if (constantClz == null || constantSig == null) + { + throw new IllegalArgumentException(CLASS + ": Values cannot be null!"); + } + + m_clz = constantClz; + m_sig = constantSig; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iRefClz = stream.readUnsignedShort(); + m_iRefSig = stream.readUnsignedShort(); + } + + /** + * Resolve referenced constants. + * + * @param pool the constant pool containing any constant referenced by + * this constant (i.e. referenced by index) + */ + protected void postdisassemble(ConstantPool pool) + { + ClassConstant constClz = m_clz; + SignatureConstant constSig = m_sig; + + if (constClz == null || constSig == null) + { + constClz = m_clz = (ClassConstant) pool.getConstant(m_iRefClz); + constSig = m_sig = (SignatureConstant) pool.getConstant(m_iRefSig); + + // post disassemble dependent + constClz.postdisassemble(pool); + constSig.postdisassemble(pool); + } + } + + /** + * Register referenced constants. + * + * @param pool the constant pool to register referenced constants with + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_clz); + pool.registerConstant(m_sig); + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeShort(pool.findConstant(m_clz)); + stream.writeShort(pool.findConstant(m_sig)); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + RefConstant that = (RefConstant) obj; + int nResult = this.m_clz.compareTo(that.m_clz); + if (nResult == 0) + { + nResult = this.m_sig.compareTo(that.m_sig); + } + return nResult; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return (m_clz == null ? "[" + m_iRefClz + ']' : m_clz.toString()) + ", " + + (m_sig == null ? "[" + m_iRefSig + ']' : m_sig.toString()); + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + return m_clz.format() + '.' + m_sig.format(); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + RefConstant that = (RefConstant) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_clz.equals(that.m_clz) + && this.m_sig.equals(that.m_sig); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the name of the class (or interface). + * + * @return the class name + */ + public String getClassName() + { + return m_clz.getValue(); + } + + /** + * Get the name of the field/method. + * + * @return the field/method name + */ + public String getName() + { + return m_sig.getName(); + } + + /** + * Get the type of the field or signature of the method. + * + * @return the field type/method signature + */ + public String getType() + { + return m_sig.getType(); + } + + /** + * Get the Class constant which holds the class name. + * + * @return the Class constant which contains the class name + */ + public ClassConstant getClassConstant() + { + return m_clz; + } + + /** + * Get the signature constant which holds the type and name of the + * field/method reference. + * + * @return the signature constant which contains the type/name + */ + public SignatureConstant getSignatureConstant() + { + return m_sig; + } + + /** + * Get the UTF constant which holds the field/method name. + * + * @return the UTF constant which contains the name + */ + public UtfConstant getNameConstant() + { + return m_sig.getNameConstant(); + } + + /** + * Get the UTF constant which holds the field type/method signature. + * + * @return the UTF constant which contains the type + */ + public UtfConstant getTypeConstant() + { + return m_sig.getTypeConstant(); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "RefConstant"; + + /** + * The class constant representing the class/interface containing the + * referenced field/method. + */ + private ClassConstant m_clz; + + /** + * The type/name information for the field/method. + */ + private SignatureConstant m_sig; + + /** + * Class pool index of the class constant. + *

+ * If this has been disassembled (previous to the "postdisassemble" + * invocation), the reference to the UTF constant is still by index, as + * it was in the persistent .class structure. + */ + private int m_iRefClz; + + /** + * Class pool index of the signature constant. + *

+ * If this has been disassembled (previous to the "postdisassemble" + * invocation), the reference to the UTF constant is still by index, as + * it was in the persistent .class structure. + */ + private int m_iRefSig; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ret.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ret.java new file mode 100644 index 0000000000000..927a8b8a0ff00 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Ret.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataOutput; + +import java.util.HashSet; + + +/** +* The RET op returns from a JSR (internal subroutine). +*

+* JASM op         :  RET       (0xa9??)
+* JVM byte code(s):  RET       (0xa9??)
+*                    WIDE RET  (0xc4a9????)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Ret extends Op implements Constants, OpVariable + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the Variable to push + */ + public Ret(Rvar var) + { + super(RET); + m_var = var; + + if (var == null) + { + throw new IllegalArgumentException(CLASS + + ": Variable must not be null!"); + } + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toString() + { + return format(null, getName() + ' ' + m_var.format(), null); + } + + /** + * Produce a human-readable string describing the byte code. + * + * @return a string describing the byte code + */ + public String toJasm() + { + return getName() + ' ' + m_var.format(); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + int n = m_var.getSlot(); + + // up to 0xFF uses the normal ret + if (n <= 0xFF) + { + stream.writeByte(RET); + stream.writeByte(n); + } + // otherwise use the WIDE modifier + else + { + stream.writeByte(WIDE); + stream.writeByte(RET); + stream.writeShort(n); + } + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + setSize(m_var.getSlot() <= 0xFF ? 2 : 4); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the variable affected by this op. + * + * @return the variable + */ + public OpDeclare getVariable() + { + return m_var; + } + + + // ----- for definite assignment + + /** + * Get list of variables in-scope and definitely-assigned at the RET. + * + * @return the set of in-scope assigned vars + */ + protected HashSet getVariables() + { + return m_setVars; + } + + /** + * Set list of variables in-scope and definitely-assigned at the RET. + * + * @param setVars the set of in-scope assigned vars + */ + protected void setVariables(HashSet setVars) + { + m_setVars = setVars; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Ret"; + + /** + * The variable loaded by this op. + */ + private Rvar m_var; + + /** + * What variables are currently in scope and have a value when the RET + * is encountered. + */ + private HashSet m_setVars; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Return.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Return.java new file mode 100644 index 0000000000000..a76d0f0d65894 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Return.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The RETURN simple op returns from the method. +*

+* JASM op         :  RETURN  (0xb1)
+* JVM byte code(s):  RETURN  (0xb1)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Return extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Return() + { + super(RETURN); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Return"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Rstore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Rstore.java new file mode 100644 index 0000000000000..4934afba7888c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Rstore.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The RSTORE variable-size op stores a return address. +*

+* JASM op         :  RSTORE    (0x3a)
+* JVM byte code(s):  ASTORE    (0x3a)
+*                    ASTORE_0  (0x4b)
+*                    ASTORE_1  (0x4c)
+*                    ASTORE_2  (0x4d)
+*                    ASTORE_3  (0x4e)
+* Details         :
+* 
+* +* @version 0.50, 06/17/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Rstore extends OpStore implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param var the variable to push + */ + public Rstore(Rvar var) + { + super(RSTORE, var); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + // if the label context (a sub-routine) does not have a reachable + // RET op, then assemble to nothing (since the JSR will assemble + // to a GOTO) + if (getVariable().getContext().getRet() == null) + { + return; + } + + super.assemble(stream, pool); + } + + + // ----- Op operations -------------------------------------------------- + + /** + * Calculate and set the size of the assembled op based on the offset of + * the op and the constant pool which is passed. + * + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void calculateSize(ConstantPool pool) + { + // see comment in assemble() + if (getVariable().getContext().getRet() == null) + { + setSize(0); + } + else + { + super.calculateSize(pool); + } + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Rstore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeInvisibleAnnotationsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeInvisibleAnnotationsAttribute.java new file mode 100644 index 0000000000000..0d07a77a6f7a3 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeInvisibleAnnotationsAttribute.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +/** +* Represents a Java Virtual Machine "RuntimeInvisibleAnnotations" attribute +* +* +*

+* The RuntimeInvisibleAnnotations Attribute is defined by the JDK 1.5 +* documentation as: +*

+*

+*   RuntimeInvisibleAnnotations_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u2 num_annotations;
+*       annotation annotations[num_annotations]
+*       }
+* 
+* +* @author rhl 2008.09.23 +*/ +public class RuntimeInvisibleAnnotationsAttribute + extends AbstractAnnotationsAttribute + implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a runtime invisible annotations attribute. + * + * @param context the JVM structure containing the attribute + */ + protected RuntimeInvisibleAnnotationsAttribute(VMStructure context) + { + super(context, ATTR_RTINVISANNOT); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeInvisibleParameterAnnotationsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeInvisibleParameterAnnotationsAttribute.java new file mode 100644 index 0000000000000..13ffa11f266e8 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeInvisibleParameterAnnotationsAttribute.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +/** +* Represents a Java Virtual Machine "RuntimeInvisibleParameterAnnotations" attribute +* +* +*

+* The RuntimeInvisibleParameterAnnotations Attribute is defined by the JDK 1.5 +* documentation as: +*

+*

+*   RuntimeInvisibleParameterAnnotations_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u1 num_parameters;
+*           {
+*           u2 num_annotations;
+*           annotation annotations[num_annotations]
+*           } parameter_annotations[num_parameters]
+*       }
+* 
+* +* @author rhl 2008.09.23 +*/ +public class RuntimeInvisibleParameterAnnotationsAttribute + extends AbstractParameterAnnotationsAttribute + implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a runtime invisible parameter annotations attribute. + * + * @param context the JVM structure containing the attribute + */ + protected RuntimeInvisibleParameterAnnotationsAttribute(VMStructure context) + { + super(context, ATTR_RTINVISPARAMANNOT); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeInvisibleTypeAnnotationsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeInvisibleTypeAnnotationsAttribute.java new file mode 100644 index 0000000000000..0b682d7e23e8c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeInvisibleTypeAnnotationsAttribute.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +/** +* Represents a Java Virtual Machine "RuntimeInvisibleTypeAnnotations" attribute +* +* +*

+* The RuntimeInvisibleTypeAnnotations Attribute is defined by the JDK 1.8 +* documentation as: +*

+*

+*   RuntimeInvisibleTypeAnnotations_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u2 num_annotations;
+*       type_annotation annotations[num_annotations]
+*       }
+* 
+* +* @author hr 2014.05.28 +*/ +public class RuntimeInvisibleTypeAnnotationsAttribute + extends AbstractAnnotationsAttribute + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a runtime invisible type annotations attribute. + * + * @param context the JVM structure containing the attribute + */ + protected RuntimeInvisibleTypeAnnotationsAttribute(VMStructure context) + { + super(context, ATTR_RTINVISTANNOT); + } + + + // ----- AbstractAnnotationsAttribute operations ------------------------ + + @Override + protected Annotation instantiateAnnotation() + { + return new TypeAnnotation(); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeVisibleAnnotationsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeVisibleAnnotationsAttribute.java new file mode 100644 index 0000000000000..f5a61cee960fe --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeVisibleAnnotationsAttribute.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +/** +* Represents a Java Virtual Machine "RuntimeVisibleAnnotations" attribute +* +* +*

+* The RuntimeVisibleAnnotations Attribute is defined by the JDK 1.5 +* documentation as: +*

+*

+*   RuntimeVisibleAnnotations_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u2 num_annotations;
+*       annotation annotations[num_annotations]
+*       }
+* 
+* +* @author rhl 2008.09.23 +*/ +public class RuntimeVisibleAnnotationsAttribute + extends AbstractAnnotationsAttribute + implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a runtime visible annotations attribute. + * + * @param context the JVM structure containing the attribute + */ + protected RuntimeVisibleAnnotationsAttribute(VMStructure context) + { + super(context, ATTR_RTVISANNOT); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeVisibleParameterAnnotationsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeVisibleParameterAnnotationsAttribute.java new file mode 100644 index 0000000000000..dffffcff6dbb6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeVisibleParameterAnnotationsAttribute.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +/** +* Represents a Java Virtual Machine "RuntimeVisibleParameterAnnotations" attribute +* +* +*

+* The RuntimeVisibleParameterAnnotations Attribute is defined by the JDK 1.5 +* documentation as: +*

+*

+*   RuntimeVisibleParameterAnnotations_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u1 num_parameters;
+*           {
+*           u2 num_annotations;
+*           annotation annotations[num_annotations]
+*           } parameter_annotations[num_parameters]
+*       }
+* 
+* +* @author rhl 2008.09.23 +*/ +public class RuntimeVisibleParameterAnnotationsAttribute + extends AbstractParameterAnnotationsAttribute + implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a runtime visible parameter annotations attribute. + * + * @param context the JVM structure containing the attribute + */ + protected RuntimeVisibleParameterAnnotationsAttribute(VMStructure context) + { + super(context, ATTR_RTVISPARAMANNOT); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeVisibleTypeAnnotationsAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeVisibleTypeAnnotationsAttribute.java new file mode 100644 index 0000000000000..417df87cc9a7c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/RuntimeVisibleTypeAnnotationsAttribute.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +/** +* Represents a Java Virtual Machine "RuntimeVisibleTypeAnnotations" attribute +* +* +*

+* The RuntimeVisibleTypeAnnotations Attribute is defined by the JDK 1.8 +* documentation as: +*

+*

+*   RuntimeVisibleTypeAnnotations_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length;
+*       u2 num_annotations;
+*       type_annotation annotations[num_annotations]
+*       }
+* 
+* +* @author hr 2014.05.28 +*/ +public class RuntimeVisibleTypeAnnotationsAttribute + extends AbstractAnnotationsAttribute + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a runtime visible type annotations attribute. + * + * @param context the JVM structure containing the attribute + */ + protected RuntimeVisibleTypeAnnotationsAttribute(VMStructure context) + { + super(context, ATTR_RTVISTANNOT); + } + + + // ----- AbstractAnnotationsAttribute operations ------------------------ + + @Override + protected Annotation instantiateAnnotation() + { + return new TypeAnnotation(); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Rvar.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Rvar.java new file mode 100644 index 0000000000000..a23d16837d978 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Rvar.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The RVAR pseudo-op declares a return address variable. The variable can +* optionally be named. +*

+* JASM op         :  RVAR  (0xf1)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Rvar extends OpDeclare implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Rvar() + { + super(RVAR, null, null); + } + + /** + * Construct the op. + * + * @param sName the name of the variable + */ + public Rvar(String sName) + { + super(RVAR, sName, null); + } + + /** + * Construct the op. Used by disassembler. + * + * @param iVar the variable index + */ + protected Rvar(int iVar) + { + super(RVAR, null, null, iVar); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Rvar"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Saload.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Saload.java new file mode 100644 index 0000000000000..e9c09505b70ff --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Saload.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The SALOAD simple op pushes a short element from an array onto the +* stack. +*

+* JASM op         :  SALOAD (0x35)
+* JVM byte code(s):  SALOAD (0x35)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Saload extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Saload() + { + super(SALOAD); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Saload"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Sastore.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Sastore.java new file mode 100644 index 0000000000000..50f4b474f019d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Sastore.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The SASTORE simple op stores a short array element. +*

+* JASM op         :  SASTORE (0x56)
+* JVM byte code(s):  SASTORE (0x56)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Sastore extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Sastore() + { + super(SASTORE); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Sastore"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SignatureAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SignatureAttribute.java new file mode 100644 index 0000000000000..0edf7ca3d857a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SignatureAttribute.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a Java Virtual Machine "Signature" attribute which +* specifies the class/method/field signature. +* +*

+* The Signature Attribute is defined by the JDK 1.5 documentation as: +*

+*

+*   Signature_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length; (=2)
+*       u2 signature_index;
+*       }
+* 
+* +* @author rhl 2008.09.23 +*/ +public class SignatureAttribute extends Attribute implements Constants + { + + // ----- constructors --------------------------------------------------- + + /** + * Construct a signature attribute. + * + * @param context the JVM structure containing the attribute + */ + protected SignatureAttribute(VMStructure context) + { + super(context, ATTR_SIGNATURE); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + m_utfSignature = + (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + pool.registerConstant(m_utfSignature); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(2); + stream.writeShort(pool.findConstant(m_utfSignature)); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return super.getName() + '=' + m_utfSignature; + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the signature. + * + * @return the signature + */ + public String getSignature() + { + return m_utfSignature.getValue(); + } + + /** + * Set the signature. + * + * @param sSignature the signature + */ + public void setSignature(String sSignature) + { + m_utfSignature = new UtfConstant(sSignature); + m_fModified = true; + } + + /** + * Get the constant holding the signature. + * + * @return the signature constant + */ + public UtfConstant getSignatureConstant() + { + return m_utfSignature; + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- data members --------------------------------------------------- + + /** + * The signature. + */ + private UtfConstant m_utfSignature; + + /** + * Has the attribute been modified? + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SignatureConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SignatureConstant.java new file mode 100644 index 0000000000000..62463df170c52 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SignatureConstant.java @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents the name and type of a Java Virtual Machine method or field. +* This constant type references two UtfConstant instances. +* +* @version 0.50, 05/13/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class SignatureConstant extends Constant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected SignatureConstant() + { + super(CONSTANT_NAMEANDTYPE); + } + + /** + * Construct a constant whose values are java strings containing the name + * of a Java method or field and the method signature or field type. This + * constructor is a helper which creates UTF constants from the passed + * strings. + * + * @param sName the method/field name + * @param sType the method signature/field type + */ + public SignatureConstant(String sName, String sType) + { + this(new UtfConstant(sName), new UtfConstant(sType.replace('.', '/'))); + } + + /** + * Construct a Signature constant which references the passed UTF constant. + * + * @param constantName the referenced UTF constant which contains the + * name of the field/method + * @param constantType the referenced UTF constant which contains the + * field type/method signature + */ + public SignatureConstant(UtfConstant constantName, UtfConstant constantType) + { + this(); + + if (constantName == null || constantType == null) + { + throw new IllegalArgumentException(CLASS + ": Values cannot be null!"); + } + + m_utfName = constantName; + m_utfType = constantType; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iRefName = stream.readUnsignedShort(); + m_iRefType = stream.readUnsignedShort(); + } + + /** + * Resolve referenced constants. + * + * @param pool the constant pool containing any constant referenced by + * this constant (i.e. referenced by index) + */ + protected void postdisassemble(ConstantPool pool) + { + UtfConstant constName = m_utfName; + UtfConstant constType = m_utfType; + + if (constName == null || constType == null) + { + constName = m_utfName = (UtfConstant) pool.getConstant(m_iRefName); + constType = m_utfType = (UtfConstant) pool.getConstant(m_iRefType); + + constName.postdisassemble(pool); + constType.postdisassemble(pool); + } + } + + /** + * Register referenced constants. + * + * @param pool the constant pool to register referenced constants with + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utfName); + pool.registerConstant(m_utfType); + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeShort(pool.findConstant(m_utfName)); + stream.writeShort(pool.findConstant(m_utfType)); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + SignatureConstant that = (SignatureConstant) obj; + int nResult = this.m_utfName.compareTo(that.m_utfName); + if (nResult == 0) + { + nResult = this.m_utfType.compareTo(that.m_utfType); + } + return nResult; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Signature)->" + + (m_utfName == null ? "[" + m_iRefName + ']' : m_utfName.toString()) + + ", " + + (m_utfType == null ? "[" + m_iRefType + ']' : m_utfType.toString()); + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + String sName = m_utfName.format(); + String sType = m_utfType.format(); + if (sType.charAt(0) == '(') + { + return sName + sType; + } + else + { + return sName; + } + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + SignatureConstant that = (SignatureConstant) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_utfName.equals(that.m_utfName) + && this.m_utfType.equals(that.m_utfType); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the name portion of the constant as a string. + * + * @return the field/method name + */ + public String getName() + { + return m_utfName.getValue(); + } + + /** + * Get the type portion of the constant as a string. + * + * @return the field type/method signature + */ + public String getType() + { + return m_utfType.getValue(); + } + + /** + * Get the UTF constant which holds the field/method name. + * + * @return the UTF constant which contains the name + */ + public UtfConstant getNameConstant() + { + return m_utfName; + } + + /** + * Get the UTF constant which holds the field type/method signature. + * + * @return the UTF constant which contains the type + */ + public UtfConstant getTypeConstant() + { + return m_utfType; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "SignatureConstant"; + + /** + * The UTF constant referenced by this Signature constant containing the + * field/method name. + */ + private UtfConstant m_utfName; + + /** + * The UTF constant referenced by this Signature constant containing the + * field type/method signature. + */ + private UtfConstant m_utfType; + + /** + * Class pool index of the "name" UTF constant. + *

+ * If this has been disassembled (previous to the "postdisassemble" + * invocation), the reference to the UTF constant is still by index, as + * it was in the persistent .class structure. + */ + private int m_iRefName; + + /** + * Class pool index of the "type" UTF constant. + *

+ * If this has been disassembled (previous to the "postdisassemble" + * invocation), the reference to the UTF constant is still by index, as + * it was in the persistent .class structure. + */ + private int m_iRefType; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Snewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Snewarray.java new file mode 100644 index 0000000000000..435660aae7609 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Snewarray.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The SNEWARRAY pseudo-op instantiates an array of short. +*

+* JASM op         :  SNEWARRAY    (0xf6)
+* JVM byte code(s):  NEWARRAY     (0xbc)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Snewarray extends OpArray implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Snewarray() + { + super(SNEWARRAY); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Snewarray"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SourceFileAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SourceFileAttribute.java new file mode 100644 index 0000000000000..02b10d124c7ce --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SourceFileAttribute.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a Java Virtual Machine "SourceFile" Attribute which specifies +* the file name from which the Java .class was compiled. +* +* @version 0.50, 05/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class SourceFileAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a source file attribute. + * + * @param context the JVM structure containing the attribute + */ + protected SourceFileAttribute(VMStructure context) + { + super(context, ATTR_FILENAME); + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + stream.readInt(); + m_utf = (UtfConstant) pool.getConstant(stream.readUnsignedShort()); + } + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(super.getNameConstant()); + pool.registerConstant(m_utf); + } + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + stream.writeInt(2); + stream.writeShort(pool.findConstant(m_utf)); + } + + /** + * Determine if the attribute has been modified. + * + * @return true if the attribute has been modified + */ + public boolean isModified() + { + return m_fModified; + } + + /** + * Reset the modified state of the VM structure. + *

+ * This method must be overridden by sub-classes which do not maintain + * the attribute as binary. + */ + protected void resetModified() + { + m_fModified = false; + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the attribute. + * + * @return a string describing the attribute + */ + public String toString() + { + return super.getName() + '=' + m_utf.getValue(); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + SourceFileAttribute that = (SourceFileAttribute) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_utf.equals(that.m_utf); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine the source file name. + * + * @return the file name + */ + public String getSourceFile() + { + return m_utf.getValue(); + } + + /** + * Set the source file name. + * + * @param sName the file name + */ + public void setSourceFile(String sName) + { + m_utf = new UtfConstant(sName); + m_fModified = true; + } + + /** + * Get the constant holding the source file name. + * + * @return the source file constant + */ + public UtfConstant getSourceFileConstant() + { + return m_utf; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "SourceFileAttribute"; + + /** + * The name of the source file. + */ + private UtfConstant m_utf; + + /** + * Has the attribute been modified? + */ + private boolean m_fModified; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/StackMapTableAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/StackMapTableAttribute.java new file mode 100644 index 0000000000000..557deabfddd4b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/StackMapTableAttribute.java @@ -0,0 +1,1373 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ +package com.tangosol.dev.assembler; + +import com.oracle.coherence.common.collections.ChainedIterator; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** +* Represents a Java Virtual Machine "StackMapTable" attribute. +* +*

+* The StackMapTable Attribute was defined by JDK 1.6 under bytecode +* version 50.0. The structure is defined as: +*

+*

+* StackMapTable_attribute
+*     {
+*     u2              attribute_name_index;
+*     u4              attribute_length;
+*     u2              number_of_entries;
+*     stack_map_frame entries[number_of_entries];
+*     }
+* 
+* +* @author hr 2012.08.06 +*/ +public class StackMapTableAttribute + extends Attribute + implements Constants + { + + // ----- constructors --------------------------------------------------- + + /** + * Construct a StackMapTableAttribute under the provided context. + * + * @param context a related VMStructure object + */ + protected StackMapTableAttribute(VMStructure context) + { + super(context, ATTR_STACKMAPTABLE); + } + + // ----- Attribute methods ---------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + stream.readInt(); + int cFrames = stream.readUnsignedShort(); + + List listFrames = m_listFrames = new LinkedList(); + + for (int i = 0; i < cFrames; ++i) + { + listFrames.add(loadFrame(this, stream, pool)); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + stream.writeShort(pool.findConstant(super.getNameConstant())); + + List listFrames = m_listFrames; + int cLength = 2; + + for (StackMapFrame frame : listFrames) + { + cLength += frame.size(); + } + stream.writeInt(cLength); + + stream.writeShort(listFrames.size()); + for (StackMapFrame frame : listFrames) + { + frame.assemble(stream, pool); + } + } + + // ----- accessors ------------------------------------------------------ + + /** + * Return a List of {@link StackMapFrame}'s allowing the list to be + * mutated. + * + * @return a List of StackMapFrame's + */ + public List getFrames() + { + return m_listFrames; + } + + // ----- constants ------------------------------------------------------ + + /** + * Create and disassembles a StackMapFrame or return null. + * + * @param context the StackMapTable attribute this frame belongs to + * @param stream DataInput stream to disassemble + * @param pool the ConstantPool allowing access to constants + * + * @return an appropriate StackMapFrame or null + * + * @throws IOException iff an error occurred during disassembly + */ + protected static StackMapFrame loadFrame(StackMapTableAttribute context, DataInput stream, ConstantPool pool) + throws IOException + { + StackMapFrame frame = null; + int nTag = stream.readUnsignedByte(); + + if ((nTag & 0xC0) == 0) // same_frame + { + frame = context.new SameFrame(nTag); + } + else if ((nTag & 0x80) == 0) // same_locals_1_stack_item_frame + { + frame = context.new SameLocalsOneStackItemFrame(nTag); + } + else + { + switch (nTag) + { + case 247: // same_locals_1_stack_item_frame_extended + frame = context.new SameLocalsOneStackItemFrameExtended(); + break; + case 248: // chop_frame + case 249: + case 250: + frame = context.new ChopFrame(nTag); + break; + case 251: //same_frame_extended + frame = context.new SameFrameExtended(); + break; + case 252: // append_frame + case 253: + case 254: + frame = context.new AppendFrame(nTag); + break; + case 255: // full_frame + frame = context.new FullFrame(); + break; + } + } + + if (frame != null) + { + frame.disassemble(stream, pool); + } + return frame; + } + + /** + * Creates and disassembles a "verification_type_info" structure into + * an instance of {@link StackMapFrame.VariableInfo} or null. + * + * @param context the StackMapTable attribute this frame belongs to + * @param stream DataInput stream to disassemble + * @param pool the ConstantPool allowing access to constants + * + * @return a VariableInfo object or subclass or null + * + * @throws IOException iff an error occurred during disassembly + */ + protected static StackMapFrame.VariableInfo loadVariableInfo(StackMapFrame context, DataInput stream, ConstantPool pool) + throws IOException + { + int nTag = stream.readByte(); + StackMapFrame.VariableInfo var = null; + switch (nTag) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + var = context.new VariableInfo(nTag); + break; + case 7: + var = context.new ObjectVariableInfo(); + break; + case 8: + var = context.new UninitializedVariableInfo(); + break; + } + + if (var != null) + { + var.disassemble(stream, pool); + } + return var; + } + + // ----- inner class: StackMapFrame ------------------------------------- + + /** + * Represents a Java Virtual Machine "stack_map_frame" structure within + * the "StackMapTable" attribute. + * + *

+ * The stack_map_frame structure was defined by JDK 1.6 under bytecode + * version 50.0. The structure is defined as: + *

+ *

+    * union stack_map_frame
+    *     {
+    *     same_frame;
+    *     same_locals_1_stack_item_frame;
+    *     same_locals_1_stack_item_frame_extended;
+    *     chop_frame;
+    *     same_frame_extended;
+    *     append_frame;
+    *     full_frame;
+    *     }
+    * 
+ * + * @see SameFrame + * @see SameLocalsOneStackItemFrame + * @see SameLocalsOneStackItemFrameExtended + * @see ChopFrame + * @see SameFrameExtended + * @see AppendFrame + * @see FullFrame + */ + public abstract class StackMapFrame + extends VMStructure + implements Constants + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a StackMapFrame with the provided {@code nTag}. + * + * @param nTag byte tag representing the type of the stack frame + */ + public StackMapFrame(int nTag) + { + m_nTag = nTag; + } + + // ----- VMStructure methods ---------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + } + + /** + * {@inheritDoc} + */ + @Override + protected void preassemble(ConstantPool pool) + { + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + stream.writeByte(m_nTag); + } + + // ----- abstract methods ------------------------------------------- + + /** + * The Code attribute offset this frame refers to. + * + * @return the Code attribute offset this frame refers to + */ + public abstract int getOffset(); + + /** + * Returns the size of this frame in bytes. + * + * @return the size of this frame in bytes + */ + protected abstract int size(); + + // ----- inner class: VariableInfo ---------------------------------- + + /** + * A VariableInfo represents an instance of a verification_type_info + * structure as specified in 50.0 bytecode specification. The + * verification_type_info has the following structure: + *
+        * union verification_type_info
+        *     {
+        *     Top_variable_info;
+        *     Integer_variable_info;
+        *     Float_variable_info;
+        *     Long_variable_info;
+        *     Double_variable_info;
+        *     Null_variable_info;
+        *     UninitializedThis_variable_info;
+        *     Object_variable_info;
+        *     Uninitialized_variable_info;
+        *     }
+        * 
+ * This class represents all of the above structures except the final + * two that are represented by {@link ObjectVariableInfo} and + * {@link UninitializedVariableInfo} respectively. The underlying + * types can be distinguished via {@link #getType()}. + * + * @see VariableInfoType + */ + public class VariableInfo + extends VMStructure + implements Constants + { + + // ----- constructors ------------------------------------------- + + /** + * Create a VariableInfo object with the provided byte tag. + * + * @param nTag a byte tag representing the type of VariableInfo + */ + public VariableInfo(int nTag) + { + m_type = VariableInfoType.values()[nTag]; + } + + // ----- accessors ---------------------------------------------- + + /** + * Returns a {@link VariableInfoType} enum representation of the + * type of this VariableInfo object. + * + * @return enum representation of the type of this VariableInfo + * object + */ + public VariableInfoType getType() + { + return m_type; + } + + // ----- VMStructure methods ------------------------------------ + + /** + * {@inheritDoc} + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + } + + /** + * {@inheritDoc} + */ + protected void preassemble(ConstantPool pool) + { + } + + /** + * {@inheritDoc} + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeByte(m_type.ordinal()); + } + + /** + * Returns the size of this VariableInfo instance in bytes. + * + * @return the size of this VariableInfo instance in bytes + */ + protected int size() + { + return 1; + } + + // ----- data members ------------------------------------------- + + /** + * Enum representation of the type of this VariableInfo. + */ + private VariableInfoType m_type; + } + + // ----- inner class: ObjectVariableInfo ---------------------------- + + /** + * A ObjectVariableInfo represents an instance of a VariableInfo + * such that the type is {@link VariableInfoType#Object}. The + * structure varies from all other types and is defined below: + *
+        * Object_variable_info
+        *     {
+        *     u1 tag = ITEM_Object;
+        *     u2 cpool_index;
+        *     }
+        * 
+ * Opposed to providing the constant pool index this class provides + * the constant value. + */ + public class ObjectVariableInfo + extends VariableInfo + { + + // ----- constructors ------------------------------------------- + + /** + * Creates an instance of ObjectVariableInfo. + */ + public ObjectVariableInfo() + { + super(VariableInfoType.Object.ordinal()); + } + + // ----- accessors ---------------------------------------------- + + /** + * Returns the constant pool value this object refers to. + * + * @return the constant pool value this object refers to + */ + public Constant getName() + { + return m_objectName; + } + + // ----- VariableInfo methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + super.disassemble(stream, pool); + + m_objectName = pool.getConstant(stream.readUnsignedShort()); + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(pool.findConstant(m_objectName)); + } + + /** + * {@inheritDoc} + */ + protected int size() + { + return 3; + } + + // ----- data members ------------------------------------------- + + /** + * The object name this variable info refers to. + */ + private Constant m_objectName; + } + + // ----- inner class: UninitializedVariableInfo --------------------- + + /** + * A UninitializedVariableInfo represents an instance of a VariableInfo + * such that the type is {@link VariableInfoType#Uninitialized}. The + * structure varies from all other types and is defined below: + *
+        * Uninitialized_variable_info
+        *     {
+        *     u1 tag = ITEM_Uninitialized;
+        *     u2 offset;
+        *     }
+        * 
+ */ + public class UninitializedVariableInfo + extends VariableInfo + { + + // ----- constructors ------------------------------------------- + + /** + * Creates an UninitializedVariableInfo object. + */ + public UninitializedVariableInfo() + { + super(VariableInfoType.Uninitialized.ordinal()); + } + + // ----- accessors ---------------------------------------------- + + /** + * Returns the offset of the location in the Code attribute that + * this variable info refers to, i.e. the associated new + * instruction. + * + * @return the offset of the location in the Code attribute that + * this variable info refers to + */ + public int getOffset() + { + return m_nOffset; + } + + // ----- VariableInfo methods ----------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + super.disassemble(stream, pool); + + m_nOffset = stream.readUnsignedShort(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_nOffset); + } + + /** + * {@inheritDoc} + */ + protected int size() + { + return 3; + } + + // ----- data members ------------------------------------------- + + /** + * The offset of the location in the Code attribute that + * this variable info refers to, i.e. the associated new + * instruction. + */ + private int m_nOffset; + } + + // ----- data members ----------------------------------------------- + + /** + * The byte tag of this stack map frame. + */ + int m_nTag; + + /** + * The length of this frame in bytes. + */ + int m_nLength; + } + + // ----- inner class: SameFrame ----------------------------------------- + + /** + * A specialization of the StackMapFrame with the following definition: + *
+    * SameFrame means the frame has exactly the same locals as the previous
+    * stack map frame and that the number of stack items is zero.
+    * 
+ */ + public class SameFrame + extends StackMapFrame + { + + // ----- constructors ----------------------------------------------- + + /** + * Constructs a SameFrame instance with the provided byte tag + * (0 - 63). + * + * @param nTag the byte tag within the region for a same_frame + */ + public SameFrame(int nTag) + { + super(nTag); + } + + /** + * {@inheritDoc} + */ + public int getOffset() + { + return m_nTag; + } + + /** + * {@inheritDoc} + */ + protected int size() + { + return 1; + } + } + + // ----- inner class: SameFrame ----------------------------------------- + + /** + * A specialization of the StackMapFrame with the following definition: + *
+    * SameFrameExtended means the frame has exactly the same locals as the
+    * previous stack map frame and that the number of stack items is zero.
+    * 
+ */ + public class SameFrameExtended + extends StackMapFrame + { + + // ----- constructors ----------------------------------------------- + + /** + * Constructs a SameFrameExtended instance with the provided byte tag + * (251). + */ + public SameFrameExtended() + { + super(251); + } + + // ----- accessors -------------------------------------------------- + + /** + * {@inheritDoc} + */ + public int getOffset() + { + return m_nOffset; + } + + /** + * Set the Code attribute offset this frame refers to. + * + * @param nOffset + */ + public void setOffset(int nOffset) + { + m_nOffset = nOffset; + } + + /** + * {@inheritDoc} + */ + protected int size() + { + return 3; + } + + // ----- StackMapFrame methods -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + super.disassemble(stream, pool); + + m_nOffset = stream.readUnsignedShort(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_nOffset); + } + + // ----- data members ----------------------------------------------- + + /** + * The Code attribute offset this frame refers to. + */ + private int m_nOffset; + } + + // ----- inner class: SameLocalsOneStackItemFrame ----------------------- + + /** + * A specialization of the StackMapFrame with the following definition: + *
+    * SameLocalsOneStackItemFrame means the frame has exactly the same locals
+    * as the previous stack map frame and that the number of stack items is 1.
+    * 
+ * The offset can be discovered via {@link #getOffset()}. + */ + public class SameLocalsOneStackItemFrame + extends StackMapFrame + { + + // ----- constructors ----------------------------------------------- + + /** + * Creates a SameLocalsOneStackItemFrame instance. + * + * @param nTag the byte tag within the region for a + * same_locals_1_stack_item_frame + */ + public SameLocalsOneStackItemFrame(int nTag) + { + super(nTag); + } + + // ----- accessors -------------------------------------------------- + + /** + * {@inheritDoc} + */ + public int getOffset() + { + return m_nTag - 64; + } + + /** + * Returns the stack item ({@link VariableInfo}) for this frame. + * + * @return the stack item for this frame + */ + public VariableInfo getStack() + { + return m_stack; + } + + /** + * Sets the stack item ({@link VariableInfo}) for this frame. + * + * @param varInfo the stack item for this frame + */ + public void setStack(VariableInfo varInfo) + { + m_stack = varInfo; + } + + /** + * {@inheritDoc} + */ + protected int size() + { + return 1 + m_stack.size(); + } + + // ----- StackMapFrame methods -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + super.disassemble(stream, pool); + + // verification_type_info tag + m_stack = loadVariableInfo(this, stream, pool); + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + super.assemble(stream, pool); + + if (m_stack != null) + { + m_stack.assemble(stream, pool); + } + } + + // ----- data members ----------------------------------------------- + + /** + * The stack item ({@link VariableInfo}) for this frame. + */ + VariableInfo m_stack; + } + + // ----- inner class: SameLocalsOneStackItemFrameExtended --------------- + + /** + * A specialization of the StackMapFrame with the following definition: + *
+    * SameLocalsOneStackItemFrameExtended means the frame has exactly the
+    * same locals as the previous stack map frame and that the number of
+    * stack items is 1.
+    * 
+ * The explicitly defined offset can be discovered via {@link #getOffset()}. + */ + public class SameLocalsOneStackItemFrameExtended + extends SameLocalsOneStackItemFrame + { + + // ----- constructors ----------------------------------------------- + + /** + * Creates a SameLocalsOneStackItemFrameExtended instance. + */ + public SameLocalsOneStackItemFrameExtended() + { + super(247); + } + + // ----- accessors -------------------------------------------------- + + /** + * {@inheritDoc} + */ + public int getOffset() + { + return m_nOffset; + } + + /** + * Set the Code attribute offset this frame refers to. + * + * @param nOffset + */ + public void setOffset(int nOffset) + { + m_nOffset = nOffset; + } + + /** + * {@inheritDoc} + */ + protected int size() + { + return 3 + m_stack.size(); + } + + // ----- StackMapFrame methods -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + m_nOffset = stream.readUnsignedShort(); + // verification_type_info tag + super.disassemble(stream, pool); + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + stream.writeByte(m_nTag); + stream.writeShort(m_nOffset); + if (m_stack != null) + { + m_stack.assemble(stream, pool); + } + } + + // ----- data members ----------------------------------------------- + + /** + * The Code attribute offset this frame refers to. + */ + private int m_nOffset; + } + + // ----- inner class: ChopFrame ----------------------------------------- + + /** + * A specialization of the StackMapFrame with the following definition: + *
+    * ChopFrame means that the operand stack is empty and the current locals
+    * are the same as the locals in the previous frame, except that the k
+    * last locals are absent.
+    * 
+ * The value of {@code k}, mentioned above, can be determined by + * {@link #getAbsentLocals()}. The explicitly defined offset can be + * discovered via {@link #getOffset()}. + */ + public class ChopFrame + extends StackMapFrame + { + + // ----- constructors ----------------------------------------------- + + /** + * Constructs a ChopFrame instance. + * + * @param nTag the byte tag within the region for a chop_frame + */ + public ChopFrame(int nTag) + { + super(nTag); + } + + // ----- accessors -------------------------------------------------- + + /** + * The number of absent locals in the previous frame. + * + * @return the number of absent locals in the previous frame + */ + public int getAbsentLocals() + { + return 251 - m_nTag; + } + + /** + * {@inheritDoc} + */ + public int getOffset() + { + return m_nOffset; + } + + /** + * Set the Code attribute offset this frame refers to. + * + * @param nOffset + */ + public void setOffset(int nOffset) + { + m_nOffset = nOffset; + } + + /** + * {@inheritDoc} + */ + protected int size() + { + return 3; + } + + // ----- StackMapFrame methods -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + super.disassemble(stream, pool); + + m_nOffset = stream.readUnsignedShort(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_nOffset); + } + + // ----- data members ----------------------------------------------- + + /** + * The Code attribute offset this frame refers to. + */ + private int m_nOffset; + } + + // ----- inner class: AppendFrame --------------------------------------- + + /** + * A specialization of the StackMapFrame with the following definition: + *
+    * AppendFrame means that the operand stack is empty and the current
+    * locals are the same as the locals in the previous frame, except that k
+    * additional locals are defined.
+    * 
+ * The value of {@code k}, mentioned above, can be determined by + * {@link #getAppendedLocals()}. The explicitly defined offset can be + * discovered via {@link #getOffset()}. + */ + public class AppendFrame + extends StackMapFrame + { + + // ----- constructors ----------------------------------------------- + + /** + * Constructs a AppendFrame instance. + * + * @param nTag the byte tag within the region for a append_frame + */ + public AppendFrame(int nTag) + { + super(nTag); + } + + // ----- accessors -------------------------------------------------- + + /** + * Returns the number of additional locals. + * + * @return the number of additional locals + */ + public int getAppendedLocals() + { + return m_nTag - 251; + } + + /** + * {@inheritDoc} + */ + public int getOffset() + { + return m_nOffset; + } + + /** + * Set the Code attribute offset this frame refers to. + * + * @param nOffset + */ + public void setOffset(int nOffset) + { + m_nOffset = nOffset; + } + + /** + * {@inheritDoc} + */ + protected int size() + { + int cSize = 3; + for (VariableInfo var : m_listLocals) + { + cSize += var.size(); + } + return cSize; + } + + /** + * Returns an Enumeration of {@link VariableInfo} objects. + * + * @return an Enumeration of VariableInfo objects + */ + public Enumeration getLocals() + { + return Collections.enumeration(m_listLocals); + } + + /** + * Adds a VariableInfo to the append_frame ensuring it is permitted + * under the constraints of the byte tag. + * + * @param i index position to add the VariableInfo + * @param var the VariableInfo to add + * + * @return whether the VariableInfo was added + */ + public boolean add(int i, VariableInfo var) + { + List listVars = m_listLocals; + if (getAppendedLocals() >= listVars.size()) + { + return false; + } + listVars.add(i, var); + return true; + } + + /** + * Adds a VariableInfo to the append_frame ensuring it is permitted + * under the constraints of the byte tag. + * + * @param var the VariableInfo to add + * + * @return whether the VariableInfo was added + */ + public boolean add(VariableInfo var) + { + List listVars = m_listLocals; + if (getAppendedLocals() >= listVars.size()) + { + return false; + } + return listVars.add(var); + } + + /** + * Removes a VariableInfo from the append_frame. + * + * @param i index position to remove the VariableInfo + * + * @return the removed VariableInfo object + */ + public VariableInfo remove(int i) + { + VariableInfo var = m_listLocals.remove(i); + return var; + } + + /** + * Removes the given VariableInfo from the append_frame. + * + * @return whenther the VariableInfo was removed + */ + public boolean remove(VariableInfo var) + { + return m_listLocals.remove(var); + } + + // ----- StackMapFrame methods -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + super.disassemble(stream, pool); + + m_nOffset = stream.readUnsignedShort(); + + int cLocals = getAppendedLocals(); + List listLocals = m_listLocals = new LinkedList(); + for (int i = 0; i < cLocals; ++i) + { + listLocals.add(loadVariableInfo(this, stream, pool)); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_nOffset); + for (VariableInfo var : m_listLocals) + { + var.assemble(stream, pool); + } + } + + // ----- data members ----------------------------------------------- + + /** + * The Code attribute offset this frame refers to. + */ + private int m_nOffset; + + /** + * The local ({@link VariableInfo}) variables to append. + */ + private List m_listLocals; + } + + // ----- inner class: AppendFrame --------------------------------------- + + /** + * A specialization of the StackMapFrame with the following definition: + *
+    * FullFrame encompasses all the required values for a frame; an offset,
+    * an array of local and an array or stack variable info objects.
+    * 
+ */ + public class FullFrame + extends StackMapFrame + { + + // ----- constructors ----------------------------------------------- + + /** + * Construct a FullFrame instance. + */ + public FullFrame() + { + super(255); + } + + // ----- accessors -------------------------------------------------- + + /** + * {@inheritDoc} + */ + public int getOffset() + { + return m_nOffset; + } + + /** + * Returns a List of {@link VariableInfo} objects + * representing local variables. + * + * @return a List of {@link VariableInfo} objects representing + * local variables + */ + public List getLocals() + { + return m_listLocals; + } + + /** + * Returns a List of {@link VariableInfo} objects + * representing stack variables. + * + * @return a List of {@link VariableInfo} objects representing + * stack variables + */ + public List getStack() + { + return m_listStack; + } + + /** + * Set the Code attribute offset this frame refers to. + * + * @param nOffset + */ + public void setOffset(int nOffset) + { + m_nOffset = nOffset; + } + + /** + * {@inheritDoc} + */ + protected int size() + { + int cSize = 7; + for (Iterator iter = new ChainedIterator(m_listLocals.iterator(), m_listStack.iterator()); + iter.hasNext(); ) + { + cSize += iter.next().size(); + } + return cSize; + } + + // ----- StackMapTable methods -------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + super.disassemble(stream, pool); + + m_nOffset = stream.readUnsignedShort(); + + int cItems = stream.readUnsignedShort(); + List listLocals = m_listLocals = new LinkedList(); + for (int i = 0; i < cItems; ++i) + { + listLocals.add(loadVariableInfo(this, stream, pool)); + } + cItems = stream.readUnsignedShort(); + List listStack = m_listStack = new LinkedList(); + for (int i = 0; i < cItems; ++i) + { + listStack.add(loadVariableInfo(this, stream, pool)); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_nOffset); + List listLocals = m_listLocals; + stream.writeShort(listLocals.size()); + for (VariableInfo var : listLocals) + { + var.assemble(stream, pool); + } + + List listStack = m_listStack; + stream.writeShort(listStack.size()); + for (VariableInfo var : listStack) + { + var.assemble(stream, pool); + } + } + + // ----- data members ----------------------------------------------- + + /** + * The Code attribute offset this frame refers to. + */ + private int m_nOffset; + + /** + * The local ({@link VariableInfo}) variables in this frame. + */ + private List m_listLocals = new LinkedList(); + + /** + * The stack items ({@link VariableInfo}). + */ + private List m_listStack = new LinkedList(); + } + + // ----- inner enum: VariableInfoType ----------------------------------- + + /** + * An enum representing the various types of + * {@link StackMapFrame.VariableInfo} objects. + */ + public enum VariableInfoType + { + Top, + Integer, + Float, + Long, + Double, + Null, + UninitializedThis, + Object, + Uninitialized + } + + // ----- data members --------------------------------------------------- + + /** + * The frames encapsulated by the StackMapTable. + */ + private List m_listFrames; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/StringConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/StringConstant.java new file mode 100644 index 0000000000000..11ebf9eb4f0b7 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/StringConstant.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a Java Virtual Machine byte code character string constant. +* This constant type is referenced only by the LDC and LDC_W opcodes. This +* constant type does not store the string constant itself; instead it +* references a UtfConstant. +* +* @version 0.50, 05/13/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class StringConstant + extends Constant + implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected StringConstant() + { + super(CONSTANT_STRING); + } + + /** + * Construct a constant whose value is a java string. This constructor + * is a helper which creates a UTF constant from the passed string. + * + * @param sText the java string + */ + public StringConstant(String sText) + { + this(new UtfConstant(sText)); + } + + /** + * Construct a String constant which references the passed UTF constant. + * + * @param constant the referenced UTF constant which contains the value + * of the String constant + */ + public StringConstant(UtfConstant constant) + { + this(); + + if (constant == null) + { + throw new IllegalArgumentException(CLASS + ": Value cannot be null!"); + } + + m_utf = constant; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iRef = stream.readUnsignedShort(); + } + + /** + * Resolve referenced constants. + * + * @param pool the constant pool containing any constant referenced by + * this constant (i.e. referenced by index) + */ + protected void postdisassemble(ConstantPool pool) + { + m_utf = (UtfConstant) pool.getConstant(m_iRef); + } + + /** + * Register referenced constants. + * + * @param pool the constant pool to register referenced constants with + */ + protected void preassemble(ConstantPool pool) + { + pool.registerConstant(m_utf); + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeShort(pool.findConstant(m_utf)); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + StringConstant that = (StringConstant) obj; + return this.m_utf.compareTo(that.m_utf); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(String)->" + (m_utf == null ? "[" + m_iRef + ']' : m_utf.toString()); + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + return toQuotedStringEscape(m_utf.format()); + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + StringConstant that = (StringConstant) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_utf.equals(that.m_utf); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the string value of the constant. + * + * @return the constant's string value + */ + public String getValue() + { + return m_utf.getValue(); + } + + /** + * Get the UTF constant which holds the string value. + * + * @return the UTF constant referenced from this constant + */ + public UtfConstant getValueConstant() + { + return m_utf; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "StringConstant"; + + /** + * The UTF constant referenced by this String constant. + */ + private UtfConstant m_utf; + + /** + * If this has been disassembled (previous to the "postdisassemble" + * invocation), the reference to the UTF constant is still by index, as + * it was in the persistent .class structure. + */ + private int m_iRef; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Swap.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Swap.java new file mode 100644 index 0000000000000..f84d485cb2b2e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Swap.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The SWAP simple op swaps the top two words in the stack. +*

+* JASM op         :  SWAP  (0x5f)
+* JVM byte code(s):  SWAP  (0x5f)
+* Details         :
+* 
+* +* @version 0.50, 06/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Swap extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Swap() + { + super(SWAP); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Swap"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Switch.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Switch.java new file mode 100644 index 0000000000000..b7d7c7ac69d83 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Switch.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The SWITCH op implements the general multi-case switch. It assembles to +* either a TABLESWITCH or a LOOKUPSWITCH depending on which is more +* "efficient". Since a TABLESWITCH is always more efficient for performance, +* it is always selected unless the amount of additional code generated for +* the TABLESWITCH compared to the LOOKUPSWITCH is considered to be too +* expensive in terms of size. +*

+* JASM op         :  SWITCH        (0xfc)
+* JVM byte code(s):  TABLESWITCH   (0xaa)
+*                    LOOKUPSWITCH  (0xab)
+* Details         :
+* 
+* +* @version 0.50, 06/16/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Switch extends OpSwitch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the default label to branch to if no cases match + */ + public Switch(Label label) + { + super(SWITCH, label); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Determine which op should be used to implement the switch. + * + * @return one of TABLESWITCH, LOOKUPSWITCH + */ + protected int getSwitch() + { + int iOp = super.getSwitch(); + if (iOp == SWITCH) + { + // based on the case ops, determine which is better: + // a TABLESWITCH or a LOOKUPSWITCH + Case[] acase = getCases(); + int cCases = acase.length; + + if (cCases <= 1) + { + // with zero or one case, a LOOKUPSWITCH is as or more efficient + // space-wise and theoretically just as efficient time-wise + iOp = LOOKUPSWITCH; + } + else + { + int iLow = acase[0].getCase(); + int iHigh = acase[cCases - 1].getCase(); + + double dblRange = (double) iHigh - (double) iLow + 1.0; + double dblSpread = dblRange / cCases; + + // 2.0 is break-even for space + // 4.0 is leaning toward speed at the expense of space + iOp = (dblSpread > 4.0 ? LOOKUPSWITCH : TABLESWITCH); + } + } + + return iOp; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Switch"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SyntheticAttribute.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SyntheticAttribute.java new file mode 100644 index 0000000000000..19e2f42d55769 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/SyntheticAttribute.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a Java Virtual Machine "synthetic" attribute which specifies +* that a member (i.e. field or method) is "tool generated" (i.e. will not +* be found by that exact name in the source). This attribute can also +* apply to a class. +*

+* The Synthetic Attribute is defined by the JDK 1.1 documentation as: +*

+*

+*   Synthetic_attribute
+*       {
+*       u2 attribute_name_index;
+*       u4 attribute_length; (=0)
+*       }
+* 
+* +* @version 0.50, 05/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class SyntheticAttribute extends Attribute implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct a synthetic attribute. + * + * @param context the JVM structure containing the attribute + */ + protected SyntheticAttribute(VMStructure context) + { + super(context, ATTR_SYNTHETIC); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "SyntheticAttribute"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Tableswitch.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Tableswitch.java new file mode 100644 index 0000000000000..3f769cc816a61 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Tableswitch.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The TABLESWITCH op is a bounded jump table with a default jump. +*

+* JASM op         :  TABLESWITCH   (0xaa)
+* JVM byte code(s):  TABLESWITCH   (0xaa)
+* Details         :
+* 
+* +* @version 0.50, 06/17/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Tableswitch extends OpSwitch implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + * + * @param label the default label to branch to if no cases match + */ + public Tableswitch(Label label) + { + super(TABLESWITCH, label); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Tableswitch"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Try.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Try.java new file mode 100644 index 0000000000000..ad393345ab63c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Try.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + +import java.util.Vector; +import java.util.Enumeration; + + +/** +* The TRY pseudo op marks the start of a guarded section. +*

+* JASM op         :  TRY (0xfe)
+* JVM byte code(s):  n/a
+* Details         :
+* 
+* +* @version 0.50, 06/18/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Try extends Op implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Try() + { + super(TRY); + } + + + // ----- accessors ------------------------------------------------------ + + /** + * (Internal) Adds a Catch to the Try. + * + * @param op the Catch op + */ + protected void addCatch(Catch op) + { + Catch[] acatchOld = m_acatch; + int ccatchOld = acatchOld.length; + int ccatchNew = ccatchOld + 1; + Catch[] acatchNew = new Catch[ccatchNew]; + System.arraycopy(acatchOld, 0, acatchNew, 0, ccatchOld); + acatchNew[ccatchOld] = op; + m_acatch = acatchNew; + } + + /** + * Get the Catch ops associated with the Try. + * + * @return an array of Catch ops + */ + public Catch[] getCatches() + { + return m_acatch; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Try"; + + /** + * An empty array of catches. + */ + private static final Catch[] EMPTY = new Catch[0]; + + /** + * An array of Catch ops for the Try op. + */ + private Catch[] m_acatch = EMPTY; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/TypeAnnotation.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/TypeAnnotation.java new file mode 100644 index 0000000000000..2c70486b0ee0d --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/TypeAnnotation.java @@ -0,0 +1,1739 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.Arrays; + +/** + * TypeAnnotation represents a Java Virtual Machine {@code type_annotation} + * structure as used by 'RuntimeVisibleTypeAnnotations_attribute' and + * 'RuntimeInvisibleTypeAnnotations_attribute'. + *

+ * The TypeAnnotation structure is defined by the JVM 8 Spec as: + *

+ *     TypeAnnotation
+ *         {
+ *         u1 target_type;
+ *         union
+ *             {
+ *             type_parameter_target;
+ *             supertype_target;
+ *             type_parameter_bound_target;
+ *             empty_target;
+ *             method_formal_parameter_target;
+ *             throws_target;
+ *             localvar_target;
+ *             catch_target;
+ *             offset_target;
+ *             type_argument_target;
+ *             } target_info;
+ *         type_path target_path;
+ *         u2        type_index;
+ *         u2        num_element_value_pairs;
+ *             {
+ *             u2            element_name_index;
+ *             element_value value;
+ *             } element_value_pairs[num_element_value_pairs];
+ *         }
+ * 
+ * + * @author hr 2014.05.22 + */ +public class TypeAnnotation + extends Annotation + { + // ----- VMStructure operations ----------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + // typed_annotation's additional structure is before the standard + // annotation, hence the call to super last + + m_target = AbstractTarget.loadTarget(stream, pool); + + (m_typePath = new TypePath()).disassemble(stream, pool); + + super.disassemble(stream, pool); + } + + @Override + protected void preassemble(ConstantPool pool) + { + super.preassemble(pool); + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + // typed_annotation's additional structure is before the standard + // annotation, hence the call to super last + + m_target.assemble(stream, pool); // target_type & target_info + m_typePath.assemble(stream, pool); // target_path + + super.assemble(stream, pool); + } + + // ----- Annotation operations ------------------------------------------ + + @Override + public int getSize() + { + return m_target.getSize() + m_typePath.getSize() + super.getSize(); + } + + // ----- inner class: AbstractTarget ------------------------------------ + + /** + * A base type for all targets for TypeAnnotations. + */ + public abstract static class AbstractTarget + extends VMStructure + implements Constants + { + // ----- constructors ----------------------------------------------- + + /** + * Construct the base type for all targets used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type + */ + protected AbstractTarget(byte bType) + { + m_bType = bType; + } + + // ----- abstract methods ------------------------------------------- + + /** + * Return the number of bytes consumed by this structure when serialized. + * + * @return the number of bytes consumed by this structure when serialized + */ + protected int getSize() + { + return 1; + } + + // ----- constant helpers ------------------------------------------- + + /** + * Return a newly constructed {@link AbstractTarget} based on the next + * byte in the provided steam. The byte should correspond to one of + * the bytes in the {@code TARGET_*} constants of this class. + * + * @param stream the stream to read from + * @param pool the constant pool to look up references + * + * @return an appropriate AbstractTarget + * + * @throws IOException if reading from the stream errors + */ + public static AbstractTarget loadTarget(DataInput stream, ConstantPool pool) + throws IOException + { + byte bType = stream.readByte(); + AbstractTarget target; + + switch (bType) + { + case TARGET_CLASS: + case TARGET_METHOD: + // type_parameter_target + target = new TypeParameterTarget(bType); + break; + case TARGET_EXT_IMPL: + // supertype_target + target = new SuperTypeTarget(bType); + break; + case TARGET_PARAM_BOUND_CLASS: + case TARGET_PARAM_BOUND_METHOD: + // type_parameter_bound_target + target = new TypeParameterBoundTarget(bType); + break; + case TARGET_FIELD: + case TARGET_METHOD_RETURN: + case TARGET_METHOD_RECEIVER: + // empty_target + target = new EmptyTarget(bType); + break; + case TARGET_METHOD_PARAM: + // formal_parameter_target + target = new FormalParameterTarget(bType); + break; + case TARGET_METHOD_THROWS: + // throws_target + target = new ThrowsTarget(bType); + break; + case TARGET_CODE_LOCAL_VAR: + case TARGET_CODE_RESOURCE_VAR: + // localvar_target + target = new LocalVariableTarget(bType); + break; + case TARGET_CODE_EXCEPTION_PARAM: + // catch_target + target = new CatchTarget(bType); + break; + case TARGET_CODE_INSTANCEOF: + case TARGET_CODE_NEW: + case TARGET_CODE_METHOD_REF_NEW: + case TARGET_CODE_METHOD_REF: + // offset_target + target = new OffsetTarget(bType); + break; + case TARGET_CODE_CAST: + case TARGET_CODE_CONSTRUCTOR: + case TARGET_CODE_METHOD: + case TARGET_CODE_METHOD_REF_NEW_ARG: + case TARGET_CODE_METHOD_REF_ARG: + // type_argument_target + target = new TypeArgumentTarget(bType); + break; + default: + throw new IllegalStateException("Unexpected target type: " + + String.format("%X", bType)); + } + + target.disassemble(stream, pool); + + return target; + } + + // ----- VMStructure operations ------------------------------------- + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + stream.writeByte(m_bType); + } + + // ----- non-code target type constants ----------------------------- + + /** + * Kind of Target: Type Parameter declaration of generic class or interface. + */ + public static final byte TARGET_CLASS = 0x00; + + /** + * Kind of Target: Type Parameter declaration of generic method or constructor. + */ + public static final byte TARGET_METHOD = 0x01; + + /** + * Kind of Target: Type in extends clause of class or interface declaration + * or in implements clause of interface declaration. + */ + public static final byte TARGET_EXT_IMPL = 0x10; + + /** + * Kind of Target: Type in bound of type parameter declaration of + * generic class or interface. + */ + public static final byte TARGET_PARAM_BOUND_CLASS = 0x11; + + /** + * Kind of Target: Type in bound of type parameter declaration of + * generic method or constructor. + */ + public static final byte TARGET_PARAM_BOUND_METHOD = 0x12; + + /** + * Kind of Target: Type in field declaration. + */ + public static final byte TARGET_FIELD = 0x13; + + /** + * Kind of Target: Return type of methods or newly constructed object. + */ + public static final byte TARGET_METHOD_RETURN = 0x14; + + /** + * Kind of Target: Receiver type of method or constructor. + */ + public static final byte TARGET_METHOD_RECEIVER = 0x15; + + /** + * Kind of Target: Type in the formal parameter declaration of + * method, constructor, or lambda expression. + */ + public static final byte TARGET_METHOD_PARAM = 0x16; + + /** + * Kind of Target: Type in throws clause of method or constructor. + */ + public static final byte TARGET_METHOD_THROWS = 0x17; + + // ----- code target type constants --------------------------------- + + /** + * Kind of Target: Type in local variable declaration. + */ + public static final byte TARGET_CODE_LOCAL_VAR = 0x40; + + /** + * Kind of Target: Type in resource variable declaration. + */ + public static final byte TARGET_CODE_RESOURCE_VAR = 0x41; + + /** + * Kind of Target: Type in exception parameter declaration. + */ + public static final byte TARGET_CODE_EXCEPTION_PARAM = 0x42; + + /** + * Kind of Target: Type in instanceof expression. + */ + public static final byte TARGET_CODE_INSTANCEOF = 0x43; + + /** + * Kind of Target: Type in new declaration. + */ + public static final byte TARGET_CODE_NEW = 0x44; + + /** + * Kind of Target: Type in method reference expression using ::new. + */ + public static final byte TARGET_CODE_METHOD_REF_NEW = 0x45; + + /** + * Kind of Target: Type in method reference expression using ::Identifier. + */ + public static final byte TARGET_CODE_METHOD_REF = 0x46; + + /** + * Kind of Target: Type in cast expression. + */ + public static final byte TARGET_CODE_CAST = 0x47; + + /** + * Kind of Target: Type argument for generic constructor in new expression + * or explicit constructor invocation statement. + */ + public static final byte TARGET_CODE_CONSTRUCTOR = 0x48; + + /** + * Kind of Target: Type argument for generic method in method invocation + * expression. + */ + public static final byte TARGET_CODE_METHOD = 0x49; + + /** + * Kind of Target: Type argument for generic constructor in method + * reference expression using ::new. + */ + public static final byte TARGET_CODE_METHOD_REF_NEW_ARG = 0x4A; + + /** + * Kind of Target: Type argument for generic method in method reference + * expression using ::Identifier. + */ + public static final byte TARGET_CODE_METHOD_REF_ARG = 0x4B; + + // ----- data members ----------------------------------------------- + + /** + * The target_type value for this target. + */ + protected byte m_bType; + } + + + // ----- data structures for class / method / field targets ------------- + + // ----- inner class: TypeParameterTarget ------------------------------- + + /** + * TypeAnnotations with a TypeParameterTarget target the annotation to a + * generic class, interface, method or constructor. + * + * @see #TARGET_CLASS + * @see #TARGET_METHOD + */ + protected static class TypeParameterTarget + extends AbstractTarget + { + /** + * Construct TypeParameterTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x00 or 0x01) + */ + protected TypeParameterTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iTypeParameter = stream.readUnsignedByte(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeByte(m_iTypeParameter); + } + + // ----- AbstractTarget operations ---------------------------------- + + @Override + protected int getSize() + { + return super.getSize() + 1; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the type parameter index to the types in the relevant class, + * interface, method or constructor. + * + * @return the type parameter index to the types in the relevant class, + * interface, method or constructor + */ + public int getTypeParameterIndex() + { + return m_iTypeParameter; + } + + /** + * Set the type parameter index to the types in the relevant class, + * interface, method or constructor. The index must be in the range 0-255. + * + * @param iTypeParameter the type parameter index to the types in the + * relevant class, interface, method or constructor + * + * @throws IllegalArgumentException if iTypeParameter is not within range + */ + public void setTypeParameterIndex(int iTypeParameter) + { + if ((iTypeParameter & ~0xFF) != 0) + { + throw new IllegalArgumentException("Type Parameter index must be in the range 0-255"); + } + m_iTypeParameter = iTypeParameter; + } + + // ----- data members ----------------------------------------------- + + /** + * An index into the class, interface, method, or constructors generic + * type parameters. + */ + protected int m_iTypeParameter; + } + + // ----- inner class: SuperTypeTarget ----------------------------------- + + /** + * TypeAnnotations with a SuperTypeTarget, target the annotation to the + * extends or implements clause of a class or interface. + * + * @see #TARGET_EXT_IMPL + */ + protected static class SuperTypeTarget + extends AbstractTarget + { + /** + * Construct SuperTypeTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x10) + */ + protected SuperTypeTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iSuperType = stream.readUnsignedShort(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_iSuperType); + } + + // ----- AbstractTarget operations ---------------------------------- + + @Override + protected int getSize() + { + return super.getSize() + 2; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the super type index for the relevant class or interface. + * + * @return the super type index for the relevant class or interface + */ + public int getSuperParameterIndex() + { + return m_iSuperType; + } + + /** + * Set the super type index for the the relevant class or interface. + * The index must be in the range 0-65535. + * + * @param iSuperType the super type index of the relevant class or interface + * + * @throws IllegalArgumentException if iSuperType is not within range + */ + public void setSuperTypeIndex(int iSuperType) + { + if ((iSuperType & ~0xFFFF) != 0) + { + throw new IllegalArgumentException("Super Type (extends or implements) " + + "index must be in the range 0-65535"); + } + m_iSuperType = iSuperType; + } + + // ----- data members ----------------------------------------------- + + /** + * An index into the class or interface's extends or implements declaration. + */ + protected int m_iSuperType; + } + + // ----- inner class: TypeParameterBoundTarget -------------------------- + + /** + * TypeAnnotations with a TypeParameterBoundTarget, target the annotation + * to a type parameter in either the generic class, interface, method or + * constructor. + * + * @see #TARGET_PARAM_BOUND_CLASS + * @see #TARGET_PARAM_BOUND_METHOD + */ + protected static class TypeParameterBoundTarget + extends TypeParameterTarget + { + /** + * Construct TypeParameterBoundTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x11 or 0x12) + */ + protected TypeParameterBoundTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + super.disassemble(stream, pool); + + m_iBound = stream.readUnsignedByte(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeByte(m_iBound); + } + + // ----- AbstractTarget operations ---------------------------------- + + @Override + protected int getSize() + { + return super.getSize() + 2; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the bound index to the types in the relevant class, interface, + * method or constructor. + * + * @return the bound index to the types in the relevant class, interface, + * method or constructor + */ + public int getBoundIndex() + { + return m_iBound; + } + + /** + * Set the bound index to the types in the relevant class, interface, + * method or constructor. The index must be in the range 0-255. + * + * @param iBound the bound index to the types in the relevant class, + * interface, method or constructor + * + * @throws IllegalArgumentException if iBound is not within range + */ + public void setBoundIndex(int iBound) + { + if ((iBound & ~0xFF) != 0) + { + throw new IllegalArgumentException("Bound index must be in the range 0-255"); + } + m_iBound = iBound; + } + + // ----- data members ----------------------------------------------- + + /** + * An index into the class, interface, method, or constructors generic + * type parameters. + */ + protected int m_iBound; + } + + // ----- inner class: EmptyTarget --------------------------------------- + + /** + * TypeAnnotations with an EmptyTarget, target the annotation to the + * info object (class, method, or field) where either the {@link + * RuntimeVisibleTypeAnnotationsAttribute} or {@link RuntimeInvisibleTypeAnnotationsAttribute} + * attribute resides, thus no ancillary targeting information is required. + * + * @see #TARGET_FIELD + * @see #TARGET_METHOD_RETURN + * @see #TARGET_METHOD_RECEIVER + */ + protected static class EmptyTarget + extends AbstractTarget + { + /** + * Construct an EmptyTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x13, 0x14, 0x15) + */ + protected EmptyTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + } + } + + // ----- inner class: FormalParameterTarget ----------------------------- + + /** + * TypeAnnotations with a FormalParameterTarget target the annotation to + * the type in a formal parameter declaration of a method, constructor, or + * lambda expression. + * + * @see #TARGET_METHOD_PARAM + */ + protected static class FormalParameterTarget + extends AbstractTarget + { + /** + * Construct FormalParameterTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x16) + */ + protected FormalParameterTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iFormalTypeParam = stream.readUnsignedByte(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeByte(m_iFormalTypeParam); + } + + // ----- AbstractTarget operations ---------------------------------- + + @Override + protected int getSize() + { + return super.getSize() + 1; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the index to the formal type parameter in the relevant method, + * constructor, or lambda expression. + * + * @return index to the formal type parameter in the relevant method, + * constructor, or lambda expression + */ + public int getFormalTypeParamIndex() + { + return m_iFormalTypeParam; + } + + /** + * Set the index to the formal type parameter in the relevant method, + * constructor, or lambda expression. The index must be in the range 0-255. + * + * @param iFormalTypeParam the index to the formal type parameter in + * the relevant method, constructor, or lambda + * expression + * + * @throws IllegalArgumentException if iFormalTypeParam is not within range + */ + public void setFormalTypeParamIndex(int iFormalTypeParam) + { + if ((iFormalTypeParam & ~0xFF) != 0) + { + throw new IllegalArgumentException("Type Parameter index must be in the range 0-255"); + } + m_iFormalTypeParam = iFormalTypeParam; + } + + // ----- data members ----------------------------------------------- + + /** + * An index to the formal type parameter in the relevant method, + * constructor, or lambda expression. + */ + protected int m_iFormalTypeParam; + } + + // ----- inner class: ThrowsTarget -------------------------------------- + + /** + * TypeAnnotations with a ThrowsTarget, target the annotation to the + * throws clause of a method or constructor declaration. + * + * @see #TARGET_METHOD_THROWS + */ + protected static class ThrowsTarget + extends AbstractTarget + { + /** + * Construct ThrowsTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x17) + */ + protected ThrowsTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iThrowsClause = stream.readUnsignedShort(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_iThrowsClause); + } + + // ----- AbstractTarget operations ---------------------------------- + + @Override + protected int getSize() + { + return super.getSize() + 2; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return the throws clause index for the relevant method or constructor. + * + * @return the throws clause index for the relevant method or constructor + */ + public int getThrowsClauseIndex() + { + return m_iThrowsClause; + } + + /** + * Set the throws clause index for the the relevant method or constructor. + * The index must be in the range 0-65535. + * + * @param iThrowsClause the throws clause index of the relevant method + * or constructor + * + * @throws IllegalArgumentException if iThrowsClause is not within range + */ + public void setThrowsClauseIndex(int iThrowsClause) + { + if ((iThrowsClause & ~0xFFFF) != 0) + { + throw new IllegalArgumentException("Method or constructor throws clause " + + "index must be in the range 0-65535"); + } + m_iThrowsClause = iThrowsClause; + } + + // ----- data members ----------------------------------------------- + + /** + * An index into the method or constructor's throws clause. + */ + protected int m_iThrowsClause; + } + + + // ----- data structures for code target -------------------------------- + + // ----- inner class: LocalVariableTarget ------------------------------- + + /** + * TypeAnnotations with a LocalVariableTarget, target the annotation to + * local variable declarations, including variables defined within both the + * try-with-resources statement and the Code attribute of the method. + *

+ * The associated {@link RuntimeVisibleTypeAnnotationsAttribute} or + * {@link RuntimeInvisibleTypeAnnotationsAttribute} must be an attribute + * of the {@link CodeAttribute}. + * + * @see #TARGET_CODE_LOCAL_VAR + * @see #TARGET_CODE_RESOURCE_VAR + */ + protected static class LocalVariableTarget + extends AbstractTarget + { + /** + * Construct LocalVariableTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x40 or 0x41) + */ + protected LocalVariableTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + int cRange = m_cRange = stream.readUnsignedShort(); + LiveRange rangePrev = null; + for (int i = 0; i < cRange; ++i) + { + LiveRange range = new LiveRange(); + range.disassemble(stream, pool); + + if (rangePrev == null) + { + m_firstRange = range; + } + else + { + rangePrev.m_next = range; + } + rangePrev = range; + } + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + int cRange = m_cRange; + stream.writeShort(cRange); + + for (LiveRange range = m_firstRange; range != null; range = range.m_next, --cRange) + { + range.assemble(stream, pool); + } + + assert cRange == 0; + } + + // ----- AbstractTarget operations ---------------------------------- + + @Override + protected int getSize() + { + return super.getSize() + 2 + m_cRange * LiveRange.getSize(); + } + + // ----- accessors -------------------------------------------------- + + /** + * Add a 'live' range for the annotated local variable. + * + * @param of the offset into the code array where this variable + * starts + * @param cLength the number of consecutive elements in the code + * array where this variable is live + * @param iCurrentFrame an index into the local variables for the + * current frame that represents this local variable + */ + public void addLiveRange(int of, int cLength, int iCurrentFrame) + { + LiveRange range = new LiveRange(); + range.setVariableLiveRange(of, cLength); + range.setCurrentFrameIndex(iCurrentFrame); + + if (m_firstRange == null) + { + m_firstRange = range; + } + else + { + LiveRange rangeLast = m_firstRange; + while (rangeLast.m_next != null) + { + rangeLast = rangeLast.m_next; + } + rangeLast.m_next = range; + } + ++m_cRange; + } + + // ----- inner class: LiveRange ------------------------------------- + + /** + * A 'live' range represents a local variable's liveness within a code + * array. + */ + public static class LiveRange + extends VMStructure + implements Constants + { + // ----- VMStructure operations --------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + m_iStartPC = stream.readUnsignedShort(); + m_cLength = stream.readUnsignedShort(); + m_iCurrentFrame = stream.readUnsignedShort(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + stream.writeShort(m_iStartPC); + stream.writeShort(m_cLength); + stream.writeShort(m_iCurrentFrame); + } + + // ----- accessors ---------------------------------------------- + + /** + * Return an offset into the code array where this variable starts. + * + * @return an offset into the code array where this variable starts + */ + public int getCodeOffset() + { + return m_iStartPC; + } + + /** + * Return the number of elements in the code array that represent + * this variable's liveness. + * + * @return the number of elements in the code array where this variable + * is live + */ + public int getLength() + { + return m_cLength; + } + + /** + * Set the window of the code array where this variable is live. + * + * @param of the offset into the code array where this variable + * starts + * @param cLength the number of consecutive elements in the code + * array where this variable is live + */ + public void setVariableLiveRange(int of, int cLength) + { + // we could validate against the code array + m_iStartPC = of; + m_cLength = cLength; + } + + /** + * Return an index into the local variables for the current frame + * that represents this local variable. + * + * @return an index into the local variables for the current frame + * that represents this local variable + */ + public int getCurrentFrameIndex() + { + return m_iCurrentFrame; + } + + /** + * Set the index into the local variables for the current frame + * that represents this local variable. + * + * @param iCurrentFrame an index into the local variables for the + * current frame that represents this local + * variable + */ + public void setCurrentFrameIndex(int iCurrentFrame) + { + m_iCurrentFrame = iCurrentFrame; + } + + /** + * Return the number of bytes consumed by this structure when serialized. + * + * @return the number of bytes consumed by this structure when serialized + */ + protected static int getSize() + { + return 6; + } + + // ----- data members ------------------------------------------- + + /** + * An index into the code array (inclusive) where the variable starts. + */ + protected int m_iStartPC; + + /** + * The number of consecutive elements in the code array where the + * variable is 'live'. + */ + protected int m_cLength; + + /** + * An index into the local variables for the current frame that + * represents this annotated local variable. + */ + protected int m_iCurrentFrame; + + /** + * Next linked LiveRange. + */ + protected LiveRange m_next; + } + + // ----- data members ----------------------------------------------- + + /** + * A count of the number of live ranges. + */ + protected int m_cRange; + + /** + * An index into the method or constructor's throws clause. + */ + protected LiveRange m_firstRange; + } + + // ----- inner class: CatchTarget --------------------------------------- + + /** + * TypeAnnotations with a CatchTarget, target the annotation to the + * ith type in an exception parameter declaration. + * + * @see #TARGET_CODE_EXCEPTION_PARAM + */ + protected static class CatchTarget + extends AbstractTarget + { + /** + * Construct CatchTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x42) + */ + protected CatchTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iExceptionTable = stream.readUnsignedShort(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_iExceptionTable); + } + + // ----- AbstractTarget operations ---------------------------------- + + @Override + protected int getSize() + { + return super.getSize() + 2; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return an index into the {@code exception_table} of the Code attribute. + * + * @return an index into the {@code exception_table} of the Code attribute + */ + public int getExceptionTableIndex() + { + return m_iExceptionTable; + } + + /** + * Set an index into the {@code exception_table} of the Code attribute. + * + * @param iExceptionTable an index into the {@code exception_table} + * of the Code attribute + * + * @throws IllegalArgumentException if iExceptionTable is not within range + */ + public void setExceptionTableIndex(int iExceptionTable) + { + if ((iExceptionTable & ~0xFFFF) != 0) + { + throw new IllegalArgumentException("Index into the exception_table " + + "must be in the range 0-65535"); + } + m_iExceptionTable = iExceptionTable; + } + + // ----- data members ----------------------------------------------- + + /** + * An index into the {@code exception_table} of the Code attribute. + */ + protected int m_iExceptionTable; + } + + // ----- inner class: OffsetTarget -------------------------------------- + + /** + * TypeAnnotations with a OffsetTarget, target the annotation to the type + * in an instanceof expression or a new expression, or + * the type before the {@code ::} in a method reference expression. + * + * @see #TARGET_CODE_INSTANCEOF + * @see #TARGET_CODE_NEW + * @see #TARGET_CODE_METHOD_REF_NEW + * @see #TARGET_CODE_METHOD_REF + */ + protected static class OffsetTarget + extends AbstractTarget + { + /** + * Construct OffsetTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x43, 0x44, 0x45 or 0x46) + */ + protected OffsetTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iCodeArray = stream.readUnsignedShort(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_iCodeArray); + } + + // ----- AbstractTarget operations ---------------------------------- + + @Override + protected int getSize() + { + return super.getSize() + 2; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return an index to an op in the code array of the Code attribute. + * + * @return an index to an op in the code array of the Code attribute + */ + public int getCodeArrayIndex() + { + return m_iCodeArray; + } + + /** + * Set the index to an op in the code array of the Code attribute. + * + * @param iCodeArray an index to an op in the code array of the Code + * attribute + * + * @throws IllegalArgumentException if iCodeArray is not within range + */ + public void setCodeArrayIndex(int iCodeArray) + { + if ((iCodeArray & ~0xFFFF) != 0) + { + throw new IllegalArgumentException("Index into the code array " + + "must be in the range 0-65535"); + } + m_iCodeArray = iCodeArray; + } + + // ----- data members ----------------------------------------------- + + /** + * An index to an op in the code array of the Code attribute. + */ + protected int m_iCodeArray; + } + + // ----- inner class: TypeArgumentTarget -------------------------------- + + /** + * TypeAnnotations with a TypeArgumentTarget, target the annotation to a + * type in either a new expression, an explicit constructor invocation + * statement, a method invocation expression, or a method reference expression. + * + * @see #TARGET_CODE_CAST + * @see #TARGET_CODE_CONSTRUCTOR + * @see #TARGET_CODE_METHOD + * @see #TARGET_CODE_METHOD_REF_NEW_ARG + * @see #TARGET_CODE_METHOD_REF_ARG + */ + protected static class TypeArgumentTarget + extends AbstractTarget + { + /** + * Construct TypeArgumentTarget used by {@link TypeAnnotation}. + * + * @param bType byte representing target_type (0x43, 0x44, 0x45 or 0x46) + */ + protected TypeArgumentTarget(byte bType) + { + super(bType); + } + + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_iCodeArray = stream.readUnsignedShort(); + m_iTypeArgument = stream.readUnsignedByte(); + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + + stream.writeShort(m_iCodeArray); + stream.writeByte(m_iTypeArgument); + } + + // ----- AbstractTarget operations ---------------------------------- + + @Override + protected int getSize() + { + return super.getSize() + 3; + } + + // ----- accessors -------------------------------------------------- + + /** + * Return an index to an op in the code array of the Code attribute. + * + * @return an index to an op in the code array of the Code attribute + */ + public int getCodeArrayIndex() + { + return m_iCodeArray; + } + + /** + * Set the index to an op in the code array of the Code attribute. + * + * @param iCodeArray an index to an op in the code array of the Code + * attribute + * + * @throws IllegalArgumentException if iCodeArray is not within range + */ + public void setCodeArrayIndex(int iCodeArray) + { + if ((iCodeArray & ~0xFFFF) != 0) + { + throw new IllegalArgumentException("Index into the code array " + + "must be in the range 0-65535"); + } + m_iCodeArray = iCodeArray; + } + + /** + * Return an index to the type in a cast expression or an index into + * an explicit type argument list. + * + * @return an index to the type in a cast expression or an index into + * an explicit type argument list + */ + public int getTypeIndex() + { + return m_iTypeArgument; + } + + /** + * Set the index to the type in a cast expression or an index into + * an explicit type argument list. + * + * @param iType the index into the type in a cast expression or an index + * into an explicit type argument list + * + * @throws IllegalArgumentException if iType is not within range + */ + public void setTypeIndex(int iType) + { + if ((iType & ~0xFF) != 0) + { + throw new IllegalArgumentException("Index to the type in a cast expression " + + " or explicit type argument list must be in the range 0-255"); + } + m_iTypeArgument = iType; + } + + // ----- data members ----------------------------------------------- + + /** + * An index to an op in the code array of the Code attribute. + */ + protected int m_iCodeArray; + + /** + * Either an index into the type in a cast expression or an index into + * an explicit type argument list. + */ + protected int m_iTypeArgument; + } + + + // ----- inner class: TypePath ------------------------------------------ + + /** + * The TypePath structure allows locating when in a type declaration an + * annotation resides. The structure as defined in the JVM 8 specification + * is as follows: + *


+     *    type_path
+     *        {
+     *        u1 path_length;
+     *            {
+     *            u1 type_path_kind;
+     *            u1 type_argument_index;
+     *            } path[path_length];
+     *       }
+     * 
+ */ + protected static class TypePath + extends VMStructure + implements Constants + { + // ----- VMStructure operations ------------------------------------- + + @Override + protected void disassemble(DataInput stream, ConstantPool pool) throws IOException + { + int cPath = m_cPath = stream.readUnsignedByte(); + char[] acPath = m_achPath; + if (cPath > 0) + { + acPath = m_achPath = new char[cPath]; + } + + for (int i = 0; i < cPath; ++i) + { + acPath[i] = toChar(stream.readUnsignedByte(), + stream.readUnsignedByte()); + } + } + + @Override + protected void preassemble(ConstantPool pool) + { + } + + @Override + protected void assemble(DataOutput stream, ConstantPool pool) throws IOException + { + int cPath = m_cPath; + stream.writeByte(cPath); + + for (int i = 0; i < cPath; ++i) + { + stream.writeByte(getPathKind(i)); + stream.writeByte(getArgumentIndex(i)); + } + } + + // ----- public methods --------------------------------------------- + + /** + * Add a path (type_path_kind and type_argument_index) to this TypePath. + * + * @param nPathKind one of the PATH_KIND_* constants + * @param iArgument an index into an argument list + * @param iPath an index into the type_path array this path should + * reside + */ + public void addPath(int nPathKind, int iArgument, int iPath) + { + if ((nPathKind & ~PATH_KIND_MASK) != 0) + { + throw new IllegalArgumentException("Invalid type_path_kind value: " + nPathKind); + } + if ((iArgument & ~0xFF) != 0) + { + throw new IllegalArgumentException("Invalid type_argument_index provided: " + iArgument); + } + + int cPathUsed = size(); + if (iPath == -1) + { + iPath = cPathUsed; + } + + if (iPath >= m_achPath.length) + { + grow(); + } + + if ((m_achPath[iPath] & 0xFF00) != NO_PATH) + { + if (m_achPath.length == cPathUsed) + { + // about to shift elements thus ensure sufficient slots exist + grow(); + } + + // shift elements to free-up the slot requested + char[] acPath = m_achPath; + for (int i = cPathUsed - 1; i >= iPath; --i) + { + acPath[i + 1] = acPath[i]; + } + } + m_achPath[iPath] = toChar(nPathKind, iArgument); + ++m_cPath; + } + + /** + * Remove a path from the TypePath. This may cause other paths to move + * down a slot. + * + * @param iPath an index into the type_path array that should be removed + */ + public void removePath(int iPath) + { + int cPath = size(); + if (iPath < 0) + { + iPath = cPath; + } + + char[] achPath = m_achPath; + for (int i = iPath; i < cPath; ++i) + { + achPath[i] = achPath[i + 1]; + } + achPath[cPath - 1] = NO_PATH; + --m_cPath; + } + + /** + * Return the number of type paths present. + * + * @return the number of type paths present + */ + public int size() + { + return m_cPath; + } + + /** + * Return the type_path_kind for the specified path. The returned value + * will be -1 if the path has not been defined or one of the PATH_KIND_* + * constants. + * + * @param iPath an index into the type_path array + * + * @return the type_path_kind for the specified path or -1 + */ + public int getPathKind(int iPath) + { + char chPath = m_achPath[iPath]; + return chPath == NO_PATH ? -1 : chPath >> 8; + } + + /** + * Return the type_argument_index for the specified path, or -1 if the + * path does not exist. + * + * @param iPath an index into the type_path array + * + * @return the type_argument_index for the specified path, or -1 if the + * path does not exist + */ + public int getArgumentIndex(int iPath) + { + char chPath = m_achPath[iPath]; + return chPath == NO_PATH ? -1 : chPath & 0xFF; + } + + // ----- helpers ---------------------------------------------------- + + /** + * Return the number of bytes consumed by this structure when serialized. + * + * @return the number of bytes consumed by this structure when serialized + */ + protected int getSize() + { + return 1 + 2 * size(); + } + + /** + * Grow the internal data structure to accommodate more paths. + */ + protected void grow() + { + char[] acOld = m_achPath; + int cPath = acOld.length; + char[] acNew = new char[cPath + Math.min(Math.max(cPath >> 1, 4), 16)]; + + Arrays.fill(acNew, Math.max(cPath - 1, 0), acNew.length, NO_PATH); + + System.arraycopy(acOld, 0, acNew, 0, cPath); + } + + /** + * Create a char (unsigned short) representing two unsigned bytes + * (type_path_kind and type_argument_index). + * + * @param nPathKind the type_path_kind value; one of the PATH_KIND_* + * constants + * @param iArgument the type_argument_index value for this path + * + * @return a char (unsigned short) representing two unsigned bytes + * (type_path_kind and type_argument_index) + */ + protected static char toChar(int nPathKind, int iArgument) + { + return (char) (nPathKind << 8 | iArgument); + } + + // ----- constants -------------------------------------------------- + + /** + * Type Path Kind: Annotation is deeper in an array type. + */ + public static final byte PATH_KIND_ARRAY = 0x0; + + /** + * Type Path Kind: Annotation is deeper in a nested type. + */ + public static final byte PATH_KIND_NESTED = 0x1; + + /** + * Type Path Kind: Annotation is on the bound of a wildcard type argument + * of a parameterized type. + */ + public static final byte PATH_KIND_BOUND = 0x2; + + /** + * Type Path Kind: Annotation is on a type argument of a parametrized + * type. + */ + public static final byte PATH_KIND_TYPE = 0x3; + + /** + * Mask used to validate a legal path kind. + */ + private static final byte PATH_KIND_MASK = 0x3; + + /** + * Default value to suggest no path. + */ + private static final char NO_PATH = (char) 0x8000; + + // ----- data members ----------------------------------------------- + + /** + * The number of paths to locate where the annotation resides on the + * type. + */ + protected int m_cPath; + + /** + * A char array used to hold the type paths to locate where an annotation + * resides on a type declaration. + */ + protected char[] m_achPath = new char[0]; + } + + + // ----- data members --------------------------------------------------- + + /** + * The target this annotation has been placed on. + */ + protected AbstractTarget m_target; + + /** + * The TypePath structure allows the part of the type annotated to be + * located when the target is a type. + */ + protected TypePath m_typePath; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/UtfConstant.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/UtfConstant.java new file mode 100644 index 0000000000000..442feaae10e59 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/UtfConstant.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represent a Java Virtual Machine character string constant. Several +* constant types represent a string value, but this constant type actually +* has (as opposed to "references") a string value stored in the .class +* structure in UTF-8 format. +* +* @version 0.50, 05/12/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class UtfConstant extends Constant implements Constants + { + // ----- construction --------------------------------------------------- + + /** + * Constructor used internally by the Constant class. + */ + protected UtfConstant() + { + super(CONSTANT_UTF8); + } + + /** + * Construct a constant whose value is a java string. + * + * @param sText the java string + */ + public UtfConstant(String sText) + { + this(); + + if (sText == null) + { + throw new IllegalArgumentException(CLASS + ": Value cannot be null!"); + } + + m_sText = sText; + } + + + // ----- VMStructure operations ----------------------------------------- + + /** + * Read the constant information from the stream. Since constants can be + * inter-related, the dependencies are not derefenced until all constants + * are disassembled; at that point, the constants are resolved using the + * postdisassemble method. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the constant information + * @param pool the constant pool for the class which does not yet + * contain the constants referenced by this constant + */ + protected void disassemble(DataInput stream, ConstantPool pool) + throws IOException + { + m_sText = stream.readUTF(); + } + + /** + * The assembly process assembles and writes the constant to the passed + * output stream. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled constant + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected void assemble(DataOutput stream, ConstantPool pool) + throws IOException + { + super.assemble(stream, pool); + stream.writeUTF(m_sText); + } + + + // ----- Comparable operations ------------------------------------------ + + /** + * Compares this Object with the specified Object for order. Returns a + * negative integer, zero, or a positive integer as this Object is less + * than, equal to, or greater than the given Object. + * + * @param obj the Object to be compared. + * + * @return a negative integer, zero, or a positive integer as this Object + * is less than, equal to, or greater than the given Object. + * + * @exception ClassCastException the specified Object's type prevents it + * from being compared to this Object. + */ + public int compareTo(Object obj) + { + UtfConstant that = (UtfConstant) obj; + return this.m_sText.compareTo(that.m_sText); + } + + + // ----- Object operations ---------------------------------------------- + + /** + * Produce a human-readable string describing the constant. + * + * @return a string describing the constant + */ + public String toString() + { + return "(Utf) " + toQuotedStringEscape(m_sText); + } + + /** + * Format the constant as it would appear in JASM code. + * + * @return the constant as it would appear in JASM code + */ + public String format() + { + return m_sText; + } + + /** + * Compare this object to another object for equality. + * + * @param obj the other object to compare to this + * + * @return true if this object equals that object + */ + public boolean equals(Object obj) + { + try + { + UtfConstant that = (UtfConstant) obj; + return this == that + || this.getClass() == that.getClass() + && this.m_sText.equals(that.m_sText); + } + catch (NullPointerException e) + { + // obj is null + return false; + } + catch (ClassCastException e) + { + // obj is not of this class + return false; + } + } + + + // ----- accessors ------------------------------------------------------ + + /** + * Get the string value of the constant. + * + * @return the constant's string value + */ + public String getValue() + { + return m_sText; + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "UtfConstant"; + + /** + * The constant string value. + */ + private String m_sText; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/VMStructure.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/VMStructure.java new file mode 100644 index 0000000000000..d181e138b7c93 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/VMStructure.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.assembler; + + +import com.tangosol.util.Base; + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* Represents a Java Virtual Machine structure as defined by the Java Virtual +* Machine (JVM) Specification. The JVM structures include the .class file +* structure and the structures which it can contain, such as fields, methods, +* and attributes. +* +* @version 0.50, 05/11/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public abstract class VMStructure + extends Base + implements Constants + { + /** + * The disassembly process reads the structure from the passed input + * stream and uses the constant pool to dereference any constant + * references. + * + * @param stream the stream implementing java.io.DataInput from which + * to read the assembled VM structure + * @param pool the constant pool for the class which contains any + * constants referenced by this VM structure + */ + protected abstract void disassemble(DataInput stream, ConstantPool pool) + throws IOException; + + /** + * The pre-assembly step collects the necessary entries for the constant + * pool. During this step, all constants used by this VM structure and + * any sub-structures are registered with (but not yet bound by position + * in) the constant pool. + * + * @param pool the constant pool for the class which needs to be + * populated with the constants required to build this + * VM structure + */ + protected abstract void preassemble(ConstantPool pool); + + /** + * The assembly process assembles and writes the structure to the passed + * output stream, resolving any dependencies using the passed constant + * pool. + * + * @param stream the stream implementing java.io.DataOutput to which to + * write the assembled VM structure + * @param pool the constant pool for the class which by this point + * contains the entire set of constants required to build + * this VM structure + */ + protected abstract void assemble(DataOutput stream, ConstantPool pool) + throws IOException; + + /** + * Determine the identity of the VM structure (if applicable). + * + * @return the string identity of the VM structure + */ + public String getIdentity() + { + return null; + } + + /** + * Determine if the VM structure (or any contained VM structure) has been + * modified. + * + * @return true if the VM structure has been modified + */ + public boolean isModified() + { + return false; + } + + /** + * Reset the modified state of the VM structure. + */ + protected void resetModified() + { + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "VMStructure"; + + /** + * Debug flag. + */ + protected static final boolean DEBUG = false; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Znewarray.java b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Znewarray.java new file mode 100644 index 0000000000000..8b1db40c67b1c --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/Znewarray.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + + +package com.tangosol.dev.assembler; + + +import java.io.IOException; +import java.io.DataInput; +import java.io.DataOutput; + + +/** +* The ZNEWARRAY pseudo-op instantiates an array of boolean. +*

+* JASM op         :  ZNEWARRAY    (0xf3)
+* JVM byte code(s):  NEWARRAY     (0xbc)
+* Details         :
+* 
+* +* @version 0.50, 06/15/98, assembler/dis-assembler +* @author Cameron Purdy +*/ +public class Znewarray extends OpArray implements Constants + { + // ----- constructors --------------------------------------------------- + + /** + * Construct the op. + */ + public Znewarray() + { + super(ZNEWARRAY); + } + + + // ----- data members --------------------------------------------------- + + /** + * The name of this class. + */ + private static final String CLASS = "Znewarray"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/package.html b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/package.html new file mode 100644 index 0000000000000..5f5f4574506f6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/assembler/package.html @@ -0,0 +1,5 @@ + +Contains Java assembler classes. + +@serial exclude + diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Colorizer.java b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Colorizer.java new file mode 100644 index 0000000000000..b963802aacf6a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Colorizer.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.compiler; + + +/** +* This interface represents a script colorizer for use by an editor. +* +* @version 0.90, 11/16/98 +* @author Cameron Purdy +*/ +public interface Colorizer + { + // TBD + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Compiler.java b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Compiler.java new file mode 100644 index 0000000000000..d6e039b06ada6 --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Compiler.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.compiler; + + +import com.tangosol.util.ErrorList; + + +/** +* This interface represents a script compiler. +* +* The default (no-parameter) constructor is required to be available (public) +* for a compiler, since compilers are located and loaded dynamically by +* inferring the compiler class name from the language name, and then +* instantiating the compiler dynamically. As a result, the constructor must +* be predictable, and the no-parameter constructor is the easiest to use. +* +* A side-effect of the default constructor is that the compiler has no state +* after it is created. Therefore the compile method acts as both an +* initializer and a command to compile. If the compiler has global state +* (uses fields to store its state) then the compile method must be +* synchronized in case the compilation process is multi-threaded. +* +* The compiler may be re-used (multiple calls to compile) or may be used only +* to compile a single script. +* +* @version 1.00, 09/04/98 +* @author Cameron Purdy +*/ +public interface Compiler + { + /** + * Compile the passed script. + * + * @param ctx the script compilation context + * @param sScript the script to compile (as a string) + * @param errlist the error list to log to + * + * @exception CompilerException thrown if the compilation of this script + * fails + */ + void compile(Context ctx, String sScript, ErrorList errlist) + throws CompilerException; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/CompilerErrorInfo.java b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/CompilerErrorInfo.java new file mode 100644 index 0000000000000..3f1dfc1c6d79f --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/CompilerErrorInfo.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.compiler; + + +import java.util.ResourceBundle; + +import com.tangosol.util.ErrorList; + + +/** +* Extends the generic ErrorList.Item class to include compilation information. +* +* @version 1.00, 12/01/96 +* @author Cameron Purdy +* @see com.tangosol.util.ErrorList.Item +*/ +public class CompilerErrorInfo + extends ErrorList.Item + { + // ----- constructors --------------------------------------------------- + + /** + * Constructs a CompilerErrorInfo based on a severity level, error + * code, and replaceable parameters for the error description. + * + * @param nSeverity the error severity + * @param sCode the error code + * @param rb the ResourceBundle object to use to get the error description + * @param asParam an array of replaceable parameters + * @param iLine line in which the error was detected + * @param ofInLine offset of the error within the line + * @param cchError length of the detected error + */ + public CompilerErrorInfo(int nSeverity, String sCode, ResourceBundle rb, + String[] asParam, int iLine, int ofInLine, int cchError) + { + this(nSeverity, sCode, rb, asParam, iLine, ofInLine, iLine, ofInLine + cchError); + } + + /** + * Constructs a CompilerErrorInfo based on a severity level, error + * code, and replaceable parameters for the error description. + * + * @param nSeverity the error severity + * @param sCode the error code + * @param rb the ResourceBundle object to use to get the error description + * @param asParam an array of replaceable parameters + * @param iStartLine line in which the error was detected + * @param ofStart offset of the error within the line + * @param iEndLine last line in which the error was detected + * @param ofEnd length of the detected error + */ + public CompilerErrorInfo(int nSeverity, String sCode, ResourceBundle rb, + String[] asParam, int iStartLine, int ofStart, int iEndLine, int ofEnd) + { + super(sCode, nSeverity, null, asParam, null, rb); + + m_iStartLine = iStartLine; + m_ofStart = ofStart; + m_iEndLine = iEndLine; + m_ofEnd = ofEnd; + } + + + // ----- error info interface ------------------------------------------- + + /** + * Determines the line number that the error occurred in. + * + * @return the 0-based line number that the error occurred in + */ + public int getLine() + { + return m_iStartLine; + } + + /** + * Determines the offset within the line where the error occurred. + * + * @return the 0-based offset within the line where the error occurred + */ + public int getOffset() + { + return m_ofStart; + } + + /** + * Determines the length of the portion of the script which caused the + * error. If the length cannot be determined, 0 is returned. + * + * @return the length of the error + */ + public int getLength() + { + return m_iStartLine == m_iEndLine ? m_ofEnd - m_ofStart : 0; + } + + /** + * Determines the line number that the error occurred in. + * + * @return the 0-based line number that the error occurred in + */ + public int getEndLine() + { + return m_iEndLine; + } + + /** + * Determines the offset within the line where the error occurred. + * + * @return the 0-based offset within the line where the error occurred + */ + public int getEndOffset() + { + return m_ofEnd; + } + + /** + * Returns the localized textual description of the error, including the + * location within the script of the error. + * + * @return the localized error description + */ + public String toString() + { + return "(" + getLine() + ',' + getOffset() + ',' + getLength() + ") " + super.toString(); + } + + + // ----- data members --------------------------------------------------- + + /** + * Starting line. + */ + private int m_iStartLine; + + /** + * Starting offset. + */ + private int m_ofStart; + + /** + * Ending line. + */ + private int m_iEndLine; + + /** + * Ending offset. + */ + private int m_ofEnd; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/CompilerException.java b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/CompilerException.java new file mode 100644 index 0000000000000..c65041788ed1a --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/CompilerException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.compiler; + + +/** +* The CompilerException exception is used to stop the compilation of +* a script. It is thrown when an element of the compilation process +* determines that compilation cannot complete successfully and that +* compilation should be stopped. Compilation is not necessarily stopped +* when the first compilation error is encountered (as in a syntax, lexical, +* or semantic error) because the user typically wants to know about as many +* errors as possible. +* +* @version 1.00, 11/22/96 +* @author Cameron Purdy +*/ +public class CompilerException + extends Exception + { + /** + * Constructs a CompilerException with no detail message. + * A detail message is a String that describes this particular exception. + */ + public CompilerException() + { + super(); + } + + /** + * Constructs a CompilerException with the specified detail message. + * A detail message is a String that describes this particular exception. + * @param s the String that contains a detailed message + */ + public CompilerException(String s) + { + super(s); + } + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Constants.java b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Constants.java new file mode 100644 index 0000000000000..e0683750bdc7e --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Constants.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.compiler; + + +import com.tangosol.util.ClassHelper; +import com.tangosol.util.ErrorList; +import com.tangosol.util.Resources; + + +/** +* Compiler constants. +* +* @version 1.00, 2000.03.22 +* @author Cameron Purdy +*/ +public interface Constants + extends ErrorList.Constants + { + /** + * The package resources. + */ + public static final Resources RESOURCES = + ClassHelper.getPackageResources("com.tangosol.dev.compiler."); + + + // ----- error codes ---------------------------------------------------- + + public static final String WARN_DEPRECATED = "CMP-001"; + } diff --git a/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Context.java b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Context.java new file mode 100644 index 0000000000000..58d9bfc02b25b --- /dev/null +++ b/prj/coherence-core/src/main/java/com/tangosol/dev/compiler/Context.java @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2000, 2020, Oracle and/or its affiliates. + * + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package com.tangosol.dev.compiler; + + +import com.tangosol.dev.assembler.CodeAttribute; +import com.tangosol.dev.assembler.Field; +import com.tangosol.dev.assembler.FieldConstant; +import com.tangosol.dev.assembler.Method; +import com.tangosol.dev.assembler.MethodConstant; + +import com.tangosol.dev.component.DataType; + +import java.util.Enumeration; + + +/** +* This interface represents the context within which a compiler is +* operating. A context is used to resolve names which cannot be +* determined by the compiler itself. +* +* @version 1.00, 09/14/98 +* @author Cameron Purdy +*/ +public interface Context + { + // ----- environment ---------------------------------------------------- + + /** + * Generally speaking, compile types are "debug" and "non-debug". More + * specific optimizations can be toggled in a compiler-specific manner + * by providing compiler-specific options. + * + * @return true if the compile type is "debug" + */ + boolean isDebug(); + + /** + * The compiler can be used to check the script for syntax and semantic + * errors without producing code. + * + * @return true if the compiler is only being used to check the script + */ + boolean isCheck(); + + /** + * Get a compiler-specific option. + * + * @param sOption the name of a compiler-specific option in the form + * .